]> git.mxchange.org Git - friendica.git/commitdiff
Merge remote-tracking branch 'upstream/develop' into 1601-api-statuses-lookup
authorMichael Vogel <icarus@dabo.de>
Sun, 14 Feb 2016 20:04:59 +0000 (21:04 +0100)
committerMichael Vogel <icarus@dabo.de>
Sun, 14 Feb 2016 20:04:59 +0000 (21:04 +0100)
877 files changed:
README.translate.md
boot.php
database.sql
doc/Forums.md
doc/Home.md
doc/Plugins.md
doc/SSL.md
doc/Settings.md
doc/Tags-and-Mentions.md
doc/api.md
doc/autoloader.md [new file with mode: 0644]
doc/database.md
doc/database/db_gcontact.md
doc/database/db_guid.md [deleted file]
doc/database/db_item.md
doc/database/db_oembed.md [new file with mode: 0644]
doc/database/db_parsed_url.md [new file with mode: 0644]
doc/database/db_thread.md
doc/database/db_unique_contacts.md [deleted file]
doc/de/Plugins.md
doc/htconfig.md
doc/smarty3-templates.md [new file with mode: 0644]
doc/snarty3-templates.md [deleted file]
doc/translations.md
friendica_test_data.sql
htconfig.php
include/Contact.php
include/ForumManager.php [new file with mode: 0644]
include/NotificationsManager.php [new file with mode: 0644]
include/Photo.php
include/Scrape.php
include/acl_selectors.php
include/api.php
include/autoloader.php [new file with mode: 0644]
include/autoloader/ClassLoader.php [new file with mode: 0644]
include/autoloader/LICENSE.composer [new file with mode: 0644]
include/autoloader/autoload_classmap.php [new file with mode: 0644]
include/autoloader/autoload_files.php [new file with mode: 0644]
include/autoloader/autoload_namespaces.php [new file with mode: 0644]
include/autoloader/autoload_psr4.php [new file with mode: 0644]
include/bbcode.php
include/config.php
include/conversation.php
include/cron.php
include/datetime.php
include/dbstructure.php
include/delivery.php
include/dfrn.php [new file with mode: 0644]
include/diaspora.php
include/discover_poco.php
include/enotify.php
include/event.php
include/expire.php
include/features.php
include/follow.php
include/forums.php [deleted file]
include/friendica_smarty.php
include/gprobe.php
include/group.php
include/identity.php
include/items.php
include/like.php
include/network.php
include/notifier.php
include/oembed.php
include/onepoll.php
include/ostatus.php
include/plaintext.php
include/poller.php
include/profile_update.php
include/queue.php
include/remoteupdate.php [deleted file]
include/socgraph.php
include/text.php
include/threads.php
index.php
js/acl.js
js/main.js
library/Chart.js-1.0.2/Chart.min.js [new file with mode: 0644]
library/Chart.js-1.0.2/LICENSE.md [new file with mode: 0644]
library/Chart.js-1.0.2/README.md [new file with mode: 0644]
library/HTMLPurifier.auto.php [deleted file]
library/HTMLPurifier.autoload.php [deleted file]
library/HTMLPurifier.func.php [deleted file]
library/HTMLPurifier.includes.php [deleted file]
library/HTMLPurifier.kses.php [deleted file]
library/HTMLPurifier.path.php [deleted file]
library/HTMLPurifier.php [deleted file]
library/HTMLPurifier.safe-includes.php [deleted file]
library/HTMLPurifier/AttrCollections.php [deleted file]
library/HTMLPurifier/AttrDef.php [deleted file]
library/HTMLPurifier/AttrDef/CSS.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/AlphaValue.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/Background.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/Border.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/Color.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/Composite.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/Filter.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/Font.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/FontFamily.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/Length.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/ListStyle.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/Multiple.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/Number.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/Percentage.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/TextDecoration.php [deleted file]
library/HTMLPurifier/AttrDef/CSS/URI.php [deleted file]
library/HTMLPurifier/AttrDef/Enum.php [deleted file]
library/HTMLPurifier/AttrDef/HTML/Bool.php [deleted file]
library/HTMLPurifier/AttrDef/HTML/Class.php [deleted file]
library/HTMLPurifier/AttrDef/HTML/Color.php [deleted file]
library/HTMLPurifier/AttrDef/HTML/FrameTarget.php [deleted file]
library/HTMLPurifier/AttrDef/HTML/ID.php [deleted file]
library/HTMLPurifier/AttrDef/HTML/Length.php [deleted file]
library/HTMLPurifier/AttrDef/HTML/LinkTypes.php [deleted file]
library/HTMLPurifier/AttrDef/HTML/MultiLength.php [deleted file]
library/HTMLPurifier/AttrDef/HTML/Nmtokens.php [deleted file]
library/HTMLPurifier/AttrDef/HTML/Pixels.php [deleted file]
library/HTMLPurifier/AttrDef/Integer.php [deleted file]
library/HTMLPurifier/AttrDef/Lang.php [deleted file]
library/HTMLPurifier/AttrDef/Switch.php [deleted file]
library/HTMLPurifier/AttrDef/Text.php [deleted file]
library/HTMLPurifier/AttrDef/URI.php [deleted file]
library/HTMLPurifier/AttrDef/URI/Email.php [deleted file]
library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php [deleted file]
library/HTMLPurifier/AttrDef/URI/Host.php [deleted file]
library/HTMLPurifier/AttrDef/URI/IPv4.php [deleted file]
library/HTMLPurifier/AttrDef/URI/IPv6.php [deleted file]
library/HTMLPurifier/AttrTransform.php [deleted file]
library/HTMLPurifier/AttrTransform/Background.php [deleted file]
library/HTMLPurifier/AttrTransform/BdoDir.php [deleted file]
library/HTMLPurifier/AttrTransform/BgColor.php [deleted file]
library/HTMLPurifier/AttrTransform/BoolToCSS.php [deleted file]
library/HTMLPurifier/AttrTransform/Border.php [deleted file]
library/HTMLPurifier/AttrTransform/EnumToCSS.php [deleted file]
library/HTMLPurifier/AttrTransform/ImgRequired.php [deleted file]
library/HTMLPurifier/AttrTransform/ImgSpace.php [deleted file]
library/HTMLPurifier/AttrTransform/Input.php [deleted file]
library/HTMLPurifier/AttrTransform/Lang.php [deleted file]
library/HTMLPurifier/AttrTransform/Length.php [deleted file]
library/HTMLPurifier/AttrTransform/Name.php [deleted file]
library/HTMLPurifier/AttrTransform/NameSync.php [deleted file]
library/HTMLPurifier/AttrTransform/SafeEmbed.php [deleted file]
library/HTMLPurifier/AttrTransform/SafeObject.php [deleted file]
library/HTMLPurifier/AttrTransform/SafeParam.php [deleted file]
library/HTMLPurifier/AttrTransform/ScriptRequired.php [deleted file]
library/HTMLPurifier/AttrTransform/Textarea.php [deleted file]
library/HTMLPurifier/AttrTypes.php [deleted file]
library/HTMLPurifier/AttrValidator.php [deleted file]
library/HTMLPurifier/Bootstrap.php [deleted file]
library/HTMLPurifier/CSSDefinition.php [deleted file]
library/HTMLPurifier/ChildDef.php [deleted file]
library/HTMLPurifier/ChildDef/Chameleon.php [deleted file]
library/HTMLPurifier/ChildDef/Custom.php [deleted file]
library/HTMLPurifier/ChildDef/Empty.php [deleted file]
library/HTMLPurifier/ChildDef/Optional.php [deleted file]
library/HTMLPurifier/ChildDef/Required.php [deleted file]
library/HTMLPurifier/ChildDef/StrictBlockquote.php [deleted file]
library/HTMLPurifier/ChildDef/Table.php [deleted file]
library/HTMLPurifier/Config.php [deleted file]
library/HTMLPurifier/ConfigSchema.php [deleted file]
library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php [deleted file]
library/HTMLPurifier/ConfigSchema/Builder/Xml.php [deleted file]
library/HTMLPurifier/ConfigSchema/Exception.php [deleted file]
library/HTMLPurifier/ConfigSchema/Interchange.php [deleted file]
library/HTMLPurifier/ConfigSchema/Interchange/Directive.php [deleted file]
library/HTMLPurifier/ConfigSchema/Interchange/Id.php [deleted file]
library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php [deleted file]
library/HTMLPurifier/ConfigSchema/Validator.php [deleted file]
library/HTMLPurifier/ConfigSchema/ValidatorAtom.php [deleted file]
library/HTMLPurifier/ConfigSchema/schema.ser [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt [deleted file]
library/HTMLPurifier/ConfigSchema/schema/info.ini [deleted file]
library/HTMLPurifier/ContentSets.php [deleted file]
library/HTMLPurifier/Context.php [deleted file]
library/HTMLPurifier/Definition.php [deleted file]
library/HTMLPurifier/DefinitionCache.php [deleted file]
library/HTMLPurifier/DefinitionCache/Decorator.php [deleted file]
library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php [deleted file]
library/HTMLPurifier/DefinitionCache/Decorator/Memory.php [deleted file]
library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in [deleted file]
library/HTMLPurifier/DefinitionCache/Null.php [deleted file]
library/HTMLPurifier/DefinitionCache/Serializer.php [deleted file]
library/HTMLPurifier/DefinitionCache/Serializer/README [deleted file]
library/HTMLPurifier/DefinitionCacheFactory.php [deleted file]
library/HTMLPurifier/Doctype.php [deleted file]
library/HTMLPurifier/DoctypeRegistry.php [deleted file]
library/HTMLPurifier/ElementDef.php [deleted file]
library/HTMLPurifier/Encoder.php [deleted file]
library/HTMLPurifier/EntityLookup.php [deleted file]
library/HTMLPurifier/EntityLookup/entities.ser [deleted file]
library/HTMLPurifier/EntityParser.php [deleted file]
library/HTMLPurifier/ErrorCollector.php [deleted file]
library/HTMLPurifier/ErrorStruct.php [deleted file]
library/HTMLPurifier/Exception.php [deleted file]
library/HTMLPurifier/Filter.php [deleted file]
library/HTMLPurifier/Filter/ExtractStyleBlocks.php [deleted file]
library/HTMLPurifier/Filter/YouTube.php [deleted file]
library/HTMLPurifier/Generator.php [deleted file]
library/HTMLPurifier/HTMLDefinition.php [deleted file]
library/HTMLPurifier/HTMLModule.php [deleted file]
library/HTMLPurifier/HTMLModule/Bdo.php [deleted file]
library/HTMLPurifier/HTMLModule/CommonAttributes.php [deleted file]
library/HTMLPurifier/HTMLModule/Edit.php [deleted file]
library/HTMLPurifier/HTMLModule/Forms.php [deleted file]
library/HTMLPurifier/HTMLModule/Hypertext.php [deleted file]
library/HTMLPurifier/HTMLModule/Image.php [deleted file]
library/HTMLPurifier/HTMLModule/Legacy.php [deleted file]
library/HTMLPurifier/HTMLModule/List.php [deleted file]
library/HTMLPurifier/HTMLModule/Name.php [deleted file]
library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php [deleted file]
library/HTMLPurifier/HTMLModule/Object.php [deleted file]
library/HTMLPurifier/HTMLModule/Presentation.php [deleted file]
library/HTMLPurifier/HTMLModule/Proprietary.php [deleted file]
library/HTMLPurifier/HTMLModule/Ruby.php [deleted file]
library/HTMLPurifier/HTMLModule/SafeEmbed.php [deleted file]
library/HTMLPurifier/HTMLModule/SafeObject.php [deleted file]
library/HTMLPurifier/HTMLModule/Scripting.php [deleted file]
library/HTMLPurifier/HTMLModule/StyleAttribute.php [deleted file]
library/HTMLPurifier/HTMLModule/Tables.php [deleted file]
library/HTMLPurifier/HTMLModule/Target.php [deleted file]
library/HTMLPurifier/HTMLModule/Text.php [deleted file]
library/HTMLPurifier/HTMLModule/Tidy.php [deleted file]
library/HTMLPurifier/HTMLModule/Tidy/Name.php [deleted file]
library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php [deleted file]
library/HTMLPurifier/HTMLModule/Tidy/Strict.php [deleted file]
library/HTMLPurifier/HTMLModule/Tidy/Transitional.php [deleted file]
library/HTMLPurifier/HTMLModule/Tidy/XHTML.php [deleted file]
library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php [deleted file]
library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php [deleted file]
library/HTMLPurifier/HTMLModuleManager.php [deleted file]
library/HTMLPurifier/IDAccumulator.php [deleted file]
library/HTMLPurifier/Injector.php [deleted file]
library/HTMLPurifier/Injector/AutoParagraph.php [deleted file]
library/HTMLPurifier/Injector/DisplayLinkURI.php [deleted file]
library/HTMLPurifier/Injector/Linkify.php [deleted file]
library/HTMLPurifier/Injector/PurifierLinkify.php [deleted file]
library/HTMLPurifier/Injector/RemoveEmpty.php [deleted file]
library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php [deleted file]
library/HTMLPurifier/Injector/SafeObject.php [deleted file]
library/HTMLPurifier/Language.php [deleted file]
library/HTMLPurifier/Language/classes/en-x-test.php [deleted file]
library/HTMLPurifier/Language/messages/en-x-test.php [deleted file]
library/HTMLPurifier/Language/messages/en-x-testmini.php [deleted file]
library/HTMLPurifier/Language/messages/en.php [deleted file]
library/HTMLPurifier/LanguageFactory.php [deleted file]
library/HTMLPurifier/Length.php [deleted file]
library/HTMLPurifier/Lexer.php [deleted file]
library/HTMLPurifier/Lexer/DOMLex.php [deleted file]
library/HTMLPurifier/Lexer/DirectLex.php [deleted file]
library/HTMLPurifier/Lexer/PEARSax3.php [deleted file]
library/HTMLPurifier/Lexer/PH5P.php [deleted file]
library/HTMLPurifier/PercentEncoder.php [deleted file]
library/HTMLPurifier/Printer.php [deleted file]
library/HTMLPurifier/Printer/CSSDefinition.php [deleted file]
library/HTMLPurifier/Printer/ConfigForm.css [deleted file]
library/HTMLPurifier/Printer/ConfigForm.js [deleted file]
library/HTMLPurifier/Printer/ConfigForm.php [deleted file]
library/HTMLPurifier/Printer/HTMLDefinition.php [deleted file]
library/HTMLPurifier/PropertyList.php [deleted file]
library/HTMLPurifier/PropertyListIterator.php [deleted file]
library/HTMLPurifier/Strategy.php [deleted file]
library/HTMLPurifier/Strategy/Composite.php [deleted file]
library/HTMLPurifier/Strategy/Core.php [deleted file]
library/HTMLPurifier/Strategy/FixNesting.php [deleted file]
library/HTMLPurifier/Strategy/MakeWellFormed.php [deleted file]
library/HTMLPurifier/Strategy/RemoveForeignElements.php [deleted file]
library/HTMLPurifier/Strategy/ValidateAttributes.php [deleted file]
library/HTMLPurifier/StringHash.php [deleted file]
library/HTMLPurifier/StringHashParser.php [deleted file]
library/HTMLPurifier/TagTransform.php [deleted file]
library/HTMLPurifier/TagTransform/Font.php [deleted file]
library/HTMLPurifier/TagTransform/Simple.php [deleted file]
library/HTMLPurifier/Token.php [deleted file]
library/HTMLPurifier/Token/Comment.php [deleted file]
library/HTMLPurifier/Token/Empty.php [deleted file]
library/HTMLPurifier/Token/End.php [deleted file]
library/HTMLPurifier/Token/Start.php [deleted file]
library/HTMLPurifier/Token/Tag.php [deleted file]
library/HTMLPurifier/Token/Text.php [deleted file]
library/HTMLPurifier/TokenFactory.php [deleted file]
library/HTMLPurifier/URI.php [deleted file]
library/HTMLPurifier/URIDefinition.php [deleted file]
library/HTMLPurifier/URIFilter.php [deleted file]
library/HTMLPurifier/URIFilter/DisableExternal.php [deleted file]
library/HTMLPurifier/URIFilter/DisableExternalResources.php [deleted file]
library/HTMLPurifier/URIFilter/HostBlacklist.php [deleted file]
library/HTMLPurifier/URIFilter/MakeAbsolute.php [deleted file]
library/HTMLPurifier/URIFilter/Munge.php [deleted file]
library/HTMLPurifier/URIParser.php [deleted file]
library/HTMLPurifier/URIScheme.php [deleted file]
library/HTMLPurifier/URIScheme/data.php [deleted file]
library/HTMLPurifier/URIScheme/ftp.php [deleted file]
library/HTMLPurifier/URIScheme/http.php [deleted file]
library/HTMLPurifier/URIScheme/https.php [deleted file]
library/HTMLPurifier/URIScheme/mailto.php [deleted file]
library/HTMLPurifier/URIScheme/news.php [deleted file]
library/HTMLPurifier/URIScheme/nntp.php [deleted file]
library/HTMLPurifier/URISchemeRegistry.php [deleted file]
library/HTMLPurifier/UnitConverter.php [deleted file]
library/HTMLPurifier/VarParser.php [deleted file]
library/HTMLPurifier/VarParser/Flexible.php [deleted file]
library/HTMLPurifier/VarParser/Native.php [deleted file]
library/HTMLPurifier/VarParserException.php [deleted file]
library/ezyang/htmlpurifier/CREDITS [new file with mode: 0644]
library/ezyang/htmlpurifier/INSTALL [new file with mode: 0644]
library/ezyang/htmlpurifier/INSTALL.fr.utf8 [new file with mode: 0644]
library/ezyang/htmlpurifier/LICENSE [new file with mode: 0644]
library/ezyang/htmlpurifier/NEWS [new file with mode: 0644]
library/ezyang/htmlpurifier/README [new file with mode: 0644]
library/ezyang/htmlpurifier/TODO [new file with mode: 0644]
library/ezyang/htmlpurifier/VERSION [new file with mode: 0644]
library/ezyang/htmlpurifier/WHATSNEW [new file with mode: 0644]
library/ezyang/htmlpurifier/WYSIWYG [new file with mode: 0644]
library/ezyang/htmlpurifier/composer.json [new file with mode: 0644]
library/ezyang/htmlpurifier/extras/ConfigDoc/HTMLXSLTProcessor.php [new file with mode: 0644]
library/ezyang/htmlpurifier/extras/FSTools.php [new file with mode: 0644]
library/ezyang/htmlpurifier/extras/FSTools/File.php [new file with mode: 0644]
library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.auto.php [new file with mode: 0644]
library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload.php [new file with mode: 0644]
library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.php [new file with mode: 0644]
library/ezyang/htmlpurifier/extras/README [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier.auto.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier.autoload.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier.composer.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier.func.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier.includes.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier.kses.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier.path.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Background.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Length.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeObject.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeParam.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Config.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/info.ini [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Context.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Null.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Filter.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Strict.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Language.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-test.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-testmini.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Length.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DirectLex.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Node.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Comment.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Composite.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Font.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Token.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Tag.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URI.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternal.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/mailto.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php [new file with mode: 0644]
library/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php [new file with mode: 0644]
library/ezyang/htmlpurifier/package.php [new file with mode: 0644]
library/ezyang/htmlpurifier/phpdoc.ini [new file with mode: 0644]
library/ezyang/htmlpurifier/plugins/modx.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/plugins/phorum/.gitignore [new file with mode: 0644]
library/ezyang/htmlpurifier/plugins/phorum/Changelog [new file with mode: 0644]
library/ezyang/htmlpurifier/plugins/phorum/INSTALL [new file with mode: 0644]
library/ezyang/htmlpurifier/plugins/phorum/README [new file with mode: 0644]
library/ezyang/htmlpurifier/plugins/phorum/config.default.php [new file with mode: 0644]
library/ezyang/htmlpurifier/plugins/phorum/htmlpurifier.php [new file with mode: 0644]
library/ezyang/htmlpurifier/plugins/phorum/info.txt [new file with mode: 0644]
library/ezyang/htmlpurifier/plugins/phorum/init-config.php [new file with mode: 0644]
library/ezyang/htmlpurifier/plugins/phorum/migrate.bbcode.php [new file with mode: 0644]
library/ezyang/htmlpurifier/plugins/phorum/settings.php [new file with mode: 0644]
library/ezyang/htmlpurifier/plugins/phorum/settings/form.php [new file with mode: 0644]
library/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs-form.php [new file with mode: 0644]
library/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs.php [new file with mode: 0644]
library/ezyang/htmlpurifier/plugins/phorum/settings/save.php [new file with mode: 0644]
library/ezyang/htmlpurifier/release1-update.php [new file with mode: 0644]
library/ezyang/htmlpurifier/release2-tag.php [new file with mode: 0644]
library/ezyang/htmlpurifier/test-settings.sample.php [new file with mode: 0644]
library/perfect-scrollbar/README.md [new file with mode: 0644]
library/perfect-scrollbar/perfect-scrollbar.css [new file with mode: 0644]
library/perfect-scrollbar/perfect-scrollbar.jquery.js [new file with mode: 0644]
library/perfect-scrollbar/perfect-scrollbar.min.css [new file with mode: 0644]
mod/admin.php
mod/community.php
mod/contacts.php
mod/crepair.php
mod/dfrn_confirm.php
mod/dfrn_notify.php
mod/dfrn_poll.php
mod/dfrn_request.php
mod/dirfind.php
mod/display.php
mod/follow.php
mod/friendica.php
mod/help.php
mod/item.php
mod/network.php
mod/newmember.php
mod/notify.php
mod/parse_url.php
mod/ping.php
mod/poco.php
mod/profiles.php
mod/proxy.php
mod/pubsub.php
mod/settings.php
mod/wall_upload.php
update.php
util/messages.po
view/de/messages.po
view/de/strings.php
view/default.php
view/fonts/shashape.css
view/global.css
view/it/messages.po
view/it/strings.php
view/nl/messages.po
view/nl/strings.php
view/templates/acl_selector.tpl
view/templates/admin_aside.tpl
view/templates/admin_federation.tpl [new file with mode: 0644]
view/templates/admin_logs.tpl
view/templates/admin_plugins.tpl
view/templates/admin_remoteupdate.tpl [deleted file]
view/templates/admin_settings_features.tpl [new file with mode: 0644]
view/templates/admin_settings_head.tpl [new file with mode: 0644]
view/templates/admin_viewlogs.tpl [new file with mode: 0644]
view/templates/atom_feed.tpl [deleted file]
view/templates/atom_feed_dfrn.tpl [deleted file]
view/templates/atom_mail.tpl [deleted file]
view/templates/atom_relocate.tpl [deleted file]
view/templates/atom_suggest.tpl [deleted file]
view/templates/events.tpl
view/templates/events_reminder.tpl
view/templates/head.tpl
view/templates/jot-header.tpl
view/templates/notifications.tpl
view/templates/profile_advanced.tpl
view/theme/frost-mobile/default.php
view/theme/frost/default.php
view/theme/smoothly/default.php
view/theme/vier/dark.css
view/theme/vier/mobile.css
view/theme/vier/shadow.css
view/theme/vier/style.css
view/theme/vier/style.php
view/theme/vier/templates/profile_advanced.tpl
view/theme/vier/theme.php

index f434063614b224d9451d91daf3eedb6b8aba7255..728a56ab305b07862615813fc9748920553b28b5 100644 (file)
@@ -4,42 +4,32 @@ Friendica translations
 Translation Process
 -------------------
 
-The strings used in the UI of Friendica is translated at [Transifex] [1] and then
-included in the git repository at github. If you want to help with translation
-for any language, be it correcting terms or translating friendica to a
-currently not supported language, please register an account at transifex.com
-and contact the friendica translation team there.
-
-Translating friendica is simple. Just use the online tool at transifex. If you
-don't want to deal with git & co. that is fine, we check the status of the
-translations regularly and import them into the source tree at github so that
-others can use them.
-
-We do not include every translation from transifex in the source tree to avoid
-a scattered and disturbed overall experience. As an uneducated guess we have a
-lower limit of 50% translated strings before we include the language (for the
-core message.po file, addon translation will be included once all strings of
-an addon are translated. This limit is judging only by the amount of translated
-strings under the assumption that the most prominent strings for the UI will be
-translated first by a translation team. If you feel your translation useable
-before this limit, please contact us and we will probably include your teams
-work in the source tree.
-
-If you want to get your work into the source tree yourself, feel free to do so
-and contact us with and question that arises. The process is simple and
-friendica ships with all the tools necessary.
+The strings used in the UI of Friendica is translated at [Transifex] [1] and then included in the git repository at github.
+If you want to help with translation for any language, be it correcting terms or translating friendica to a currently not supported language, please register an account at transifex.com and contact the friendica translation team there.
+
+Translating friendica is simple.
+Just use the online tool at transifex.
+If you don't want to deal with git & co. that is fine, we check the status of the translations regularly and import them into the source tree at github so that others can use them.
+
+We do not include every translation from transifex in the source tree to avoid a scattered and disturbed overall experience.
+As an uneducated guess we have a lower limit of 50% translated strings before we include the language (for the core messages.po file, addont translation will be included once all strings of an addon are translated.
+This limit is judging only by the amount of translated strings under the assumption that the most prominent strings for the UI will be translated first by a translation team.
+If you feel your translation useable before this limit, please contact us and we will probably include your teams work in the source tree.
+
+If you want to help translating, please concentrate on the core messages.po file first.
+We will only include translations with a sufficient translated messages.po file.
+Translations of addons will only be included, when the core file is included as well.
+
+If you want to get your work into the source tree yourself, feel free to do so and contact us with and question that arises.
+The process is simple and friendica ships with all the tools necessary.
 
 The location of the translated files in the source tree is
     /view/LNG-CODE/
 where LNG-CODE is the language code used, e.g. de for German or fr for French.
-For the email templates (the *.tpl files) just place them into the directory
-and you are done. The translated strings come as a "message.po" file from
-transifex which needs to be translated into the PHP file friendica uses.  To do
-so, place the file in the directory mentioned above and use the "po2php"
-utility from the util directory of your friendica installation.
+The translated strings come as a "message.po" file from transifex which needs to be translated into the PHP file friendica uses.
+To do so, place the file in the directory mentioned above and use the "po2php" utility from the util directory of your friendica installation.
 
-Assuming you want to convert the German localization which is placed in
-view/de/message.po you would do the following.
+Assuming you want to convert the German localization which is placed in view/de/message.po you would do the following.
 
     1. Navigate at the command prompt to the base directory of your
        friendica installation
@@ -51,11 +41,11 @@ view/de/message.po you would do the following.
 
        The output of the script will be placed at view/de/strings.php where
        friendica is expecting it, so you can test your translation immediately.
-                                  
+
     3. Visit your friendica page to check if it still works in the language you
        just translated. If not try to find the error, most likely PHP will give
        you a hint in the log/warnings.about the error.
-                                        
+
        For debugging you can also try to "run" the file with PHP. This should
        not give any output if the file is ok but might give a hint for
        searching the bug in the file.
@@ -69,30 +59,10 @@ view/de/message.po you would do the following.
 Utilities
 ---------
 
-Additional to the po2php script there are some more utilities for translation
-in the "util" directory of the friendica source tree.  If you only want to
-translate friendica into another language you won't need any of these tools most
-likely but it gives you an idea how the translation process of friendica
-works.
+Additional to the po2php script there are some more utilities for translation in the "util" directory of the friendica source tree.
+If you only want to translate friendica into another language you wont need any of these tools most likely but it gives you an idea how the translation process of friendica works.
 
 For further information see the utils/README file.
 
-Known Problems
---------------
-
-Friendica uses the language setting of the visitors browser to determain the
-language for the UI. Most of the time this works, but there are some known
-quirks.
-
-One is that some browsers, like Safari, do the setting to "de-de" but friendica
-only has a "de" localisation.  A workaround would be to add a symbolic link
-from
-    $friendica/view/de-de
-pointing to
-    $friendica/view/de
-
-Links
------
-
 [1]:   https://www.transifex.com/projects/p/friendica/
 
index 497a97b0eca04a7c0062ef75e07c1cc934cce0ab..dd70040c64d1fa6247f7cca95f5ce8f56ffa3511 100644 (file)
--- a/boot.php
+++ b/boot.php
@@ -17,6 +17,8 @@
  * easily as email does today.
  */
 
+require_once('include/autoloader.php');
 require_once('include/config.php');
 require_once('include/network.php');
 require_once('include/plugin.php');
@@ -36,7 +38,7 @@ define ( 'FRIENDICA_PLATFORM',     'Friendica');
 define ( 'FRIENDICA_CODENAME',     'Asparagus');
 define ( 'FRIENDICA_VERSION',      '3.5-dev' );
 define ( 'DFRN_PROTOCOL_VERSION',  '2.23'    );
-define ( 'DB_UPDATE_VERSION',      1191      );
+define ( 'DB_UPDATE_VERSION',      1194      );
 
 /**
  * @brief Constant with a HTML line break.
@@ -467,6 +469,7 @@ class App {
        public  $is_tablet;
        public  $is_friendica_app;
        public  $performance = array();
+       public  $callstack = array();
 
        public $nav_sel;
 
@@ -529,6 +532,8 @@ class App {
        private $cached_profile_image;
        private $cached_profile_picdate;
 
+       private static $a;
+
        /**
         * @brief App constructor.
         */
@@ -554,6 +559,12 @@ class App {
                $this->performance["marktime"] = 0;
                $this->performance["markstart"] = microtime(true);
 
+               $this->callstack["database"] = array();
+               $this->callstack["network"] = array();
+               $this->callstack["file"] = array();
+               $this->callstack["rendering"] = array();
+               $this->callstack["parser"] = array();
+
                $this->config = array();
                $this->page = array();
                $this->pager= array();
@@ -703,6 +714,8 @@ class App {
                        }
                }
 
+               self::$a = $this;
+
        }
 
        function get_basepath() {
@@ -727,6 +740,10 @@ class App {
 
        function get_baseurl($ssl = false) {
 
+               // Is the function called statically?
+               if (!is_object($this))
+                       return(self::$a->get_baseurl($ssl));
+
                $scheme = $this->scheme;
 
                if((x($this->config,'system')) && (x($this->config['system'],'ssl_policy'))) {
@@ -903,6 +920,10 @@ class App {
        }
 
        function get_cached_avatar_image($avatar_image){
+               return $avatar_image;
+
+               // The following code is deactivated. It doesn't seem to make any sense and it slows down the system.
+               /*
                if($this->cached_profile_image[$avatar_image])
                        return $this->cached_profile_image[$avatar_image];
 
@@ -922,6 +943,7 @@ class App {
                        }
                }
                return $this->cached_profile_image[$avatar_image];
+               */
        }
 
 
@@ -1016,6 +1038,20 @@ class App {
 
                $this->performance[$value] += (float)$duration;
                $this->performance["marktime"] += (float)$duration;
+
+               // Trace the different functions with their timestamps
+               $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5);
+
+               array_shift($trace);
+
+               $function = array();
+               foreach ($trace AS $func)
+                       $function[] = $func["function"];
+
+               $function = implode(", ", $function);
+
+               $this->callstack[$value][$function] += (float)$duration;
+
        }
 
        function mark_timestamp($mark) {
index e3768c1efb67432fed9159ef1bfaee5a3b5176dc..70b315ea244f0be305b8647be65e3e1f4fee2c73 100644 (file)
@@ -1,6 +1,6 @@
 -- ------------------------------------------
--- Friendica 3.4.2 (Lily of the valley)
--- DB_UPDATE_VERSION 1190
+-- Friendica 3.5-dev (Asparagus)
+-- DB_UPDATE_VERSION 1193
 -- ------------------------------------------
 
 
@@ -8,20 +8,21 @@
 -- TABLE addon
 --
 CREATE TABLE IF NOT EXISTS `addon` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `name` varchar(255) NOT NULL DEFAULT '',
        `version` varchar(255) NOT NULL DEFAULT '',
        `installed` tinyint(1) NOT NULL DEFAULT 0,
        `hidden` tinyint(1) NOT NULL DEFAULT 0,
        `timestamp` bigint(20) NOT NULL DEFAULT 0,
-       `plugin_admin` tinyint(1) NOT NULL DEFAULT 0
+       `plugin_admin` tinyint(1) NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`)
 ) DEFAULT CHARSET=utf8;
 
 --
 -- TABLE attach
 --
 CREATE TABLE IF NOT EXISTS `attach` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `uid` int(11) NOT NULL DEFAULT 0,
        `hash` varchar(64) NOT NULL DEFAULT '',
        `filename` varchar(255) NOT NULL DEFAULT '',
@@ -33,28 +34,31 @@ CREATE TABLE IF NOT EXISTS `attach` (
        `allow_cid` mediumtext NOT NULL,
        `allow_gid` mediumtext NOT NULL,
        `deny_cid` mediumtext NOT NULL,
-       `deny_gid` mediumtext NOT NULL
+       `deny_gid` mediumtext NOT NULL,
+        PRIMARY KEY(`id`)
 ) DEFAULT CHARSET=utf8;
 
 --
 -- TABLE auth_codes
 --
 CREATE TABLE IF NOT EXISTS `auth_codes` (
-       `id` varchar(40) NOT NULL PRIMARY KEY,
+       `id` varchar(40) NOT NULL,
        `client_id` varchar(20) NOT NULL DEFAULT '',
        `redirect_uri` varchar(200) NOT NULL DEFAULT '',
        `expires` int(11) NOT NULL DEFAULT 0,
-       `scope` varchar(250) NOT NULL DEFAULT ''
+       `scope` varchar(250) NOT NULL DEFAULT '',
+        PRIMARY KEY(`id`)
 ) DEFAULT CHARSET=utf8;
 
 --
 -- TABLE cache
 --
 CREATE TABLE IF NOT EXISTS `cache` (
-       `k` varchar(255) NOT NULL PRIMARY KEY,
+       `k` varchar(255) NOT NULL,
        `v` text NOT NULL,
        `expire_mode` int(11) NOT NULL DEFAULT 0,
        `updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+        PRIMARY KEY(`k`),
         INDEX `updated` (`updated`)
 ) DEFAULT CHARSET=utf8;
 
@@ -62,34 +66,37 @@ CREATE TABLE IF NOT EXISTS `cache` (
 -- TABLE challenge
 --
 CREATE TABLE IF NOT EXISTS `challenge` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `challenge` varchar(255) NOT NULL DEFAULT '',
        `dfrn-id` varchar(255) NOT NULL DEFAULT '',
        `expire` int(11) NOT NULL DEFAULT 0,
        `type` varchar(255) NOT NULL DEFAULT '',
-       `last_update` varchar(255) NOT NULL DEFAULT ''
+       `last_update` varchar(255) NOT NULL DEFAULT '',
+        PRIMARY KEY(`id`)
 ) DEFAULT CHARSET=utf8;
 
 --
 -- TABLE clients
 --
 CREATE TABLE IF NOT EXISTS `clients` (
-       `client_id` varchar(20) NOT NULL PRIMARY KEY,
+       `client_id` varchar(20) NOT NULL,
        `pw` varchar(20) NOT NULL DEFAULT '',
        `redirect_uri` varchar(200) NOT NULL DEFAULT '',
        `name` text,
        `icon` text,
-       `uid` int(11) NOT NULL DEFAULT 0
+       `uid` int(11) NOT NULL DEFAULT 0,
+        PRIMARY KEY(`client_id`)
 ) DEFAULT CHARSET=utf8;
 
 --
 -- TABLE config
 --
 CREATE TABLE IF NOT EXISTS `config` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `cat` varchar(255) NOT NULL DEFAULT '',
        `k` varchar(255) NOT NULL DEFAULT '',
        `v` text NOT NULL,
+        PRIMARY KEY(`id`),
         INDEX `cat_k` (`cat`(30),`k`(30))
 ) DEFAULT CHARSET=utf8;
 
@@ -97,7 +104,7 @@ CREATE TABLE IF NOT EXISTS `config` (
 -- TABLE contact
 --
 CREATE TABLE IF NOT EXISTS `contact` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `uid` int(11) NOT NULL DEFAULT 0,
        `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
        `self` tinyint(1) NOT NULL DEFAULT 0,
@@ -162,6 +169,7 @@ CREATE TABLE IF NOT EXISTS `contact` (
        `notify_new_posts` tinyint(1) NOT NULL DEFAULT 0,
        `fetch_further_information` tinyint(1) NOT NULL DEFAULT 0,
        `ffi_keyword_blacklist` mediumtext NOT NULL,
+        PRIMARY KEY(`id`),
         INDEX `uid` (`uid`)
 ) DEFAULT CHARSET=utf8;
 
@@ -169,7 +177,7 @@ CREATE TABLE IF NOT EXISTS `contact` (
 -- TABLE conv
 --
 CREATE TABLE IF NOT EXISTS `conv` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `guid` varchar(64) NOT NULL DEFAULT '',
        `recips` mediumtext NOT NULL,
        `uid` int(11) NOT NULL DEFAULT 0,
@@ -177,6 +185,7 @@ CREATE TABLE IF NOT EXISTS `conv` (
        `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
        `updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
        `subject` mediumtext NOT NULL,
+        PRIMARY KEY(`id`),
         INDEX `uid` (`uid`)
 ) DEFAULT CHARSET=utf8;
 
@@ -184,27 +193,29 @@ CREATE TABLE IF NOT EXISTS `conv` (
 -- TABLE deliverq
 --
 CREATE TABLE IF NOT EXISTS `deliverq` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `cmd` varchar(32) NOT NULL DEFAULT '',
        `item` int(11) NOT NULL DEFAULT 0,
-       `contact` int(11) NOT NULL DEFAULT 0
+       `contact` int(11) NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`)
 ) DEFAULT CHARSET=utf8;
 
 --
 -- TABLE dsprphotoq
 --
 CREATE TABLE IF NOT EXISTS `dsprphotoq` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `uid` int(11) NOT NULL DEFAULT 0,
        `msg` mediumtext NOT NULL,
-       `attempt` tinyint(4) NOT NULL DEFAULT 0
+       `attempt` tinyint(4) NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`)
 ) DEFAULT CHARSET=utf8;
 
 --
 -- TABLE event
 --
 CREATE TABLE IF NOT EXISTS `event` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `uid` int(11) NOT NULL DEFAULT 0,
        `cid` int(11) NOT NULL DEFAULT 0,
        `uri` varchar(255) NOT NULL DEFAULT '',
@@ -223,6 +234,7 @@ CREATE TABLE IF NOT EXISTS `event` (
        `allow_gid` mediumtext NOT NULL,
        `deny_cid` mediumtext NOT NULL,
        `deny_gid` mediumtext NOT NULL,
+        PRIMARY KEY(`id`),
         INDEX `uid` (`uid`)
 ) DEFAULT CHARSET=utf8;
 
@@ -230,7 +242,7 @@ CREATE TABLE IF NOT EXISTS `event` (
 -- TABLE fcontact
 --
 CREATE TABLE IF NOT EXISTS `fcontact` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `url` varchar(255) NOT NULL DEFAULT '',
        `name` varchar(255) NOT NULL DEFAULT '',
        `photo` varchar(255) NOT NULL DEFAULT '',
@@ -246,6 +258,7 @@ CREATE TABLE IF NOT EXISTS `fcontact` (
        `alias` varchar(255) NOT NULL DEFAULT '',
        `pubkey` text NOT NULL,
        `updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+        PRIMARY KEY(`id`),
         INDEX `addr` (`addr`)
 ) DEFAULT CHARSET=utf8;
 
@@ -253,20 +266,22 @@ CREATE TABLE IF NOT EXISTS `fcontact` (
 -- TABLE ffinder
 --
 CREATE TABLE IF NOT EXISTS `ffinder` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `uid` int(10) unsigned NOT NULL DEFAULT 0,
        `cid` int(10) unsigned NOT NULL DEFAULT 0,
-       `fid` int(10) unsigned NOT NULL DEFAULT 0
+       `fid` int(10) unsigned NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`)
 ) DEFAULT CHARSET=utf8;
 
 --
 -- TABLE fserver
 --
 CREATE TABLE IF NOT EXISTS `fserver` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `server` varchar(255) NOT NULL DEFAULT '',
        `posturl` varchar(255) NOT NULL DEFAULT '',
        `key` text NOT NULL,
+        PRIMARY KEY(`id`),
         INDEX `server` (`server`)
 ) DEFAULT CHARSET=utf8;
 
@@ -274,7 +289,7 @@ CREATE TABLE IF NOT EXISTS `fserver` (
 -- TABLE fsuggest
 --
 CREATE TABLE IF NOT EXISTS `fsuggest` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `uid` int(11) NOT NULL DEFAULT 0,
        `cid` int(11) NOT NULL DEFAULT 0,
        `name` varchar(255) NOT NULL DEFAULT '',
@@ -282,16 +297,18 @@ CREATE TABLE IF NOT EXISTS `fsuggest` (
        `request` varchar(255) NOT NULL DEFAULT '',
        `photo` varchar(255) NOT NULL DEFAULT '',
        `note` text NOT NULL,
-       `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00'
+       `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+        PRIMARY KEY(`id`)
 ) DEFAULT CHARSET=utf8;
 
 --
 -- TABLE gcign
 --
 CREATE TABLE IF NOT EXISTS `gcign` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `uid` int(11) NOT NULL DEFAULT 0,
        `gcid` int(11) NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`),
         INDEX `uid` (`uid`),
         INDEX `gcid` (`gcid`)
 ) DEFAULT CHARSET=utf8;
@@ -300,7 +317,7 @@ CREATE TABLE IF NOT EXISTS `gcign` (
 -- TABLE gcontact
 --
 CREATE TABLE IF NOT EXISTS `gcontact` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `name` varchar(255) NOT NULL DEFAULT '',
        `nick` varchar(255) NOT NULL DEFAULT '',
        `url` varchar(255) NOT NULL DEFAULT '',
@@ -315,11 +332,17 @@ CREATE TABLE IF NOT EXISTS `gcontact` (
        `about` text NOT NULL,
        `keywords` text NOT NULL,
        `gender` varchar(32) NOT NULL DEFAULT '',
+       `birthday` varchar(32) NOT NULL DEFAULT '0000-00-00',
        `community` tinyint(1) NOT NULL DEFAULT 0,
+       `hide` tinyint(1) NOT NULL DEFAULT 0,
+       `nsfw` tinyint(1) NOT NULL DEFAULT 0,
        `network` varchar(255) NOT NULL DEFAULT '',
        `addr` varchar(255) NOT NULL DEFAULT '',
+       `notify` text NOT NULL,
+       `alias` varchar(255) NOT NULL DEFAULT '',
        `generation` tinyint(3) NOT NULL DEFAULT 0,
        `server_url` varchar(255) NOT NULL DEFAULT '',
+        PRIMARY KEY(`id`),
         INDEX `nurl` (`nurl`),
         INDEX `updated` (`updated`)
 ) DEFAULT CHARSET=utf8;
@@ -328,12 +351,13 @@ CREATE TABLE IF NOT EXISTS `gcontact` (
 -- TABLE glink
 --
 CREATE TABLE IF NOT EXISTS `glink` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `cid` int(11) NOT NULL DEFAULT 0,
        `uid` int(11) NOT NULL DEFAULT 0,
        `gcid` int(11) NOT NULL DEFAULT 0,
        `zcid` int(11) NOT NULL DEFAULT 0,
        `updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+        PRIMARY KEY(`id`),
         INDEX `cid_uid_gcid_zcid` (`cid`,`uid`,`gcid`,`zcid`),
         INDEX `gcid` (`gcid`),
         INDEX `zcid` (`zcid`)
@@ -343,11 +367,12 @@ CREATE TABLE IF NOT EXISTS `glink` (
 -- TABLE group
 --
 CREATE TABLE IF NOT EXISTS `group` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `uid` int(10) unsigned NOT NULL DEFAULT 0,
        `visible` tinyint(1) NOT NULL DEFAULT 0,
        `deleted` tinyint(1) NOT NULL DEFAULT 0,
        `name` varchar(255) NOT NULL DEFAULT '',
+        PRIMARY KEY(`id`),
         INDEX `uid` (`uid`)
 ) DEFAULT CHARSET=utf8;
 
@@ -355,10 +380,11 @@ CREATE TABLE IF NOT EXISTS `group` (
 -- TABLE group_member
 --
 CREATE TABLE IF NOT EXISTS `group_member` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `uid` int(10) unsigned NOT NULL DEFAULT 0,
        `gid` int(10) unsigned NOT NULL DEFAULT 0,
        `contact-id` int(10) unsigned NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`),
         INDEX `uid_gid_contactid` (`uid`,`gid`,`contact-id`)
 ) DEFAULT CHARSET=utf8;
 
@@ -366,7 +392,7 @@ CREATE TABLE IF NOT EXISTS `group_member` (
 -- TABLE gserver
 --
 CREATE TABLE IF NOT EXISTS `gserver` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `url` varchar(255) NOT NULL DEFAULT '',
        `nurl` varchar(255) NOT NULL DEFAULT '',
        `version` varchar(255) NOT NULL DEFAULT '',
@@ -381,6 +407,7 @@ CREATE TABLE IF NOT EXISTS `gserver` (
        `last_poco_query` datetime DEFAULT '0000-00-00 00:00:00',
        `last_contact` datetime DEFAULT '0000-00-00 00:00:00',
        `last_failure` datetime DEFAULT '0000-00-00 00:00:00',
+        PRIMARY KEY(`id`),
         INDEX `nurl` (`nurl`)
 ) DEFAULT CHARSET=utf8;
 
@@ -388,11 +415,12 @@ CREATE TABLE IF NOT EXISTS `gserver` (
 -- TABLE guid
 --
 CREATE TABLE IF NOT EXISTS `guid` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `guid` varchar(255) NOT NULL DEFAULT '',
        `plink` varchar(255) NOT NULL DEFAULT '',
        `uri` varchar(255) NOT NULL DEFAULT '',
        `network` varchar(32) NOT NULL DEFAULT '',
+        PRIMARY KEY(`id`),
         INDEX `guid` (`guid`),
         INDEX `plink` (`plink`),
         INDEX `uri` (`uri`)
@@ -402,11 +430,12 @@ CREATE TABLE IF NOT EXISTS `guid` (
 -- TABLE hook
 --
 CREATE TABLE IF NOT EXISTS `hook` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `hook` varchar(255) NOT NULL DEFAULT '',
        `file` varchar(255) NOT NULL DEFAULT '',
        `function` varchar(255) NOT NULL DEFAULT '',
        `priority` int(11) unsigned NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`),
         INDEX `hook_file_function` (`hook`(30),`file`(60),`function`(30))
 ) DEFAULT CHARSET=utf8;
 
@@ -414,7 +443,7 @@ CREATE TABLE IF NOT EXISTS `hook` (
 -- TABLE intro
 --
 CREATE TABLE IF NOT EXISTS `intro` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `uid` int(10) unsigned NOT NULL DEFAULT 0,
        `fid` int(11) NOT NULL DEFAULT 0,
        `contact-id` int(11) NOT NULL DEFAULT 0,
@@ -424,18 +453,20 @@ CREATE TABLE IF NOT EXISTS `intro` (
        `hash` varchar(255) NOT NULL DEFAULT '',
        `datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
        `blocked` tinyint(1) NOT NULL DEFAULT 1,
-       `ignore` tinyint(1) NOT NULL DEFAULT 0
+       `ignore` tinyint(1) NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`)
 ) DEFAULT CHARSET=utf8;
 
 --
 -- TABLE item
 --
 CREATE TABLE IF NOT EXISTS `item` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `guid` varchar(255) NOT NULL DEFAULT '',
        `uri` varchar(255) NOT NULL DEFAULT '',
        `uid` int(10) unsigned NOT NULL DEFAULT 0,
        `contact-id` int(11) NOT NULL DEFAULT 0,
+       `gcontact-id` int(11) unsigned NOT NULL DEFAULT 0,
        `type` varchar(255) NOT NULL DEFAULT '',
        `wall` tinyint(1) NOT NULL DEFAULT 0,
        `gravity` tinyint(1) NOT NULL DEFAULT 0,
@@ -493,6 +524,7 @@ CREATE TABLE IF NOT EXISTS `item` (
        `rendered-hash` varchar(32) NOT NULL DEFAULT '',
        `rendered-html` mediumtext NOT NULL,
        `global` tinyint(1) NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`),
         INDEX `guid` (`guid`),
         INDEX `uri` (`uri`),
         INDEX `parent` (`parent`),
@@ -509,6 +541,7 @@ CREATE TABLE IF NOT EXISTS `item` (
         INDEX `uid_thrparent` (`uid`,`thr-parent`),
         INDEX `uid_parenturi` (`uid`,`parent-uri`),
         INDEX `uid_contactid_created` (`uid`,`contact-id`,`created`),
+        INDEX `gcontactid_uid_created` (`gcontact-id`,`uid`,`created`),
         INDEX `wall_body` (`wall`,`body`(6)),
         INDEX `uid_visible_moderated_created` (`uid`,`visible`,`moderated`,`created`),
         INDEX `uid_uri` (`uid`,`uri`),
@@ -531,11 +564,12 @@ CREATE TABLE IF NOT EXISTS `item` (
 -- TABLE item_id
 --
 CREATE TABLE IF NOT EXISTS `item_id` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `iid` int(11) NOT NULL DEFAULT 0,
        `uid` int(11) NOT NULL DEFAULT 0,
        `sid` varchar(255) NOT NULL DEFAULT '',
        `service` varchar(255) NOT NULL DEFAULT '',
+        PRIMARY KEY(`id`),
         INDEX `uid` (`uid`),
         INDEX `sid` (`sid`),
         INDEX `service` (`service`),
@@ -546,17 +580,18 @@ CREATE TABLE IF NOT EXISTS `item_id` (
 -- TABLE locks
 --
 CREATE TABLE IF NOT EXISTS `locks` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `name` varchar(128) NOT NULL DEFAULT '',
        `locked` tinyint(1) NOT NULL DEFAULT 0,
-       `created` datetime DEFAULT '0000-00-00 00:00:00'
+       `created` datetime DEFAULT '0000-00-00 00:00:00',
+        PRIMARY KEY(`id`)
 ) DEFAULT CHARSET=utf8;
 
 --
 -- TABLE mail
 --
 CREATE TABLE IF NOT EXISTS `mail` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `uid` int(10) unsigned NOT NULL DEFAULT 0,
        `guid` varchar(64) NOT NULL DEFAULT '',
        `from-name` varchar(255) NOT NULL DEFAULT '',
@@ -573,6 +608,7 @@ CREATE TABLE IF NOT EXISTS `mail` (
        `uri` varchar(255) NOT NULL DEFAULT '',
        `parent-uri` varchar(255) NOT NULL DEFAULT '',
        `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+        PRIMARY KEY(`id`),
         INDEX `uid` (`uid`),
         INDEX `guid` (`guid`),
         INDEX `convid` (`convid`),
@@ -585,7 +621,7 @@ CREATE TABLE IF NOT EXISTS `mail` (
 -- TABLE mailacct
 --
 CREATE TABLE IF NOT EXISTS `mailacct` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `uid` int(11) NOT NULL DEFAULT 0,
        `server` varchar(255) NOT NULL DEFAULT '',
        `port` int(11) NOT NULL DEFAULT 0,
@@ -597,16 +633,18 @@ CREATE TABLE IF NOT EXISTS `mailacct` (
        `action` int(11) NOT NULL DEFAULT 0,
        `movetofolder` varchar(255) NOT NULL DEFAULT '',
        `pubmail` tinyint(1) NOT NULL DEFAULT 0,
-       `last_check` datetime NOT NULL DEFAULT '0000-00-00 00:00:00'
+       `last_check` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+        PRIMARY KEY(`id`)
 ) DEFAULT CHARSET=utf8;
 
 --
 -- TABLE manage
 --
 CREATE TABLE IF NOT EXISTS `manage` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `uid` int(11) NOT NULL DEFAULT 0,
        `mid` int(11) NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`),
         INDEX `uid_mid` (`uid`,`mid`)
 ) DEFAULT CHARSET=utf8;
 
@@ -614,7 +652,7 @@ CREATE TABLE IF NOT EXISTS `manage` (
 -- TABLE notify
 --
 CREATE TABLE IF NOT EXISTS `notify` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `hash` varchar(64) NOT NULL DEFAULT '',
        `type` int(11) NOT NULL DEFAULT 0,
        `name` varchar(255) NOT NULL DEFAULT '',
@@ -629,6 +667,7 @@ CREATE TABLE IF NOT EXISTS `notify` (
        `seen` tinyint(1) NOT NULL DEFAULT 0,
        `verb` varchar(255) NOT NULL DEFAULT '',
        `otype` varchar(16) NOT NULL DEFAULT '',
+        PRIMARY KEY(`id`),
         INDEX `uid` (`uid`)
 ) DEFAULT CHARSET=utf8;
 
@@ -636,24 +675,50 @@ CREATE TABLE IF NOT EXISTS `notify` (
 -- TABLE notify-threads
 --
 CREATE TABLE IF NOT EXISTS `notify-threads` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `notify-id` int(11) NOT NULL DEFAULT 0,
        `master-parent-item` int(10) unsigned NOT NULL DEFAULT 0,
        `parent-item` int(10) unsigned NOT NULL DEFAULT 0,
        `receiver-uid` int(11) NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`),
         INDEX `master-parent-item` (`master-parent-item`),
         INDEX `receiver-uid` (`receiver-uid`)
 ) DEFAULT CHARSET=utf8;
 
+--
+-- TABLE oembed
+--
+CREATE TABLE IF NOT EXISTS `oembed` (
+       `url` varchar(255) NOT NULL,
+       `content` text NOT NULL,
+       `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+        PRIMARY KEY(`url`),
+        INDEX `created` (`created`)
+) DEFAULT CHARSET=utf8;
+
+--
+-- TABLE parsed_url
+--
+CREATE TABLE IF NOT EXISTS `parsed_url` (
+       `url` varchar(255) NOT NULL,
+       `guessing` tinyint(1) NOT NULL DEFAULT 0,
+       `oembed` tinyint(1) NOT NULL DEFAULT 0,
+       `content` text NOT NULL,
+       `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+        PRIMARY KEY(`url`,`guessing`,`oembed`),
+        INDEX `created` (`created`)
+) DEFAULT CHARSET=utf8;
+
 --
 -- TABLE pconfig
 --
 CREATE TABLE IF NOT EXISTS `pconfig` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `uid` int(11) NOT NULL DEFAULT 0,
        `cat` varchar(255) NOT NULL DEFAULT '',
        `k` varchar(255) NOT NULL DEFAULT '',
        `v` mediumtext NOT NULL,
+        PRIMARY KEY(`id`),
         INDEX `uid_cat_k` (`uid`,`cat`(30),`k`(30))
 ) DEFAULT CHARSET=utf8;
 
@@ -661,7 +726,7 @@ CREATE TABLE IF NOT EXISTS `pconfig` (
 -- TABLE photo
 --
 CREATE TABLE IF NOT EXISTS `photo` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `uid` int(10) unsigned NOT NULL DEFAULT 0,
        `contact-id` int(10) unsigned NOT NULL DEFAULT 0,
        `guid` varchar(64) NOT NULL DEFAULT '',
@@ -683,6 +748,7 @@ CREATE TABLE IF NOT EXISTS `photo` (
        `allow_gid` mediumtext NOT NULL,
        `deny_cid` mediumtext NOT NULL,
        `deny_gid` mediumtext NOT NULL,
+        PRIMARY KEY(`id`),
         INDEX `uid` (`uid`),
         INDEX `resource-id` (`resource-id`),
         INDEX `guid` (`guid`)
@@ -692,7 +758,7 @@ CREATE TABLE IF NOT EXISTS `photo` (
 -- TABLE poll
 --
 CREATE TABLE IF NOT EXISTS `poll` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `uid` int(11) NOT NULL DEFAULT 0,
        `q0` mediumtext NOT NULL,
        `q1` mediumtext NOT NULL,
@@ -704,6 +770,7 @@ CREATE TABLE IF NOT EXISTS `poll` (
        `q7` mediumtext NOT NULL,
        `q8` mediumtext NOT NULL,
        `q9` mediumtext NOT NULL,
+        PRIMARY KEY(`id`),
         INDEX `uid` (`uid`)
 ) DEFAULT CHARSET=utf8;
 
@@ -711,9 +778,10 @@ CREATE TABLE IF NOT EXISTS `poll` (
 -- TABLE poll_result
 --
 CREATE TABLE IF NOT EXISTS `poll_result` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `poll_id` int(11) NOT NULL DEFAULT 0,
        `choice` int(11) NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`),
         INDEX `poll_id` (`poll_id`),
         INDEX `choice` (`choice`)
 ) DEFAULT CHARSET=utf8;
@@ -722,7 +790,7 @@ CREATE TABLE IF NOT EXISTS `poll_result` (
 -- TABLE profile
 --
 CREATE TABLE IF NOT EXISTS `profile` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `uid` int(11) NOT NULL DEFAULT 0,
        `profile-name` varchar(255) NOT NULL DEFAULT '',
        `is-default` tinyint(1) NOT NULL DEFAULT 0,
@@ -763,6 +831,7 @@ CREATE TABLE IF NOT EXISTS `profile` (
        `thumb` varchar(255) NOT NULL DEFAULT '',
        `publish` tinyint(1) NOT NULL DEFAULT 0,
        `net-publish` tinyint(1) NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`),
         INDEX `hometown` (`hometown`)
 ) DEFAULT CHARSET=utf8;
 
@@ -770,39 +839,42 @@ CREATE TABLE IF NOT EXISTS `profile` (
 -- TABLE profile_check
 --
 CREATE TABLE IF NOT EXISTS `profile_check` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `uid` int(10) unsigned NOT NULL DEFAULT 0,
        `cid` int(10) unsigned NOT NULL DEFAULT 0,
        `dfrn_id` varchar(255) NOT NULL DEFAULT '',
        `sec` varchar(255) NOT NULL DEFAULT '',
-       `expire` int(11) NOT NULL DEFAULT 0
+       `expire` int(11) NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`)
 ) DEFAULT CHARSET=utf8;
 
 --
 -- TABLE push_subscriber
 --
 CREATE TABLE IF NOT EXISTS `push_subscriber` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `uid` int(11) NOT NULL DEFAULT 0,
        `callback_url` varchar(255) NOT NULL DEFAULT '',
        `topic` varchar(255) NOT NULL DEFAULT '',
        `nickname` varchar(255) NOT NULL DEFAULT '',
        `push` int(11) NOT NULL DEFAULT 0,
        `last_update` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
-       `secret` varchar(255) NOT NULL DEFAULT ''
+       `secret` varchar(255) NOT NULL DEFAULT '',
+        PRIMARY KEY(`id`)
 ) DEFAULT CHARSET=utf8;
 
 --
 -- TABLE queue
 --
 CREATE TABLE IF NOT EXISTS `queue` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `cid` int(11) NOT NULL DEFAULT 0,
        `network` varchar(32) NOT NULL DEFAULT '',
        `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
        `last` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
        `content` mediumtext NOT NULL,
        `batch` tinyint(1) NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`),
         INDEX `cid` (`cid`),
         INDEX `created` (`created`),
         INDEX `last` (`last`),
@@ -814,21 +886,23 @@ CREATE TABLE IF NOT EXISTS `queue` (
 -- TABLE register
 --
 CREATE TABLE IF NOT EXISTS `register` (
-       `id` int(11) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) unsigned NOT NULL auto_increment,
        `hash` varchar(255) NOT NULL DEFAULT '',
        `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
        `uid` int(11) unsigned NOT NULL DEFAULT 0,
        `password` varchar(255) NOT NULL DEFAULT '',
-       `language` varchar(16) NOT NULL DEFAULT ''
+       `language` varchar(16) NOT NULL DEFAULT '',
+        PRIMARY KEY(`id`)
 ) DEFAULT CHARSET=utf8;
 
 --
 -- TABLE search
 --
 CREATE TABLE IF NOT EXISTS `search` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `uid` int(11) NOT NULL DEFAULT 0,
        `term` varchar(255) NOT NULL DEFAULT '',
+        PRIMARY KEY(`id`),
         INDEX `uid` (`uid`),
         INDEX `term` (`term`)
 ) DEFAULT CHARSET=utf8;
@@ -837,10 +911,11 @@ CREATE TABLE IF NOT EXISTS `search` (
 -- TABLE session
 --
 CREATE TABLE IF NOT EXISTS `session` (
-       `id` bigint(20) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` bigint(20) unsigned NOT NULL auto_increment,
        `sid` varchar(255) NOT NULL DEFAULT '',
        `data` text NOT NULL,
        `expire` int(10) unsigned NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`),
         INDEX `sid` (`sid`),
         INDEX `expire` (`expire`)
 ) DEFAULT CHARSET=utf8;
@@ -849,12 +924,13 @@ CREATE TABLE IF NOT EXISTS `session` (
 -- TABLE sign
 --
 CREATE TABLE IF NOT EXISTS `sign` (
-       `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `id` int(10) unsigned NOT NULL auto_increment,
        `iid` int(10) unsigned NOT NULL DEFAULT 0,
        `retract_iid` int(10) unsigned NOT NULL DEFAULT 0,
        `signed_text` mediumtext NOT NULL,
        `signature` text NOT NULL,
        `signer` varchar(255) NOT NULL DEFAULT '',
+        PRIMARY KEY(`id`),
         INDEX `iid` (`iid`),
         INDEX `retract_iid` (`retract_iid`)
 ) DEFAULT CHARSET=utf8;
@@ -863,12 +939,13 @@ CREATE TABLE IF NOT EXISTS `sign` (
 -- TABLE spam
 --
 CREATE TABLE IF NOT EXISTS `spam` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `uid` int(11) NOT NULL DEFAULT 0,
        `spam` int(11) NOT NULL DEFAULT 0,
        `ham` int(11) NOT NULL DEFAULT 0,
        `term` varchar(255) NOT NULL DEFAULT '',
        `date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+        PRIMARY KEY(`id`),
         INDEX `uid` (`uid`),
         INDEX `spam` (`spam`),
         INDEX `ham` (`ham`),
@@ -879,7 +956,7 @@ CREATE TABLE IF NOT EXISTS `spam` (
 -- TABLE term
 --
 CREATE TABLE IF NOT EXISTS `term` (
-       `tid` int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
+       `tid` int(10) unsigned NOT NULL auto_increment,
        `oid` int(10) unsigned NOT NULL DEFAULT 0,
        `otype` tinyint(3) unsigned NOT NULL DEFAULT 0,
        `type` tinyint(3) unsigned NOT NULL DEFAULT 0,
@@ -891,6 +968,7 @@ CREATE TABLE IF NOT EXISTS `term` (
        `global` tinyint(1) NOT NULL DEFAULT 0,
        `aid` int(10) unsigned NOT NULL DEFAULT 0,
        `uid` int(10) unsigned NOT NULL DEFAULT 0,
+        PRIMARY KEY(`tid`),
         INDEX `oid_otype_type_term` (`oid`,`otype`,`type`,`term`),
         INDEX `uid_term_tid` (`uid`,`term`,`tid`),
         INDEX `type_term` (`type`,`term`),
@@ -903,9 +981,10 @@ CREATE TABLE IF NOT EXISTS `term` (
 -- TABLE thread
 --
 CREATE TABLE IF NOT EXISTS `thread` (
-       `iid` int(10) unsigned NOT NULL DEFAULT 0 PRIMARY KEY,
+       `iid` int(10) unsigned NOT NULL DEFAULT 0,
        `uid` int(10) unsigned NOT NULL DEFAULT 0,
        `contact-id` int(11) unsigned NOT NULL DEFAULT 0,
+       `gcontact-id` int(11) unsigned NOT NULL DEFAULT 0,
        `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
        `edited` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
        `commented` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
@@ -926,12 +1005,15 @@ CREATE TABLE IF NOT EXISTS `thread` (
        `forum_mode` tinyint(1) NOT NULL DEFAULT 0,
        `mention` tinyint(1) NOT NULL DEFAULT 0,
        `network` varchar(32) NOT NULL DEFAULT '',
+        PRIMARY KEY(`iid`),
         INDEX `created` (`created`),
         INDEX `commented` (`commented`),
         INDEX `uid_network_commented` (`uid`,`network`,`commented`),
         INDEX `uid_network_created` (`uid`,`network`,`created`),
         INDEX `uid_contactid_commented` (`uid`,`contact-id`,`commented`),
         INDEX `uid_contactid_created` (`uid`,`contact-id`,`created`),
+        INDEX `uid_gcontactid_commented` (`uid`,`gcontact-id`,`commented`),
+        INDEX `uid_gcontactid_created` (`uid`,`gcontact-id`,`created`),
         INDEX `wall_private_received` (`wall`,`private`,`received`),
         INDEX `uid_created` (`uid`,`created`),
         INDEX `uid_commented` (`uid`,`commented`)
@@ -941,33 +1023,20 @@ CREATE TABLE IF NOT EXISTS `thread` (
 -- TABLE tokens
 --
 CREATE TABLE IF NOT EXISTS `tokens` (
-       `id` varchar(40) NOT NULL PRIMARY KEY,
+       `id` varchar(40) NOT NULL,
        `secret` text NOT NULL,
        `client_id` varchar(20) NOT NULL DEFAULT '',
        `expires` int(11) NOT NULL DEFAULT 0,
        `scope` varchar(200) NOT NULL DEFAULT '',
-       `uid` int(11) NOT NULL DEFAULT 0
-) DEFAULT CHARSET=utf8;
-
---
--- TABLE unique_contacts
---
-CREATE TABLE IF NOT EXISTS `unique_contacts` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
-       `url` varchar(255) NOT NULL DEFAULT '',
-       `nick` varchar(255) NOT NULL DEFAULT '',
-       `name` varchar(255) NOT NULL DEFAULT '',
-       `avatar` varchar(255) NOT NULL DEFAULT '',
-       `location` varchar(255) NOT NULL DEFAULT '',
-       `about` text NOT NULL,
-        INDEX `url` (`url`)
+       `uid` int(11) NOT NULL DEFAULT 0,
+        PRIMARY KEY(`id`)
 ) DEFAULT CHARSET=utf8;
 
 --
 -- TABLE user
 --
 CREATE TABLE IF NOT EXISTS `user` (
-       `uid` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `uid` int(11) NOT NULL auto_increment,
        `guid` varchar(64) NOT NULL DEFAULT '',
        `username` varchar(255) NOT NULL DEFAULT '',
        `password` varchar(255) NOT NULL DEFAULT '',
@@ -1009,6 +1078,7 @@ CREATE TABLE IF NOT EXISTS `user` (
        `deny_cid` mediumtext NOT NULL,
        `deny_gid` mediumtext NOT NULL,
        `openidserver` text NOT NULL,
+        PRIMARY KEY(`uid`),
         INDEX `nickname` (`nickname`)
 ) DEFAULT CHARSET=utf8;
 
@@ -1016,8 +1086,9 @@ CREATE TABLE IF NOT EXISTS `user` (
 -- TABLE userd
 --
 CREATE TABLE IF NOT EXISTS `userd` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `username` varchar(255) NOT NULL,
+        PRIMARY KEY(`id`),
         INDEX `username` (`username`)
 ) DEFAULT CHARSET=utf8;
 
@@ -1025,12 +1096,13 @@ CREATE TABLE IF NOT EXISTS `userd` (
 -- TABLE workerqueue
 --
 CREATE TABLE IF NOT EXISTS `workerqueue` (
-       `id` int(11) NOT NULL auto_increment PRIMARY KEY,
+       `id` int(11) NOT NULL auto_increment,
        `parameter` text NOT NULL,
        `priority` tinyint(3) unsigned NOT NULL DEFAULT 0,
        `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
        `pid` int(11) NOT NULL DEFAULT 0,
        `executed` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+        PRIMARY KEY(`id`),
         INDEX `created` (`created`)
 ) DEFAULT CHARSET=utf8;
 
index 2eac81a7225b65833ce229458115e89e7d6ab783..54e8e3879133e73dbb148ddb5a4c15b1b167acc5 100644 (file)
@@ -53,9 +53,12 @@ Posting to Community forums
 
 If you are a member of a community forum, you may post to the forum by including an @-tag in the post mentioning the forum.
 For example @bicycle would send my post to all members of the group "bicycle" in addition to the normal recipients.
-If your post is private you must also explicitly include the group in the post permissions (to allow the forum "contact" to see the post) **and** mention it in a tag (which redistributes the post to the forum members).  
+If you mention a forum (you are a member of) in a new posting, the posting will be distributed to all members of the forum, regardless of your privacy settings for the posting.
+Also, if the forum is a public forum, your posting will be public for the all internet users.
+If your post is private you must also explicitly include the group in the post permissions (to allow the forum "contact" to see the post) **and** mention it in a tag (which redistributes the post to the forum members).
+Posting privately to a public forum, will result in your posting being displayed on the forum wall, but not on yours.
 
-You may also post to a community forum by posting a "wall-to-wall" post using secure cross-site authentication.   
+You may also post to a community forum by posting a "wall-to-wall" post using secure cross-site authentication.
 
 Comments which are relayed to community forums will be relayed back to the original post creator.
 Mentioning the forum with an @-tag in a comment does not relay the message, as distribution is controlled entirely by the original post creator. 
index 3b6442867c6ccb089cea7fabadb1ff2ee1fcc4be..1f9b0cfab7c0915f272873c3f70a82e0c361479c 100644 (file)
@@ -47,8 +47,10 @@ Friendica Documentation and Resources
 * [Theme Development](help/themes)
 * [Smarty 3 Templates](help/smarty3-templates)
 * [Database schema documantation](help/database)
+* [Class Autoloading](help/autoloader)
 * [Code - Reference(Doxygen generated - sets cookies)](doc/html/)
 
+
 **External Resources**
 
 * [Main Website](http://friendica.com)
index 24d403e1f6f57514070fbc528a0bc81fd4bfc4e7..a30a3f4a71fe1db2f95815ce75717ff69439024e 100644 (file)
@@ -1,5 +1,7 @@
 Friendica Addon/Plugin development
-==========================
+==============
+
+* [Home](help)
 
 Please see the sample addon 'randplace' for a working example of using some of these features.
 Addons work by intercepting event hooks - which must be registered.
@@ -16,12 +18,12 @@ Future extensions may provide for "setup" amd "remove".
 
 Plugins should contain a comment block with the four following parameters:
 
-       /*
-       * Name: My Great Plugin
-       * Description: This is what my plugin does. It's really cool
-       * Version: 1.0
-       * Author: John Q. Public <john@myfriendicasite.com>
-       */
+    /*
+     * Name: My Great Plugin
+     * Description: This is what my plugin does. It's really cool.
+     * Version: 1.0
+     * Author: John Q. Public <john@myfriendicasite.com>
+     */
 
 Register your plugin hooks during installation.
 
@@ -45,7 +47,7 @@ Your hook callback functions will be called with at least one and possibly two a
 
 If you wish to make changes to the calling data, you must declare them as reference variables (with '&') during function declaration.
 
-###$a
+###$a
 $a is the Friendica 'App' class.
 It contains a wealth of information about the current state of Friendica:
 
@@ -56,13 +58,13 @@ It contains a wealth of information about the current state of Friendica:
 
 It is recommeded you call this '$a' to match its usage elsewhere.
 
-###$b
+###$b
 $b can be called anything you like.
 This is information specific to the hook currently being processed, and generally contains information that is being immediately processed or acted on that you can use, display, or alter.
 Remember to declare it with '&' if you wish to alter it.
 
 Modules
---------
+---
 
 Plugins/addons may also act as "modules" and intercept all page requests for a given URL path.
 In order for a plugin to act as a module it needs to define a function "plugin_name_module()" which takes no arguments and needs not do anything.
@@ -72,15 +74,15 @@ These are parsed into an array $a->argv, with a corresponding $a->argc indicatin
 So http://my.web.site/plugin/arg1/arg2 would look for a module named "plugin" and pass its module functions the $a App structure (which is available to many components).
 This will include:
 
-     $a->argc = 3
-     $a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2');
+    $a->argc = 3
+    $a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2');
 
 Your module functions will often contain the function plugin_name_content(&$a), which defines and returns the page body content.
 They may also contain plugin_name_post(&$a) which is called before the _content function and typically handles the results of POST forms.
 You may also have plugin_name_init(&$a) which is called very early on and often does module initialisation.
 
 Templates
-----------
+---
 
 If your plugin needs some template, you can use the Friendica template system.
 Friendica uses [smarty3](http://www.smarty.net/) as a template engine.
@@ -104,140 +106,140 @@ See also the wiki page [Quick Template Guide](https://github.com/friendica/frien
 Current hooks
 -------------
 
-###'authenticate'
+### 'authenticate'
 'authenticate' is called when a user attempts to login.
 $b is an array containing:
 
-       'username' => the supplied username
-       'password' => the supplied password
+    'username' => the supplied username
+    'password' => the supplied password
     'authenticated' => set this to non-zero to authenticate the user.
     'user_record' => successful authentication must also return a valid user record from the database
 
-###'logged_in'
+### 'logged_in'
 'logged_in' is called after a user has successfully logged in.
 $b contains the $a->user array.
 
-###'display_item'
+### 'display_item'
 'display_item' is called when formatting a post for display.
 $b is an array:
 
        'item' => The item (array) details pulled from the database
        'output' => the (string) HTML representation of this item prior to adding it to the page
 
-###'post_local'
+### 'post_local'
 * called when a status post or comment is entered on the local system
 * $b is the item array of the information to be stored in the database
 * Please note: body contents are bbcode - not HTML
 
-###'post_local_end'
+### 'post_local_end'
 * called when a local status post or comment has been stored on the local system
 * $b is the item array of the information which has just been stored in the database
 * Please note: body contents are bbcode - not HTML
 
-###'post_remote'
+### 'post_remote'
 * called when receiving a post from another source. This may also be used to post local activity or system generated messages.
 * $b is the item array of information to be stored in the database and the item body is bbcode.
 
-###'settings_form'
+### 'settings_form'
 * called when generating the HTML for the user Settings page
 * $b is the (string) HTML of the settings page before the final '</form>' tag.
 
-###'settings_post'
+### 'settings_post'
 * called when the Settings pages are submitted
 * $b is the $_POST array
 
-###'plugin_settings'
+### 'plugin_settings'
 * called when generating the HTML for the addon settings page
 * $b is the (string) HTML of the addon settings page before the final '</form>' tag.
 
-###'plugin_settings_post'
+### 'plugin_settings_post'
 * called when the Addon Settings pages are submitted
 * $b is the $_POST array
 
-###'profile_post'
+### 'profile_post'
 * called when posting a profile page
 * $b is the $_POST array
 
-###'profile_edit'
+### 'profile_edit'
 'profile_edit' is called prior to output of profile edit page.
 $b is an array containing:
 
        'profile' => profile (array) record from the database
        'entry' => the (string) HTML of the generated entry
 
-###'profile_advanced'
+### 'profile_advanced'
 * called when the HTML is generated for the 'Advanced profile', corresponding to the 'Profile' tab within a person's profile page
 * $b is the (string) HTML representation of the generated profile
 * The profile array details are in $a->profile.
 
-###'directory_item'
+### 'directory_item'
 'directory_item' is called from the Directory page when formatting an item for display.
 $b is an array:
 
        'contact' => contact (array) record for the person from the database
     'entry' => the (string) HTML of the generated entry
 
-###'profile_sidebar_enter'
+### 'profile_sidebar_enter'
 * called prior to generating the sidebar "short" profile for a page
 * $b is the person's profile array
 
-###'profile_sidebar'
+### 'profile_sidebar'
 'profile_sidebar is called when generating the sidebar "short" profile for a page.
 $b is an array:
 
        'profile' => profile (array) record for the person from the database
        'entry' => the (string) HTML of the generated entry
 
-###'contact_block_end'
+### 'contact_block_end'
 is called when formatting the block of contacts/friends on a profile sidebar has completed.
 $b is an array:
 
        'contacts' => array of contacts
        'output' => the (string) generated HTML of the contact block
 
-###'bbcode'
+### 'bbcode'
 * called during conversion of bbcode to html
 * $b is a string converted text
 
-###'html2bbcode'
+### 'html2bbcode'
 * called during conversion of html to bbcode (e.g. remote message posting)
 * $b is a string converted text
 
-###'page_header'
+### 'page_header'
 * called after building the page navigation section
 * $b is a string HTML of nav region
 
-###'personal_xrd'
+### 'personal_xrd'
 'personal_xrd' is called prior to output of personal XRD file.
 $b is an array:
 
        'user' => the user record for the person
        'xml' => the complete XML to be output
 
-###'home_content'
+### 'home_content'
 * called prior to output home page content, shown to unlogged users
 * $b is (string) HTML of section region
 
-###'contact_edit'
+### 'contact_edit'
 is called when editing contact details on an individual from the Contacts page.
 $b is an array:
 
        'contact' => contact record (array) of target contact
        'output' => the (string) generated HTML of the contact edit page
 
-###'contact_edit_post'
+### 'contact_edit_post'
 * called when posting the contact edit page.
 * $b is the $_POST array
 
-###'init_1'
+### 'init_1'
 * called just after DB has been opened and before session start
 * $b is not used or passed
 
-###'page_end'
+### 'page_end'
 * called after HTML content functions have completed
 * $b is (string) HTML of content div
 
-###'avatar_lookup'
+### 'avatar_lookup'
 'avatar_lookup' is called when looking up the avatar.
 $b is an array:
 
@@ -245,11 +247,11 @@ $b is an array:
        'email' => email to look up the avatar for
        'url' => the (string) generated URL of the avatar
 
-###'emailer_send_prepare'
+### 'emailer_send_prepare'
 'emailer_send_prepare' called from Emailer::send() before building the mime message.
 $b is an array, params to Emailer::send()
 
-       'fromName' => name of the sender
+    'fromName' => name of the sender
     'fromEmail' => email fo the sender
     'replyTo' => replyTo address to direct responses
     'toEmail' => destination email address
@@ -258,20 +260,20 @@ $b is an array, params to Emailer::send()
     'textVersion' => text only version of the message
     'additionalMailHeader' => additions to the smtp mail header
 
-###'emailer_send'
+### 'emailer_send'
 is called before calling PHP's mail().
 $b is an array, params to mail()
 
-       'to'
-       'subject'
+    'to'
+    'subject'
     'body'
     'headers'
 
-###'nav_info'
+### 'nav_info'
 is called after the navigational menu is build in include/nav.php.
 $b is an array containing $nav from nav.php.
 
-###'template_vars'
+### 'template_vars'
 is called before vars are passed to the template engine to render the page.
 The registered function can add,change or remove variables passed to template.
 $b is an array with:
@@ -463,4 +465,3 @@ mod/cb.php: call_hooks('cb_afterpost');
 mod/cb.php:    call_hooks('cb_content', $o);
 
 mod/directory.php:                     call_hooks('directory_item', $arr);
-
index a72eec2a16c6796bfdcf781218729d3e8a1653b3..bcff929fe54effd699f0f27ae55fb9a341ed744a 100644 (file)
@@ -90,8 +90,8 @@ If you run your own server, upload the files and check out the Mozilla wiki link
 Let's encrypt
 ---
 
-If you run your own server and you control your name server, the "Let's encrypt" initiative might become an interesting alternative.
-Their offer is not ready, yet.
+If you run your own server, the "Let's encrypt" initiative might become an interesting alternative.
+Their offer is in public beta right now.
 Check out [their website](https://letsencrypt.org/) for status updates.
 
 Web server settings
index ae7d916078c8017a2d7243ebbf1ab097a88e335d..86254cb29e37f65dfbe521ba32813100356dd0aa 100644 (file)
@@ -24,6 +24,9 @@ You are not required to provide the year.
 System settings
 ---
 
+**Settings should be done in the admin panel** (/admin).
+Those settings found in the database, will always override the settings added to the ``.htconfig.php`` file.
+
 ###Language
 
 Please see util/README for information on creating language translations.
@@ -219,6 +222,8 @@ LOGGER_DEBUG will show a good deal of information about system activity but will
 You may also select LOGGER_ALL but due to the volume of information we recommend only enabling this when you are tracking down a specific problem.
 Other log levels are possible but are not being used at the present time. 
 
+Please be aware that turning on the logging can fill up the disk space on your server relatively quick.
+You should take preventions with e.g. [logrotate](https://en.wikipedia.org/wiki/Log_rotation) or similar tools.
 
 ###PHP error logging
 
@@ -237,4 +242,6 @@ The vast majority of issues reported at these levels are completely harmless.
 Please report to the developers any errors you encounter in the logs using the recommended settings above.
 They generally indicate issues which need to be resolved. 
 
-If you encounter a blank (white) page when using the application, view the PHP logs - as this almost always indicates an error has occurred.  
+If you encounter a blank (white) page when using the application, view the PHP logs - as this almost always indicates an error has occurred.
+
+*Note*: PHP logging cannot be activated from the admin panel but has to be configured from the ``.htconfig.php`` file.
index 5501d0d580ffca4708afc3e1c905eeaccb1554be..eac2c97f84937ed559bff64b0f8d8f4ebe39ce58 100644 (file)
@@ -21,11 +21,22 @@ You can tag a person on a different network or one that is **not in your social
        
 * @mike@macgirvin.com - This is called a "remote mention" and can only be an email-style locator, not a web URL.
 
-Unless their system blocks unsolicited "mentions", the person tagged will likely receive a "Mention" post/activity or become a direct participant in the conversation in the case of public posts. Please note that Friendica blocks incoming "mentions" from people with no relationship to you. This is a spam prevention measure.
-
-Remote mentions are delivered using the OStatus protocol. This protocol is used by Friendica and GNU Social and several other systems, but is not currently implemented in Diaspora. 
-
-Friendica makes no distinction between people and groups for the purpose of tagging. (Some other networks use !group to indicate a group.)
+Unless their system blocks unsolicited "mentions", the person tagged will likely receive a "Mention" post/activity or become a direct participant in the conversation in the case of public posts.
+Friendica blocks incoming “mentions” from people with no relationship to you.
+The exception is an ongiong conversation started from a contact of both you and the 3rd person or a conversation in a forum where you are a member of.
+This is a spam prevention measure.
+
+Remote mentions are delivered using the OStatus protocol.
+This protocol is used by Friendica and GNU Social and several other systems, but is not currently implemented in Diaspora. 
+As the OStatus protocol allows this Friendica user can be @-mentioned by users from platforms using this protocol in conversations if the "Enable OStatus support" is activated on the Friendica node.
+These @-mentions wont be blocked, even if there is no relationship between the sender and the receiver of the message.
+
+Friendica makes no distinction between people and forums for the purpose of tagging.
+(Some other networks use !forum to indicate a forum.)
+
+If you sort your contacts into groups, you cannot @-mention these groups.
+But you can select the group in the access control when creating a new posting, to allow (or disallow) a certain group of people to see the posting.
+See [Groups and Privacy](help/Groups-and-Privacy) for more details about grouping your contacts.
 
 **Topical Tags**
 
index ced078f55669860ccd6d77a9542e43f72d616420..c020f403ff2eacdf81138d98ef8ada61ba999b18 100644 (file)
@@ -7,6 +7,21 @@ Please refer to the linked documentation for further information.
 ## Implemented API calls\r
 \r
 ### General\r
+#### HTTP Method\r
+\r
+API endpoints can restrict the method used to request them.\r
+Using an invalid method results in HTTP error 405 "Method Not Allowed".\r
+\r
+In this document, the required method is listed after the endpoint name. "*" means every method can be used.\r
+\r
+#### Auth\r
+\r
+Friendica supports basic http auth and OAuth 1 to authenticate the user to the api.\r
+\r
+OAuth settings can be added by the user in web UI under /settings/oauth/\r
+\r
+In this document, endpoints which requires auth are marked with "AUTH" after endpoint name\r
+\r
 #### Unsupported parameters\r
 * cursor: Not implemented in GNU Social\r
 * trim_user: Not implemented in GNU Social\r
@@ -38,9 +53,9 @@ Error body is
 json:\r
 ```\r
        {\r
-         "error": "Specific error message",\r
-         "request": "API path requested",\r
-         "code": "HTTP error code"\r
+               "error": "Specific error message",\r
+               "request": "API path requested",\r
+               "code": "HTTP error code"\r
        }\r
 ```\r
 \r
@@ -54,19 +69,20 @@ xml:
 ```\r
 \r
 ---\r
-### account/rate_limit_status\r
+### account/rate_limit_status (*; AUTH)\r
 \r
 ---\r
-### account/verify_credentials\r
+### account/verify_credentials (*; AUTH)\r
 #### Parameters\r
+\r
 * skip_status: Don't show the "status" field. (Default: false)\r
 * include_entities: "true" shows entities for pictures and links (Default: false)\r
 \r
 ---\r
-### conversation/show\r
+### conversation/show (*; AUTH)\r
 Unofficial Twitter command. It shows all direct answers (excluding the original post) to a given id.\r
 \r
-#### Parameters\r
+#### Parameter\r
 * id: id of the post\r
 * count: Items per page (default: 20)\r
 * page: page number\r
@@ -80,7 +96,7 @@ Unofficial Twitter command. It shows all direct answers (excluding the original
 * contributor_details\r
 \r
 ---\r
-### direct_messages\r
+### direct_messages (*; AUTH)\r
 #### Parameters\r
 * count: Items per page (default: 20)\r
 * page: page number\r
@@ -93,7 +109,7 @@ Unofficial Twitter command. It shows all direct answers (excluding the original
 * skip_status\r
 \r
 ---\r
-### direct_messages/all\r
+### direct_messages/all (*; AUTH)\r
 #### Parameters\r
 * count: Items per page (default: 20)\r
 * page: page number\r
@@ -102,7 +118,7 @@ Unofficial Twitter command. It shows all direct answers (excluding the original
 * getText: Defines the format of the status field. Can be "html" or "plain"\r
 \r
 ---\r
-### direct_messages/conversation\r
+### direct_messages/conversation (*; AUTH)\r
 Shows all direct messages of a conversation\r
 #### Parameters\r
 * count: Items per page (default: 20)\r
@@ -113,7 +129,7 @@ Shows all direct messages of a conversation
 * uri: URI of the conversation\r
 \r
 ---\r
-### direct_messages/new\r
+### direct_messages/new (POST,PUT; AUTH)\r
 #### Parameters\r
 * user_id: id of the user\r
 * screen_name: screen name (for technical reasons, this value is not unique!)\r
@@ -122,7 +138,7 @@ Shows all direct messages of a conversation
 * title: Title of the direct message\r
 \r
 ---\r
-### direct_messages/sent\r
+### direct_messages/sent (*; AUTH)\r
 #### Parameters\r
 * count: Items per page (default: 20)\r
 * page: page number\r
@@ -132,7 +148,7 @@ Shows all direct messages of a conversation
 * include_entities: "true" shows entities for pictures and links (Default: false)\r
 \r
 ---\r
-### favorites\r
+### favorites (*; AUTH)\r
 #### Parameters\r
 * count: Items per page (default: 20)\r
 * page: page number\r
@@ -144,22 +160,23 @@ Shows all direct messages of a conversation
 * user_id\r
 * screen_name\r
 \r
-Favorites aren't displayed to other users, so "user_id" and "screen_name". So setting this value will result in an empty array.\r
+Favorites aren't displayed to other users, so "user_id" and "screen_name" are unsupported.\r
+Set this values will result in an empty array.\r
 \r
 ---\r
-### favorites/create\r
+### favorites/create (POST,PUT; AUTH)\r
 #### Parameters\r
 * id\r
 * include_entities: "true" shows entities for pictures and links (Default: false)\r
 \r
 ---\r
-### favorites/destroy\r
+### favorites/destroy (POST,DELETE; AUTH)\r
 #### Parameters\r
 * id\r
 * include_entities: "true" shows entities for pictures and links (Default: false)\r
 \r
 ---\r
-### followers/ids\r
+### followers/ids (*; AUTH)\r
 #### Parameters\r
 * stringify_ids: Should the id numbers be sent as text (true) or number (false)? (default: false)\r
 \r
@@ -171,139 +188,7 @@ Favorites aren't displayed to other users, so "user_id" and "screen_name". So se
 Friendica doesn't allow showing followers of other users.\r
 \r
 ---\r
-### friendica/activity/<verb>\r
-#### parameters\r
-* id: item id\r
-\r
-Add or remove an activity from an item.\r
-'verb' can be one of:\r
-- like\r
-- dislike\r
-- attendyes\r
-- attendno\r
-- attendmaybe\r
-\r
-To remove an activity, prepend the verb with "un", eg. "unlike" or "undislike"\r
-Attend verbs disable eachother: that means that if "attendyes" was added to an item, adding "attendno" remove previous "attendyes".\r
-Attend verbs should be used only with event-related items (there is no check at the moment)\r
-\r
-#### Return values\r
-\r
-On success:\r
-json\r
-```"ok"```\r
-\r
-xml\r
-```<ok>true</ok>```\r
-\r
-On error:\r
-HTTP 400 BadRequest\r
-\r
----\r
-### friendica/photo\r
-#### Parameters\r
-* photo_id: Resource id of a photo.\r
-* scale: (optional) scale value of the photo\r
-\r
-Returns data of a picture with the given resource.\r
-If 'scale' isn't provided, returned data include full url to each scale of the photo.\r
-If 'scale' is set, returned data include image data base64 encoded.\r
-\r
-possibile scale value are:\r
-0: original or max size by server settings\r
-1: image with or height at <= 640\r
-2: image with or height at <= 320\r
-3: thumbnail 160x160\r
-\r
-4: Profile image at 175x175\r
-5: Profile image at 80x80\r
-6: Profile image at 48x48\r
-\r
-An image used as profile image has only scale 4-6, other images only 0-3\r
-\r
-#### Return values\r
-\r
-json\r
-```\r
-       {\r
-         "id": "photo id"\r
-         "created": "date(YYYY-MM-GG HH:MM:SS)",\r
-         "edited": "date(YYYY-MM-GG HH:MM:SS)",\r
-         "title": "photo title",\r
-         "desc": "photo description",\r
-         "album": "album name",\r
-         "filename": "original file name",\r
-         "type": "mime type",\r
-         "height": "number",\r
-         "width": "number",\r
-         "profile": "1 if is profile photo",\r
-         "link": {\r
-               "<scale>": "url to image"\r
-               ...\r
-         },\r
-         // if 'scale' is set\r
-         "datasize": "size in byte",\r
-         "data": "base64 encoded image data"\r
-       }\r
-```\r
-\r
-xml\r
-```\r
-       <photo>\r
-               <id>photo id</id>\r
-               <created>date(YYYY-MM-GG HH:MM:SS)</created>\r
-               <edited>date(YYYY-MM-GG HH:MM:SS)</edited>\r
-               <title>photo title</title>\r
-               <desc>photo description</desc>\r
-               <album>album name</album>\r
-               <filename>original file name</filename>\r
-               <type>mime type</type>\r
-               <height>number</height>\r
-               <width>number</width>\r
-               <profile>1 if is profile photo</profile>\r
-               <links type="array">\r
-                       <link type="mime type" scale="scale number" href="image url"/>\r
-                       ...\r
-               </links>\r
-       </photo>\r
-```\r
-\r
----\r
-### friendica/photos/list\r
-\r
-Returns a list of all photo resources of the logged in user.\r
-\r
-#### Return values\r
-\r
-json\r
-```\r
-       [\r
-               {\r
-                       id: "resource_id",\r
-                       album: "album name",\r
-                       filename: "original file name",\r
-                       type: "image mime type",\r
-                       thumb: "url to thumb sized image"\r
-               },\r
-               ...\r
-       ]\r
-```\r
-\r
-xml\r
-```\r
-       <photos type="array">\r
-               <photo id="resource_id"\r
-                       album="album name"\r
-                       filename="original file name"\r
-                       type="image mime type">\r
-                               "url to thumb sized image"\r
-               </photo>\r
-               ...\r
-       </photos>\r
-```\r
-\r
----\r
-### friends/ids\r
+### friends/ids (*; AUTH)\r
 #### Parameters\r
 * stringify_ids: Should the id numbers be sent as text (true) or number (false)? (default: false)\r
 \r
@@ -315,15 +200,15 @@ xml
 Friendica doesn't allow showing friends of other users.\r
 \r
 ---\r
-### help/test\r
+### help/test (*)\r
 \r
 ---\r
-### media/upload\r
+### media/upload (POST,PUT; AUTH)\r
 #### Parameters\r
 * media: image data\r
 \r
 ---\r
-### oauth/request_token\r
+### oauth/request_token (*)\r
 #### Parameters\r
 * oauth_callback\r
 \r
@@ -331,7 +216,7 @@ Friendica doesn't allow showing friends of other users.
 * x_auth_access_type\r
 \r
 ---\r
-### oauth/access_token\r
+### oauth/access_token (*)\r
 #### Parameters\r
 * oauth_verifier\r
 \r
@@ -341,7 +226,7 @@ Friendica doesn't allow showing friends of other users.
 * x_auth_mode\r
 \r
 ---\r
-### statuses/destroy\r
+### statuses/destroy (POST,DELETE; AUTH)\r
 #### Parameters\r
 * id: message number\r
 * include_entities: "true" shows entities for pictures and links (Default: false)\r
@@ -350,15 +235,21 @@ Friendica doesn't allow showing friends of other users.
 * trim_user\r
 \r
 ---\r
-### statuses/followers\r
+### statuses/followers (*; AUTH)\r
+\r
+#### Parameters\r
+\r
 * include_entities: "true" shows entities for pictures and links (Default: false)\r
 \r
 ---\r
-### statuses/friends\r
+### statuses/friends (*; AUTH)\r
+\r
+#### Parameters\r
+\r
 * include_entities: "true" shows entities for pictures and links (Default: false)\r
 \r
 ---\r
-### statuses/friends_timeline\r
+### statuses/friends_timeline (*; AUTH)\r
 #### Parameters\r
 * count: Items per page (default: 20)\r
 * page: page number\r
@@ -374,7 +265,7 @@ Friendica doesn't allow showing friends of other users.
 * contributor_details\r
 \r
 ---\r
-### statuses/home_timeline\r
+### statuses/home_timeline (*; AUTH)\r
 #### Parameters\r
 * count: Items per page (default: 20)\r
 * page: page number\r
@@ -390,7 +281,7 @@ Friendica doesn't allow showing friends of other users.
 * contributor_details\r
 \r
 ---\r
-### statuses/mentions\r
+### statuses/mentions (*; AUTH)\r
 #### Parameters\r
 * count: Items per page (default: 20)\r
 * page: page number\r
@@ -404,7 +295,7 @@ Friendica doesn't allow showing friends of other users.
 * contributor_details\r
 \r
 ---\r
-### statuses/public_timeline\r
+### statuses/public_timeline (*; AUTH)\r
 #### Parameters\r
 * count: Items per page (default: 20)\r
 * page: page number\r
@@ -418,7 +309,7 @@ Friendica doesn't allow showing friends of other users.
 * trim_user\r
 \r
 ---\r
-### statuses/replies\r
+### statuses/replies (*; AUTH)\r
 #### Parameters\r
 * count: Items per page (default: 20)\r
 * page: page number\r
@@ -432,7 +323,7 @@ Friendica doesn't allow showing friends of other users.
 * contributor_details\r
 \r
 ---\r
-### statuses/retweet\r
+### statuses/retweet (POST,PUT; AUTH)\r
 #### Parameters\r
 * id: message number\r
 * include_entities: "true" shows entities for pictures and links (Default: false)\r
@@ -441,7 +332,7 @@ Friendica doesn't allow showing friends of other users.
 * trim_user\r
 \r
 ---\r
-### statuses/show\r
+### statuses/show (*; AUTH)\r
 #### Parameters\r
 * id: message number\r
 * conversation: if set to "1" show all messages of the conversation with the given id\r
@@ -476,7 +367,7 @@ Friendica doesn't allow showing friends of other users.
 * display_coordinates\r
 \r
 ---\r
-### statuses/user_timeline\r
+### statuses/user_timeline (*; AUTH)\r
 #### Parameters\r
 * user_id: id of the user\r
 * screen_name: screen name (for technical reasons, this value is not unique!)\r
@@ -489,15 +380,16 @@ Friendica doesn't allow showing friends of other users.
 * include_entities: "true" shows entities for pictures and links (Default: false)\r
 \r
 #### Unsupported parameters\r
+\r
 * include_rts\r
 * trim_user\r
 * contributor_details\r
 \r
 ---\r
-### statusnet/config\r
+### statusnet/config (*)\r
 \r
 ---\r
-### statusnet/version\r
+### statusnet/version (*)\r
 \r
 #### Unsupported parameters\r
 * user_id\r
@@ -507,7 +399,7 @@ Friendica doesn't allow showing friends of other users.
 Friendica doesn't allow showing followers of other users.\r
 \r
 ---\r
-### users/search\r
+### users/search (*)\r
 #### Parameters\r
 * q: name of the user\r
 \r
@@ -517,7 +409,7 @@ Friendica doesn't allow showing followers of other users.
 * include_entities\r
 \r
 ---\r
-### users/show\r
+### users/show (*)\r
 #### Parameters\r
 * user_id: id of the user\r
 * screen_name: screen name (for technical reasons, this value is not unique!)\r
@@ -533,8 +425,39 @@ Friendica doesn't allow showing friends of other users.
 \r
 ## Implemented API calls (not compatible with other APIs)\r
 \r
+\r
+---\r
+### friendica/activity/<verb>\r
+#### parameters\r
+* id: item id\r
+\r
+Add or remove an activity from an item.\r
+'verb' can be one of:\r
+\r
+- like\r
+- dislike\r
+- attendyes\r
+- attendno\r
+- attendmaybe\r
+\r
+To remove an activity, prepend the verb with "un", eg. "unlike" or "undislike"\r
+Attend verbs disable eachother: that means that if "attendyes" was added to an item, adding "attendno" remove previous "attendyes".\r
+Attend verbs should be used only with event-related items (there is no check at the moment)\r
+\r
+#### Return values\r
+\r
+On success:\r
+json\r
+```"ok"```\r
+\r
+xml\r
+```<ok>true</ok>```\r
+\r
+On error:\r
+HTTP 400 BadRequest\r
+\r
 ---\r
-### friendica/group_show\r
+### friendica/group_show (*; AUTH)\r
 Return all or a specified group of the user with the containing contacts as array.\r
 \r
 #### Parameters\r
@@ -542,22 +465,23 @@ Return all or a specified group of the user with the containing contacts as arra
 \r
 #### Return values\r
 Array of:\r
+\r
 * name: name of the group\r
 * gid: id of the group\r
 * user: array of group members (return from api_get_user() function for each member)\r
 \r
 \r
 ---\r
-### friendica/group_delete\r
+### friendica/group_delete (POST,DELETE; AUTH)\r
 delete the specified group of contacts; API call need to include the correct gid AND name of the group to be deleted.\r
 \r
----\r
-### Parameters\r
+#### Parameters\r
 * gid: id of the group to be deleted\r
 * name: name of the group to be deleted\r
 \r
 #### Return values\r
 Array of:\r
+\r
 * success: true if successfully deleted\r
 * gid: gid of the deleted group\r
 * name: name of the deleted group\r
@@ -566,19 +490,22 @@ Array of:
 \r
 \r
 ---\r
-### friendica/group_create\r
+### friendica/group_create (POST,PUT; AUTH)\r
 Create the group with the posted array of contacts as members.\r
+\r
 #### Parameters\r
 * name: name of the group to be created\r
 \r
 #### POST data\r
-JSON data as Array like the result of „users/group_show“:\r
+JSON data as Array like the result of "users/group_show":\r
+\r
 * gid\r
 * name\r
 * array of users\r
 \r
 #### Return values\r
 Array of:\r
+\r
 * success: true if successfully created or reactivated\r
 * gid: gid of the created group\r
 * name: name of the created group\r
@@ -587,26 +514,175 @@ Array of:
 \r
 \r
 ---\r
-### friendica/group_update\r
+### friendica/group_update (POST)\r
 Update the group with the posted array of contacts as members (post all members of the group to the call; function will remove members not posted).\r
+\r
 #### Parameters\r
 * gid: id of the group to be changed\r
 * name: name of the group to be changed\r
 \r
 #### POST data\r
 JSON data as array like the result of „users/group_show“:\r
+\r
 * gid\r
 * name\r
 * array of users\r
 \r
 #### Return values\r
 Array of:\r
+\r
 * success: true if successfully updated\r
 * gid: gid of the changed group\r
 * name: name of the changed group\r
 * status: „missing user“ | „ok“\r
 * wrong users: array of users, which were not available in the contact table\r
 \r
+\r
+\r
+---\r
+### friendica/notifications (GET)\r
+Return last 50 notification for current user, ordered by date with unseen item on top\r
+\r
+#### Parameters\r
+none\r
+\r
+#### Return values\r
+Array of:\r
+\r
+* id: id of the note\r
+* type: type of notification as int (see NOTIFY_* constants in boot.php)\r
+* name: full name of the contact subject of the note\r
+* url: contact's profile url\r
+* photo: contact's profile photo\r
+* date: datetime string of the note\r
+* timestamp: timestamp of the node\r
+* date_rel: relative date of the note (eg. "1 hour ago")\r
+* msg: note message in bbcode\r
+* msg_html: note message in html\r
+* msg_plain: note message in plain text\r
+* link: link to note\r
+* seen: seen state: 0 or 1\r
+\r
+\r
+---\r
+### friendica/notifications/seen (POST)\r
+Set note as seen, returns item object if possible\r
+\r
+#### Parameters\r
+id: id of the note to set seen\r
+\r
+#### Return values\r
+If the note is linked to an item, the item is returned, just like one of the "statuses/*_timeline" api.\r
+\r
+If the note is not linked to an item, a success status is returned:\r
+\r
+* "success" (json) | "&lt;status&gt;success&lt;/status&gt;" (xml)\r
+\r
+\r
+---\r
+### friendica/photo (*; AUTH)\r
+#### Parameters\r
+* photo_id: Resource id of a photo.\r
+* scale: (optional) scale value of the photo\r
+\r
+Returns data of a picture with the given resource.\r
+If 'scale' isn't provided, returned data include full url to each scale of the photo.\r
+If 'scale' is set, returned data include image data base64 encoded.\r
+\r
+possibile scale value are:\r
+\r
+* 0: original or max size by server settings\r
+* 1: image with or height at <= 640\r
+* 2: image with or height at <= 320\r
+* 3: thumbnail 160x160\r
+* 4: Profile image at 175x175\r
+* 5: Profile image at 80x80\r
+* 6: Profile image at 48x48\r
+\r
+An image used as profile image has only scale 4-6, other images only 0-3\r
+\r
+#### Return values\r
+\r
+json\r
+```\r
+       {\r
+               "id": "photo id"\r
+               "created": "date(YYYY-MM-GG HH:MM:SS)",\r
+               "edited": "date(YYYY-MM-GG HH:MM:SS)",\r
+               "title": "photo title",\r
+               "desc": "photo description",\r
+               "album": "album name",\r
+               "filename": "original file name",\r
+               "type": "mime type",\r
+               "height": "number",\r
+               "width": "number",\r
+               "profile": "1 if is profile photo",\r
+               "link": {\r
+                       "<scale>": "url to image"\r
+                       ...\r
+               },\r
+               // if 'scale' is set\r
+               "datasize": "size in byte",\r
+               "data": "base64 encoded image data"\r
+       }\r
+```\r
+\r
+xml\r
+```\r
+       <photo>\r
+               <id>photo id</id>\r
+               <created>date(YYYY-MM-GG HH:MM:SS)</created>\r
+               <edited>date(YYYY-MM-GG HH:MM:SS)</edited>\r
+               <title>photo title</title>\r
+               <desc>photo description</desc>\r
+               <album>album name</album>\r
+               <filename>original file name</filename>\r
+               <type>mime type</type>\r
+               <height>number</height>\r
+               <width>number</width>\r
+               <profile>1 if is profile photo</profile>\r
+               <links type="array">\r
+               <link type="mime type" scale="scale number" href="image url"/>\r
+                       ...\r
+               </links>\r
+       </photo>\r
+```\r
+\r
+---\r
+### friendica/photos/list (*; AUTH)\r
+\r
+Returns a list of all photo resources of the logged in user.\r
+\r
+#### Return values\r
+\r
+json\r
+```\r
+       [\r
+               {\r
+                       id: "resource_id",\r
+                       album: "album name",\r
+                       filename: "original file name",\r
+                       type: "image mime type",\r
+                       thumb: "url to thumb sized image"\r
+               },\r
+               ...\r
+       ]\r
+```\r
+\r
+xml\r
+```\r
+       <photos type="array">\r
+               <photo id="resource_id"\r
+               album="album name"\r
+               filename="original file name"\r
+               type="image mime type">\r
+                       "url to thumb sized image"\r
+               </photo>\r
+               ...\r
+       </photos>\r
+```\r
+\r
+\r
 ---\r
 ## Not Implemented API calls\r
 The following API calls are implemented in GNU Social but not in Friendica: (incomplete)\r
@@ -702,13 +778,13 @@ The following API calls from the Twitter API aren't implemented neither in Frien
 ### BASH / cURL\r
 Betamax has documentated some example API usage from a [bash script](https://en.wikipedia.org/wiki/Bash_(Unix_shell) employing [curl](https://en.wikipedia.org/wiki/CURL) (see [his posting](https://betamax65.de/display/betamax65/43539)).\r
 \r
-    /usr/bin/curl -u USER:PASS https://YOUR.FRIENDICA.TLD/api/statuses/update.xml -d source="some source id" -d status="the status you want to post"\r
+/usr/bin/curl -u USER:PASS https://YOUR.FRIENDICA.TLD/api/statuses/update.xml -d source="some source id" -d status="the status you want to post"\r
 \r
 ### Python\r
 The [RSStoFriedika](https://github.com/pafcu/RSStoFriendika) code can be used as an example of how to use the API with python. The lines for posting are located at [line 21](https://github.com/pafcu/RSStoFriendika/blob/master/RSStoFriendika.py#L21) and following.\r
 \r
-    def tweet(server, message, group_allow=None):\r
-        url = server + '/api/statuses/update'\r
-        urllib2.urlopen(url, urllib.urlencode({'status': message,'group_allow[]':group_allow}, doseq=True))\r
+def tweet(server, message, group_allow=None):\r
+url = server + '/api/statuses/update'\r
+urllib2.urlopen(url, urllib.urlencode({'status': message,'group_allow[]':group_allow}, doseq=True))\r
 \r
 There is also a [module for python 3](https://bitbucket.org/tobiasd/python-friendica) for using the API.\r
diff --git a/doc/autoloader.md b/doc/autoloader.md
new file mode 100644 (file)
index 0000000..947eade
--- /dev/null
@@ -0,0 +1,209 @@
+Autoloader\r
+==========\r
+\r
+* [Home](help)\r
+\r
+There is some initial support to class autoloading in Friendica core.\r
+\r
+The autoloader code is in `include/autoloader.php`.\r
+It's derived from composer autoloader code.\r
+\r
+Namespaces and Classes are mapped to folders and files in `library/`,\r
+and the map must be updated by hand, because we don't use composer yet.\r
+The mapping is defined by files in `include/autoloader/` folder.\r
+\r
+Currently, only HTMLPurifier library is loaded using autoloader.\r
+\r
+\r
+## A quick introdution to class autoloading\r
+\r
+The autoloader it's a way for php to automagically include the file that define a class when the class is first used, without the need to use "require_once" every time.\r
+\r
+Once is setup you don't have to use it in any way. You need a class? you use the class.\r
+\r
+At his basic is a function passed to the "spl_autoload_register()" function, which receive as argument the class name the script want and is it job to include the correct php file where that class is defined.\r
+The best source for documentation is [php site](http://php.net/manual/en/language.oop5.autoload.php).\r
+\r
+One example, based on fictional friendica code.\r
+\r
+Let's say you have a php file in "include/" that define a very useful class:\r
+\r
+```\r
+    file: include/ItemsManager.php\r
+    <?php\r
+    namespace \Friendica;\r
+    \r
+    class ItemsManager {\r
+       public function getAll() { ... }\r
+       public function getByID($id) { ... }\r
+    }\r
+```\r
+\r
+The class "ItemsManager" has been declared in "Friendica" namespace.\r
+Namespaces are useful to keep things separated and avoid names clash (could be that a library you want to use defines a class named "ItemsManager", but as long as is in another namespace, you don't have any problem)\r
+\r
+If we were using composer, we had configured it with path where to find the classes of "Friendica" namespace, and then the composer script will generate the autoloader machinery for us.\r
+As we don't use composer, we need check that the autoloader knows the Friendica namespace.\r
+So in "include/autoloader/autoload_psr4.php" there should be something like\r
+\r
+```\r
+    $vendorDir = dirname(dirname(dirname(__FILE__)))."/library";\r
+    $baseDir = dirname($vendorDir);\r
+    return array(\r
+       "Friendica" => array($baseDir."/include");\r
+    );\r
+```\r
+\r
+\r
+That tells the autoloader code to look for files that defines classes in "Friendica" namespace under "include/" folder. (And btw, that's why the file has the same name as the class it defines.)\r
+\r
+*note*: The structure of files in "include/autoloader/" has been copied from the code generated by composer, to ease the work of enable autoloader for external libraries under "library/"\r
+\r
+Let's say now that you need to load some items in a view, maybe in a fictional "mod/network.php".\r
+Somewere at the start of the scripts, the autoloader was initialized. In Friendica is done at the top of "boot.php", with "require_once('include/autoloader.php');".\r
+\r
+The code will be something like:\r
+\r
+```\r
+    file: mod/network.php\r
+    <?php\r
+    \r
+    function network_content(&$a) {\r
+       $itemsmanager = new \Friendica\ItemsManager();\r
+       $items = $itemsmanager->getAll();\r
+    \r
+       // pass $items to template\r
+       // return result\r
+    }\r
+```\r
+\r
+That's a quite simple example, but look: no "require()"!\r
+You need to use a class, you use the class and you don't need to do anything more.\r
+\r
+Going further: now we have a bunch of "*Manager" classes that cause some code duplication, let's define a BaseManager class, where to move all code in common between all managers:\r
+\r
+```\r
+    file: include/BaseManager.php\r
+    <?php\r
+    namespace \Friendica;\r
+    \r
+    class BaseManager {\r
+      public function thatFunctionEveryManagerUses() { ... }\r
+    }\r
+```\r
+\r
+and then let's change the ItemsManager class to use this code\r
+\r
+```\r
+    file: include/ItemsManager.php\r
+    <?php\r
+    namespace \Friendica;\r
+    \r
+    class ItemsManager extends BaseManager {\r
+       public function getAll() { ... }\r
+       public function getByID($id) { ... }\r
+    }\r
+```\r
+\r
+The autoloader don't mind what you need the class for. You need a class, you get the class.\r
+It works with the "BaseManager" example here, it works when we need to call static methods on a class:\r
+\r
+```\r
+    file: include/dfrn.php\r
+    <?php    \r
+    namespace \Friendica;\r
+    \r
+    class dfrn {\r
+      public static function  mail($item, $owner) { ... }\r
+    }\r
+```\r
+\r
+```\r
+    file: mod/mail.php\r
+    <?php\r
+    \r
+    mail_post($a){\r
+     ...\r
+     \Friendica\dfrn::mail($item, $owner);\r
+     ...\r
+    }\r
+```\r
+\r
+If your code is in same namespace as the class you need, you don't need to prepend it:\r
+\r
+```\r
+    file: include/delivery.php\r
+    <?php\r
+    \r
+    namespace \Friendica;\r
+    \r
+    // this is the same content of current include/delivery.php, \r
+    // but has been declared to be in "Friendica" namespace\r
+    \r
+    [...]\r
+    switch($contact['network']) {\r
+    \r
+        case NETWORK_DFRN:\r
+            if ($mail) {\r
+                $item['body'] = ...\r
+                $atom = dfrn::mail($item, $owner);\r
+            } elseif ($fsuggest) {\r
+                $atom = dfrn::fsuggest($item, $owner);\r
+                q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item['id']));\r
+            } elseif ($relocate)\r
+                $atom = dfrn::relocate($owner, $uid);\r
+    [...]\r
+```\r
+\r
+This is real "include/delivery.php" unchanged, but as the code is declared to be in "Friendica" namespace, you don't need to write it when you need to use the "dfrn" class.\r
+But if you want to use classes from another library, you need to use the full namespace, e.g.\r
+\r
+```\r
+    <?php\r
+    namespace \Frienidca;\r
+    \r
+    class Diaspora {\r
+      public function md2bbcode() {\r
+        $html = \Michelf\MarkdownExtra::defaultTransform($text); \r
+      }\r
+    }\r
+```\r
+\r
+if you use that class in many places of the code and you don't want to write the full path to the class everytime, you can use the "use" php keyword\r
+\r
+```\r
+    <?php\r
+    namespace \Frienidca;\r
+    \r
+    use \Michelf\MarkdownExtra;\r
+    \r
+    class Diaspora {\r
+      public function md2bbcode() {\r
+        $html = MarkdownExtra::defaultTransform($text); \r
+      }\r
+    }\r
+```\r
+\r
+Note that namespaces are like paths in filesystem, separated by "\", with the first "\" being the global scope.\r
+You can go more deep if you want to, like:\r
+\r
+```\r
+    <?php\r
+    namespace \Friendica\Network;\r
+    \r
+    class DFRN {\r
+    }\r
+```\r
+\r
+or\r
+\r
+```\r
+    <?php\r
+    namespace \Friendica\DBA;\r
+    \r
+    class MySQL {\r
+    }\r
+```\r
+\r
+So you can think of namespaces as folders in a unix filesystem, with global scope as the root ("\").\r
+\r
index 1893f77d0cb3362a763d96b0a3673c83b77a55e1..e37df05e099e51bc65e3b2cec14964399c152923 100644 (file)
@@ -27,7 +27,6 @@ Database Tables
 | [group](help/database/db_group)                      | privacy groups, group info                       |
 | [group_member](help/database/db_group_member)        | privacy groups, member info                      |
 | [gserver](help/database/db_gserver)                  |                                                  |
-| [guid](help/database/db_guid)                        |                                                  |
 | [hook](help/database/db_hook)                        | plugin hook registry                             |
 | [intro](help/database/db_intro)                      |                                                  |
 | [item](help/database/db_item)                        | all posts                                        |
@@ -38,6 +37,8 @@ Database Tables
 | [manage](help/database/db_manage)                    | table of accounts that can "su" each other       |
 | [notify](help/database/db_notify)                    | notifications                                    |
 | [notify-threads](help/database/db_notify-threads)    |                                                  |
+| [oembed](help/database/db_oembed)                    | cache for OEmbed queries                         |
+| [parsed_url](help/database/db_parsed_url)            | cache for "parse_url" queries                    |
 | [pconfig](help/database/db_pconfig)                  | personal (per user) configuration storage        |
 | [photo](help/database/db_photo)                      | photo storage                                    |
 | [poll](help/database/db_poll)                        | data for polls                                   |
@@ -54,7 +55,6 @@ Database Tables
 | [term](help/database/db_term)                        | item taxonomy (categories, tags, etc.) table     |
 | [thread](help/database/db_thread)                    |                                                  |
 | [tokens](help/database/db_tokens)                    | OAuth usage                                      |
-| [unique_contacts](help/database/db_unique_contacts)  |                                                  |
 | [user](help/database/db_user)                        | local user table                                 |
 | [userd](help/database/db_userd)                      |                                                  |
 | [workerqueue](help/database/db_workerqueue)          |                                                  |
index 8e9de43c96fb99827970be4c99a810fd9f796627..c47e72622816f760c7e3f07f83b94456fb15f276 100644 (file)
@@ -18,9 +18,14 @@ Table gcontact
 | about        |                                    | text             | NO   |     | NULL                |                |
 | keywords     | puplic keywords (interests)        | text             | NO   |     | NULL                |                |
 | gender       |                                    | varchar(32)      | NO   |     |                     |                |
+| birthday     |                                    | varchar(32)      | NO   |     | 0000-00-00          |                |
 | community    | 1 if contact is forum account      | tinyint(1)       | NO   |     | 0                   |                |
+| hide         | 1 = should be hidden from search   | tinyint(1)       | NO   |     | 0                   |                |
+| nsfw         | 1 = contact posts nsfw content     | tinyint(1)       | NO   |     | 0                   |                |
 | network      | social network protocol            | varchar(255)     | NO   |     |                     |                |
 | addr         |                                    | varchar(255)     | NO   |     |                     |                |
+| notify       |                                    | text             | NO   |     |                     |                |
+| alias        |                                    | varchar(255)     | NO   |     |                     |                |
 | generation   |                                    | tinyint(3)       | NO   |     | 0                   |                |
 | server_url   | baseurl of the contacts server     | varchar(255)     | NO   |     |                     |                |
 
diff --git a/doc/database/db_guid.md b/doc/database/db_guid.md
deleted file mode 100644 (file)
index f607597..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-Table guid
-==========
-
-| Field   | Description      | Type             | Null | Key | Default | Extra          |
-|---------|------------------|------------------|------|-----|---------|----------------|
-| id      | sequential ID    | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
-| guid    |                  | varchar(255)     | NO   | MUL |         |                |
-| plink   |                  | varchar(255)     | NO   | MUL |         |                |
-| uri     |                  | varchar(255)     | NO   | MUL |         |                |
-| network |                  | varchar(32)      | NO   |     |         |                |
-
-Return to [database documentation](help/database)
index 3ed986a78151eaa8c51aae4f1e07819b16ce3a43..00fa213c8999778284a15ce17b15e5c355826ef7 100644 (file)
@@ -8,6 +8,7 @@ Table item
 | uri           |                                                                                                                                                            | varchar(255)        | NO   | MUL |                     |                |
 | uid           | user.id which owns this copy of the item                                                                                                                   | int(10) unsigned    | NO   | MUL | 0                   |                |
 | contact-id    | contact.id                                                                                                                                                 | int(11)             | NO   | MUL | 0                   |                |
+| gcontact-id   | ID of the global contact                                                                                                                                   | int(11)             | NO   | MUL | 0                   |                |
 | type          |                                                                                                                                                            | varchar(255)        | NO   |     |                     |                |
 | wall          | This item was posted to the wall of uid                                                                                                                    | tinyint(1)          | NO   | MUL | 0                   |                |
 | gravity       |                                                                                                                                                            | tinyint(1)          | NO   |     | 0                   |                |
diff --git a/doc/database/db_oembed.md b/doc/database/db_oembed.md
new file mode 100644 (file)
index 0000000..5e994ec
--- /dev/null
@@ -0,0 +1,10 @@
+Table oembed
+============
+
+| Field        | Description                        | Type         | Null | Key | Default             | Extra |
+| ------------ | ---------------------------------- | ------------ | ---- | --- | ------------------- | ----- |
+| url          | page url                           | varchar(255) | NO   | PRI | NULL                |       |
+| content      | OEmbed data of the page            | text         | NO   |     | NULL                |       |
+| created      | datetime of creation               | datetime     | NO   | MUL | 0000-00-00 00:00:00 |       |
+
+Return to [database documentation](help/database)
diff --git a/doc/database/db_parsed_url.md b/doc/database/db_parsed_url.md
new file mode 100644 (file)
index 0000000..ada42c2
--- /dev/null
@@ -0,0 +1,12 @@
+Table parsed_url
+================
+
+| Field        | Description                        | Type         | Null | Key | Default             | Extra |
+| ------------ | ---------------------------------- | ------------ | ---- | --- | ------------------- | ----- |
+| url          | page url                           | varchar(255) | NO   | PRI | NULL                |       |
+| guessing     | is the "guessing" mode active?     | tinyint(1)   | NO   | PRI | 0                   |       |
+| oembed       | is the data the result of oembed?  | tinyint(1)   | NO   | PRI | 0                   |       |
+| content      | page data                          | text         | NO   |     | NULL                |       |
+| created      | datetime of creation               | datetime     | NO   | MUL | 0000-00-00 00:00:00 |       |
+
+Return to [database documentation](help/database)
index 78af5cb37aa9d8c089e8d86cc413fd00a3b4361f..4c912bec101f6ddaa858c80721757dcc8bde58e5 100644 (file)
@@ -1,30 +1,31 @@
 Table thread
 ============
 
-| Field      | Description      | Type             | Null | Key | Default             | Extra |
-|------------|------------------|------------------|------|-----|---------------------|-------|
-| iid        | sequential ID    | int(10) unsigned | NO   | PRI | 0                   |       |
-| uid        |                  | int(10) unsigned | NO   | MUL | 0                   |       |
-| contact-id |                  | int(11) unsigned | NO   |     | 0                   |       |
-| created    |                  | datetime         | NO   | MUL | 0000-00-00 00:00:00 |       |
-| edited     |                  | datetime         | NO   |     | 0000-00-00 00:00:00 |       |
-| commented  |                  | datetime         | NO   | MUL | 0000-00-00 00:00:00 |       |
-| received   |                  | datetime         | NO   |     | 0000-00-00 00:00:00 |       |
-| changed    |                  | datetime         | NO   |     | 0000-00-00 00:00:00 |       |
-| wall       |                  | tinyint(1)       | NO   | MUL | 0                   |       |
-| private    |                  | tinyint(1)       | NO   |     | 0                   |       |
-| pubmail    |                  | tinyint(1)       | NO   |     | 0                   |       |
-| moderated  |                  | tinyint(1)       | NO   |     | 0                   |       |
-| visible    |                  | tinyint(1)       | NO   |     | 0                   |       |
-| spam       |                  | tinyint(1)       | NO   |     | 0                   |       |
-| starred    |                  | tinyint(1)       | NO   |     | 0                   |       |
-| ignored    |                  | tinyint(1)       | NO   |     | 0                   |       |
-| bookmark   |                  | tinyint(1)       | NO   |     | 0                   |       |
-| unseen     |                  | tinyint(1)       | NO   |     | 1                   |       |
-| deleted    |                  | tinyint(1)       | NO   |     | 0                   |       |
-| origin     |                  | tinyint(1)       | NO   |     | 0                   |       |
-| forum_mode |                  | tinyint(1)       | NO   |     | 0                   |       |
-| mention    |                  | tinyint(1)       | NO   |     | 0                   |       |
-| network    |                  | varchar(32)      | NO   |     |                     |       |
+| Field       | Description      | Type             | Null | Key | Default             | Extra |
+|-------------|------------------|------------------|------|-----|---------------------|-------|
+| iid         | sequential ID    | int(10) unsigned | NO   | PRI | 0                   |       |
+| uid         |                  | int(10) unsigned | NO   | MUL | 0                   |       |
+| contact-id  |                  | int(11) unsigned | NO   |     | 0                   |       |
+| gcontact-id | Global Contact   | int(11) unsigned | NO   |     | 0                   |       |
+| created     |                  | datetime         | NO   | MUL | 0000-00-00 00:00:00 |       |
+| edited      |                  | datetime         | NO   |     | 0000-00-00 00:00:00 |       |
+| commented   |                  | datetime         | NO   | MUL | 0000-00-00 00:00:00 |       |
+| received    |                  | datetime         | NO   |     | 0000-00-00 00:00:00 |       |
+| changed     |                  | datetime         | NO   |     | 0000-00-00 00:00:00 |       |
+| wall        |                  | tinyint(1)       | NO   | MUL | 0                   |       |
+| private     |                  | tinyint(1)       | NO   |     | 0                   |       |
+| pubmail     |                  | tinyint(1)       | NO   |     | 0                   |       |
+| moderated   |                  | tinyint(1)       | NO   |     | 0                   |       |
+| visible     |                  | tinyint(1)       | NO   |     | 0                   |       |
+| spam        |                  | tinyint(1)       | NO   |     | 0                   |       |
+| starred     |                  | tinyint(1)       | NO   |     | 0                   |       |
+| ignored     |                  | tinyint(1)       | NO   |     | 0                   |       |
+| bookmark    |                  | tinyint(1)       | NO   |     | 0                   |       |
+| unseen      |                  | tinyint(1)       | NO   |     | 1                   |       |
+| deleted     |                  | tinyint(1)       | NO   |     | 0                   |       |
+| origin      |                  | tinyint(1)       | NO   |     | 0                   |       |
+| forum_mode  |                  | tinyint(1)       | NO   |     | 0                   |       |
+| mention     |                  | tinyint(1)       | NO   |     | 0                   |       |
+| network     |                  | varchar(32)      | NO   |     |                     |       |
 
 Return to [database documentation](help/database)
diff --git a/doc/database/db_unique_contacts.md b/doc/database/db_unique_contacts.md
deleted file mode 100644 (file)
index 491da6a..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-Table unique_contacts
-=====================
-
-| Field    | Description      | Type         | Null | Key | Default | Extra          |
-|----------|------------------|--------------|------|-----|---------|----------------|
-| id       | sequential ID    | int(11)      | NO   | PRI | NULL    | auto_increment |
-| url      |                  | varchar(255) | NO   | MUL |         |                |
-| nick     |                  | varchar(255) | NO   |     |         |                |
-| name     |                  | varchar(255) | NO   |     |         |                |
-| avatar   |                  | varchar(255) | NO   |     |         |                |
-| location |                  | varchar(255) | NO   |     |         |                |
-| about    |                  | text         | NO   |     | NULL    |                |
-
-Return to [database documentation](help/database)
index dcff41a4b64968f6753bc29dbaab3210f0b4c425..40be4a069596e8c2f1da1fcac70883f9518f9f8d 100644 (file)
@@ -1,27 +1,28 @@
-**Friendica Addon/Plugin-Entwicklung**
+Friendica Addon/Plugin-Entwicklung
 ==============
 
 * [Zur Startseite der Hilfe](help)
 
-Bitte schau dir das Beispiel-Addon "randplace" für ein funktionierendes Beispiel für manche der hier aufgeführten Funktionen an. 
-Das Facebook-Addon bietet ein Beispiel dafür, die "addon"- und "module"-Funktion gemeinsam zu integrieren. 
-Addons arbeiten, indem sie Event Hooks abfangen. Module arbeiten, indem bestimmte Seitenanfragen (durch den URL-Pfad) abgefangen werden 
+Bitte schau dir das Beispiel-Addon "randplace" für ein funktionierendes Beispiel für manche der hier aufgeführten Funktionen an.
+Das Facebook-Addon bietet ein Beispiel dafür, die "addon"- und "module"-Funktion gemeinsam zu integrieren.
+Addons arbeiten, indem sie Event Hooks abfangen.
+Module arbeiten, indem bestimmte Seitenanfragen (durch den URL-Pfad) abgefangen werden.
 
-Plugin-Namen können keine Leerstellen oder andere Interpunktionen enthalten und werden als Datei- und Funktionsnamen genutzt. 
-Du kannst einen lesbaren Namen im Kommentarblock eintragen. 
-Jedes Addon muss beides beinhalten - eine Installations- und eine Deinstallationsfunktion, die auf dem Addon-/Plugin-Namen basieren; z.B. "plugin1name_install()". 
-Diese beiden Funktionen haben keine Argumente und sind dafür verantwortlich, Event Hooks zu registrieren und abzumelden (unregistering), die dein Plugin benötigt. 
-Die Installations- und Deinstallationsfunktionfunktionen werden auch ausgeführt (z.B. neu installiert), wenn sich das Plugin nach der Installation ändert - somit sollte deine Deinstallationsfunktion keine Daten zerstört und deine Installationsfunktion sollte bestehende Daten berücksichtigen. 
+Plugin-Namen können keine Leerstellen oder andere Interpunktionen enthalten und werden als Datei- und Funktionsnamen genutzt.
+Du kannst einen lesbaren Namen im Kommentarblock eintragen.
+Jedes Addon muss beides beinhalten - eine Installations- und eine Deinstallationsfunktion, die auf dem Addon-/Plugin-Namen basieren; z.B. "plugin1name_install()".
+Diese beiden Funktionen haben keine Argumente und sind dafür verantwortlich, Event Hooks zu registrieren und abzumelden (unregistering), die dein Plugin benötigt.
+Die Installations- und Deinstallationsfunktionfunktionen werden auch ausgeführt (z.B. neu installiert), wenn sich das Plugin nach der Installation ändert - somit sollte deine Deinstallationsfunktion keine Daten zerstört und deine Installationsfunktion sollte bestehende Daten berücksichtigen.
 Zukünftige Extensions werden möglicherweise "Setup" und "Entfernen" anbieten.
 
 Plugins sollten einen Kommentarblock mit den folgenden vier Parametern enthalten:
 
-       /*
-       * Name: My Great Plugin 
-       * Description: This is what my plugin does. It's really cool
-       * Version: 1.0
-       * Author: John Q. Public <john@myfriendicasite.com>
-       */
+    /*
+     * Name: My Great Plugin
+     * Description: This is what my plugin does. It's really cool.
+     * Version: 1.0
+     * Author: John Q. Public <john@myfriendicasite.com>
+     */
 
 Registriere deine Plugin-Hooks während der Installation.
 
@@ -29,45 +30,50 @@ Registriere deine Plugin-Hooks während der Installation.
 
 $hookname ist ein String und entspricht einem bekannten Friendica-Hook.
 
-$file steht für den Pfadnamen, der relativ zum Top-Level-Friendicaverzeichnis liegt. 
+$file steht für den Pfadnamen, der relativ zum Top-Level-Friendicaverzeichnis liegt.
 Das *sollte* "addon/plugin_name/plugin_name.php' sein.
 
 $function ist ein String und der Name der Funktion, die ausgeführt wird, wenn der Hook aufgerufen wird.
 
+Argumente
+---
+
 Deine Hook-Callback-Funktion wird mit mindestens einem und bis zu zwei Argumenten aufgerufen
 
     function myhook_function(&$a, &$b) {
 
     }
 
-Wenn du Änderungen an den aufgerufenen Daten vornehmen willst, musst du diese als Referenzvariable (mit "&") während der Funktionsdeklaration deklarieren. 
+Wenn du Änderungen an den aufgerufenen Daten vornehmen willst, musst du diese als Referenzvariable (mit "&") während der Funktionsdeklaration deklarieren.
 
-$a ist die Friendica "App"-Klasse, die eine Menge an Informationen über den aktuellen Friendica-Status beinhaltet, u.a. welche Module genutzt werden, Konfigurationsinformationen, Inhalte der Seite zum Zeitpunkt des Hook-Aufrufs. 
-Es ist empfohlen, diese Funktion "$a" zu nennen, um seine Nutzung an den Gebrauch an anderer Stelle anzugleichen. 
+$a ist die Friendica "App"-Klasse, die eine Menge an Informationen über den aktuellen Friendica-Status beinhaltet, u.a. welche Module genutzt werden, Konfigurationsinformationen, Inhalte der Seite zum Zeitpunkt des Hook-Aufrufs.
+Es ist empfohlen, diese Funktion "$a" zu nennen, um seine Nutzung an den Gebrauch an anderer Stelle anzugleichen.
 
-$b kann frei benannt werden. 
-Diese Information ist speziell auf den Hook bezogen, der aktuell bearbeitet wird, und beinhaltet normalerweise Daten, die du sofort nutzen, anzeigen oder bearbeiten kannst. 
-Achte darauf, diese mit "&" zu deklarieren, wenn du sie bearbeiten willst. 
+$b kann frei benannt werden.
+Diese Information ist speziell auf den Hook bezogen, der aktuell bearbeitet wird, und beinhaltet normalerweise Daten, die du sofort nutzen, anzeigen oder bearbeiten kannst.
+Achte darauf, diese mit "&" zu deklarieren, wenn du sie bearbeiten willst.
 
 
-**Module**
+Module
+---
 
-Plugins/Addons können auch als "Module" agieren und alle Seitenanfragen für eine bestimte URL abfangen. 
-Um ein Plugin als Modul zu nutzen, ist es nötig, die Funktion "plugin_name_module()" zu definieren, die keine Argumente benötigt und nichts weiter machen muss. 
+Plugins/Addons können auch als "Module" agieren und alle Seitenanfragen für eine bestimte URL abfangen.
+Um ein Plugin als Modul zu nutzen, ist es nötig, die Funktion "plugin_name_module()" zu definieren, die keine Argumente benötigt und nichts weiter machen muss.
 
-Wenn diese Funktion existiert, wirst du nun alle Seitenanfragen für "http://my.web.site/plugin_name" erhalten - mit allen URL-Komponenten als zusätzliche Argumente. 
-Diese werden in ein Array $a->argv geparst und stimmen mit $a->argc überein, wobei sie die Anzahl der URL-Komponenten abbilden. 
-So würde http://my.web.site/plugin/arg1/arg2 nach einem Modul "plugin" suchen und seiner Modulfunktion die $a-App-Strukur übergeben (dies ist für viele Komponenten verfügbar). Das umfasst:
+Wenn diese Funktion existiert, wirst du nun alle Seitenanfragen für "http://example.com/plugin_name" erhalten - mit allen URL-Komponenten als zusätzliche Argumente.
+Diese werden in ein Array $a->argv geparst und stimmen mit $a->argc überein, wobei sie die Anzahl der URL-Komponenten abbilden.
+So würde http://example.com/plugin/arg1/arg2 nach einem Modul "plugin" suchen und seiner Modulfunktion die $a-App-Strukur übergeben (dies ist für viele Komponenten verfügbar). Das umfasst:
 
-     $a->argc = 3
-     $a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2');
+    $a->argc = 3
+    $a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2');
 
-Deine Modulfunktionen umfassen oft die Funktion plugin_name_content(&$a), welche den Seiteninhalt definiert und zurückgibt. 
-Sie können auch plugin_name_post(&$a) umfassen, welches vor der content-Funktion aufgerufen wird und normalerweise die Resultate der POST-Formulare handhabt. 
+Deine Modulfunktionen umfassen oft die Funktion plugin_name_content(&$a), welche den Seiteninhalt definiert und zurückgibt.
+Sie können auch plugin_name_post(&$a) umfassen, welches vor der content-Funktion aufgerufen wird und normalerweise die Resultate der POST-Formulare handhabt.
 Du kannst ebenso plugin_name_init(&$a) nutzen, was oft frühzeitig aufgerufen wird und das Modul initialisert.
 
 
-**Derzeitige Hooks:**
+Derzeitige Hooks
+---
 
 **'authenticate'** - wird aufgerufen, wenn sich der User einloggt.
     $b ist ein Array
@@ -180,6 +186,9 @@ Du kannst ebenso plugin_name_init(&$a) nutzen, was oft frühzeitig aufgerufen wi
  - wird aufgerufen nachdem in include/nav,php der Inhalt des Navigations Menüs erzeugt wurde.
  - $b ist ein Array, das $nav wiederspiegelt.
 
+Komplette Liste der Hook-Callbacks
+---
+
 Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 14-Feb-2012 generiert): Bitte schau in die Quellcodes für Details zu Hooks, die oben nicht dokumentiert sind.
 
 boot.php:                      call_hooks('login_hook',$o);
@@ -204,7 +213,7 @@ include/text.php:           call_hooks('contact_block_end', $arr);
 
 include/text.php:              call_hooks('smilie', $s);
 
-include/text.php:              call_hooks('prepare_body_init', $item); 
+include/text.php:              call_hooks('prepare_body_init', $item);
 
 include/text.php:              call_hooks('prepare_body', $prep_arr);
 
@@ -359,4 +368,3 @@ mod/cb.php:                 call_hooks('cb_afterpost');
 mod/cb.php:                    call_hooks('cb_content', $o);
 
 mod/directory.php:             call_hooks('directory_item', $arr);
-
index 4764c287c8977c9d2efbbfcbcfb6a2afccf1f92b..f9c92bfa084efafd475958af90c4a169aa86dd80 100644 (file)
@@ -34,6 +34,7 @@ line to your .htconfig.php:
 * like_no_comment (Boolean) - Don't update the "commented" value of an item when it is liked.
 * local_block (Boolean) - Used in conjunction with "block_public".
 * local_search (Boolean) - Blocks the search for not logged in users to prevent crawlers from blocking your system.
+* max_connections - The poller process isn't started when 3/4 of the possible database connections are used. When the system can't detect the maximum numbers of connection then this value can be used.
 * max_contact_queue - Default value is 500.
 * max_batch_queue - Default value is 1000.
 * no_oembed (Boolean) - Don't use OEmbed to fetch more information about a link.
diff --git a/doc/smarty3-templates.md b/doc/smarty3-templates.md
new file mode 100644 (file)
index 0000000..751ef20
--- /dev/null
@@ -0,0 +1,173 @@
+Friendica Templating Documentation
+==================================
+
+* [Home](help)
+
+Friendica uses [Smarty 3](http://www.smarty.net/) as PHP templating engine. The main templates are found in
+
+               /view/templates
+
+theme authors may overwrite the default templates by putting a files with the same name into the
+
+               /view/themes/$themename/templates
+
+directory.
+
+Templates that are only used by addons shall be placed in the
+
+               /addon/$addonname/templates
+
+directory.
+
+To render a template use the function *get_markup_template* to load the template and *replace_macros* to replace the macros/variables in the just loaded template file.
+
+               $tpl = get_markup_template('install_settings.tpl');
+        $o .= replace_macros($tpl, array( ... ));
+
+the array consists of an association of an identifier and the value for that identifier, i.e.
+
+               '$title' => $install_title,
+
+where the value may as well be an array by its own.
+
+Form Templates
+--------------
+
+To guarantee a consistent look and feel for input forms, i.e. in the settings sections, there are templates for the basic form fields. They are initialized with an array of data, depending on the tyle of the field.
+
+All of these take an array for holding the values, i.e. for an one line text input field, which is required and should be used to type email addesses use something along
+
+               '$adminmail' => array('adminmail', t('Site administrator email address'), $adminmail, t('Your account email address must match this in order to use the web admin panel.'), 'required', '', 'email'),
+
+To evaluate the input value, you can then use the $_POST array, more precisely the $_POST['adminemail'] variable.
+
+Listed below are the template file names, the general purpose of the template and their field parameters.
+
+### field_checkbox.tpl
+
+A checkbox. If the checkbox is checked its value is **1**. Field parameter:
+
+0. Name of the checkbox,
+1. Label for the checkbox,
+2. State checked? if true then the checkbox will be marked as checked,
+3. Help text for the checkbox.
+
+### field_combobox.tpl
+
+A combobox, combining a pull down selection and a textual input field. Field parameter:
+
+0. Name of the combobox,
+1. Label for the combobox,
+2. Current value of the variable,
+3. Help text for the combobox,
+4. Array holding the possible values for the textual input,
+5. Array holding the possible values for the pull down selection.
+
+### field_custom.tpl
+
+A customizeable template to include a custom element in the form with the usual surroundings, Field parameter:
+
+0. Name of the field,
+1. Label for the field,
+2. the field,
+3. Help text for the field.
+
+### field_input.tpl
+
+A single line input field for textual input. Field parameter:
+
+0. Name of the field,
+1. Label for the input box,
+2. Current value of the variable,
+3. Help text for the input box,
+4. if set to "required" modern browser will check that this input box is filled when submitting the form,
+5. if set to "autofocus" modern browser will put the cursur into this box once the page is loaded,
+6. if set to "email" or "url" modern browser will check that the filled in value corresponds to an email address or URL.
+
+### field_intcheckbox.tpl
+
+A checkbox (see above) but you can define the value of it. Field parameter:
+
+0. Name of the checkbox,
+1. Label for the checkbox,
+2. State checked? if true then the checkbox will be marked as checked,
+3. Value of the checkbox,
+4. Help text for the checkbox.
+
+### field_openid.tpl
+
+An input box (see above) but prepared for special CSS styling for openID input. Field parameter:
+
+0. Name of the field,
+1. Label for the input box,
+2. Current value of the variable,
+3. Help text for the input field.
+
+### field_password.tpl
+
+A single line input field (see above) for textual input. The characters typed in will not be shown by the browser. Field parameter:
+
+0. Name of the field,
+1. Label for the field,
+2. Value for the field, e.g. the old password,
+3. Help text for the input field,
+4. if set to "required" modern browser will check that this field is filled out,
+5. if set to "autofocus" modern browser will put the cursor automatically into this input field.
+
+### field_radio.tpl
+
+A radio button. Field parameter:
+
+0. Name of the radio button,
+1. Label for the radio button,
+2. Current value of the variable,
+3. Help text for the button,
+4. if set, the radio button will be checked.
+
+### field_richtext.tpl
+
+A multi-line input field for *rich* textual content. Field parameter:
+
+0. Name of the input field,
+1. Label for the input box,
+2. Current text for the box,
+3. Help text for the input box.
+
+### field_select.tpl
+
+A drop down selection box. Field parameter:
+
+0. Name of the field,
+1. Label of the selection box,
+2. Current selected value,
+3. Help text for the selection box,
+4. Array holding the possible values of the selection drop down.
+
+### field_select_raw.tpl
+
+A drop down selection box (see above) but you have to prepare the values yourself. Field parameter:
+
+0. Name of the field,
+1. Label of the selection box,
+2. Current selected value,
+3. Help text for the selection box,
+4. Possible values of the selection drop down.
+
+### field_textarea.tpl
+
+A multi-line input field for (plain) textual content. Field parameter:
+
+0. Name of the input field,
+1. Label for the input box,
+2. Current text for the box,
+3. Help text for the input box.
+
+### field_yesno.tpl
+
+A button that has two states *yes* or *no*. Field parameter:
+
+0. Name of the input field,
+1. Label for the button,
+2. Current value,
+3. Help text for the button
+4. if set to an array of two values, these two will be used, otherwise "off" and "on".
diff --git a/doc/snarty3-templates.md b/doc/snarty3-templates.md
deleted file mode 100644 (file)
index 751ef20..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-Friendica Templating Documentation
-==================================
-
-* [Home](help)
-
-Friendica uses [Smarty 3](http://www.smarty.net/) as PHP templating engine. The main templates are found in
-
-               /view/templates
-
-theme authors may overwrite the default templates by putting a files with the same name into the
-
-               /view/themes/$themename/templates
-
-directory.
-
-Templates that are only used by addons shall be placed in the
-
-               /addon/$addonname/templates
-
-directory.
-
-To render a template use the function *get_markup_template* to load the template and *replace_macros* to replace the macros/variables in the just loaded template file.
-
-               $tpl = get_markup_template('install_settings.tpl');
-        $o .= replace_macros($tpl, array( ... ));
-
-the array consists of an association of an identifier and the value for that identifier, i.e.
-
-               '$title' => $install_title,
-
-where the value may as well be an array by its own.
-
-Form Templates
---------------
-
-To guarantee a consistent look and feel for input forms, i.e. in the settings sections, there are templates for the basic form fields. They are initialized with an array of data, depending on the tyle of the field.
-
-All of these take an array for holding the values, i.e. for an one line text input field, which is required and should be used to type email addesses use something along
-
-               '$adminmail' => array('adminmail', t('Site administrator email address'), $adminmail, t('Your account email address must match this in order to use the web admin panel.'), 'required', '', 'email'),
-
-To evaluate the input value, you can then use the $_POST array, more precisely the $_POST['adminemail'] variable.
-
-Listed below are the template file names, the general purpose of the template and their field parameters.
-
-### field_checkbox.tpl
-
-A checkbox. If the checkbox is checked its value is **1**. Field parameter:
-
-0. Name of the checkbox,
-1. Label for the checkbox,
-2. State checked? if true then the checkbox will be marked as checked,
-3. Help text for the checkbox.
-
-### field_combobox.tpl
-
-A combobox, combining a pull down selection and a textual input field. Field parameter:
-
-0. Name of the combobox,
-1. Label for the combobox,
-2. Current value of the variable,
-3. Help text for the combobox,
-4. Array holding the possible values for the textual input,
-5. Array holding the possible values for the pull down selection.
-
-### field_custom.tpl
-
-A customizeable template to include a custom element in the form with the usual surroundings, Field parameter:
-
-0. Name of the field,
-1. Label for the field,
-2. the field,
-3. Help text for the field.
-
-### field_input.tpl
-
-A single line input field for textual input. Field parameter:
-
-0. Name of the field,
-1. Label for the input box,
-2. Current value of the variable,
-3. Help text for the input box,
-4. if set to "required" modern browser will check that this input box is filled when submitting the form,
-5. if set to "autofocus" modern browser will put the cursur into this box once the page is loaded,
-6. if set to "email" or "url" modern browser will check that the filled in value corresponds to an email address or URL.
-
-### field_intcheckbox.tpl
-
-A checkbox (see above) but you can define the value of it. Field parameter:
-
-0. Name of the checkbox,
-1. Label for the checkbox,
-2. State checked? if true then the checkbox will be marked as checked,
-3. Value of the checkbox,
-4. Help text for the checkbox.
-
-### field_openid.tpl
-
-An input box (see above) but prepared for special CSS styling for openID input. Field parameter:
-
-0. Name of the field,
-1. Label for the input box,
-2. Current value of the variable,
-3. Help text for the input field.
-
-### field_password.tpl
-
-A single line input field (see above) for textual input. The characters typed in will not be shown by the browser. Field parameter:
-
-0. Name of the field,
-1. Label for the field,
-2. Value for the field, e.g. the old password,
-3. Help text for the input field,
-4. if set to "required" modern browser will check that this field is filled out,
-5. if set to "autofocus" modern browser will put the cursor automatically into this input field.
-
-### field_radio.tpl
-
-A radio button. Field parameter:
-
-0. Name of the radio button,
-1. Label for the radio button,
-2. Current value of the variable,
-3. Help text for the button,
-4. if set, the radio button will be checked.
-
-### field_richtext.tpl
-
-A multi-line input field for *rich* textual content. Field parameter:
-
-0. Name of the input field,
-1. Label for the input box,
-2. Current text for the box,
-3. Help text for the input box.
-
-### field_select.tpl
-
-A drop down selection box. Field parameter:
-
-0. Name of the field,
-1. Label of the selection box,
-2. Current selected value,
-3. Help text for the selection box,
-4. Array holding the possible values of the selection drop down.
-
-### field_select_raw.tpl
-
-A drop down selection box (see above) but you have to prepare the values yourself. Field parameter:
-
-0. Name of the field,
-1. Label of the selection box,
-2. Current selected value,
-3. Help text for the selection box,
-4. Possible values of the selection drop down.
-
-### field_textarea.tpl
-
-A multi-line input field for (plain) textual content. Field parameter:
-
-0. Name of the input field,
-1. Label for the input box,
-2. Current text for the box,
-3. Help text for the input box.
-
-### field_yesno.tpl
-
-A button that has two states *yes* or *no*. Field parameter:
-
-0. Name of the input field,
-1. Label for the button,
-2. Current value,
-3. Help text for the button
-4. if set to an array of two values, these two will be used, otherwise "off" and "on".
index b874e9ea2e8a84b717fe590cb9fe29ec081840dd..728a56ab305b07862615813fc9748920553b28b5 100644 (file)
@@ -4,42 +4,32 @@ Friendica translations
 Translation Process
 -------------------
 
-The strings used in the UI of Friendica is translated at [Transifex] [1] and then
-included in the git repository at github. If you want to help with translation
-for any language, be it correcting terms or translating friendica to a
-currently not supported language, please register an account at transifex.com
-and contact the friendica translation team there.
-
-Translating friendica is simple. Just use the online tool at transifex. If you
-don't want to deal with git & co. that is fine, we check the status of the
-translations regularly and import them into the source tree at github so that
-others can use them.
-
-We do not include every translation from transifex in the source tree to avoid
-a scattered and disturbed overall experience. As an uneducated guess we have a
-lower limit of 50% translated strings before we include the language (for the
-core message.po file, addont translation will be included once all strings of
-an addon are translated. This limit is judging only by the amount of translated
-strings under the assumption that the most prominent strings for the UI will be
-translated first by a translation team. If you feel your translation useable
-before this limit, please contact us and we will probably include your teams
-work in the source tree.
-
-If you want to get your work into the source tree yourself, feel free to do so
-and contact us with and question that arises. The process is simple and
-friendica ships with all the tools necessary.
+The strings used in the UI of Friendica is translated at [Transifex] [1] and then included in the git repository at github.
+If you want to help with translation for any language, be it correcting terms or translating friendica to a currently not supported language, please register an account at transifex.com and contact the friendica translation team there.
+
+Translating friendica is simple.
+Just use the online tool at transifex.
+If you don't want to deal with git & co. that is fine, we check the status of the translations regularly and import them into the source tree at github so that others can use them.
+
+We do not include every translation from transifex in the source tree to avoid a scattered and disturbed overall experience.
+As an uneducated guess we have a lower limit of 50% translated strings before we include the language (for the core messages.po file, addont translation will be included once all strings of an addon are translated.
+This limit is judging only by the amount of translated strings under the assumption that the most prominent strings for the UI will be translated first by a translation team.
+If you feel your translation useable before this limit, please contact us and we will probably include your teams work in the source tree.
+
+If you want to help translating, please concentrate on the core messages.po file first.
+We will only include translations with a sufficient translated messages.po file.
+Translations of addons will only be included, when the core file is included as well.
+
+If you want to get your work into the source tree yourself, feel free to do so and contact us with and question that arises.
+The process is simple and friendica ships with all the tools necessary.
 
 The location of the translated files in the source tree is
     /view/LNG-CODE/
 where LNG-CODE is the language code used, e.g. de for German or fr for French.
-For the email templates (the *.tpl files) just place them into the directory
-and you are done. The translated strings come as a "message.po" file from
-transifex which needs to be translated into the PHP file friendica uses.  To do
-so, place the file in the directory mentioned above and use the "po2php"
-utility from the util directory of your friendica installation.
+The translated strings come as a "message.po" file from transifex which needs to be translated into the PHP file friendica uses.
+To do so, place the file in the directory mentioned above and use the "po2php" utility from the util directory of your friendica installation.
 
-Assuming you want to convert the German localization which is placed in
-view/de/message.po you would do the following.
+Assuming you want to convert the German localization which is placed in view/de/message.po you would do the following.
 
     1. Navigate at the command prompt to the base directory of your
        friendica installation
@@ -47,15 +37,15 @@ view/de/message.po you would do the following.
     2. Execute the po2php script, which will place the translation
        in the strings.php file that is used by friendica.
 
-       $> php util/po2php.php view/de/message.po
+       $> php util/po2php.php view/de/messages.po
 
        The output of the script will be placed at view/de/strings.php where
-       froemdoca os expecting it, so you can test your translation mmediately.
-                                  
+       friendica is expecting it, so you can test your translation immediately.
+
     3. Visit your friendica page to check if it still works in the language you
        just translated. If not try to find the error, most likely PHP will give
        you a hint in the log/warnings.about the error.
-                                        
+
        For debugging you can also try to "run" the file with PHP. This should
        not give any output if the file is ok but might give a hint for
        searching the bug in the file.
@@ -69,27 +59,10 @@ view/de/message.po you would do the following.
 Utilities
 ---------
 
-Additional to the po2php script there are some more utilities for translation
-in the "util" directory of the friendica source tree.  If you only want to
-translate friendica into another language you wont need any of these tools most
-likely but it gives you an idea how the translation process of friendica
-works.
+Additional to the po2php script there are some more utilities for translation in the "util" directory of the friendica source tree.
+If you only want to translate friendica into another language you wont need any of these tools most likely but it gives you an idea how the translation process of friendica works.
 
 For further information see the utils/README file.
 
-Known Problems
---------------
-
-Friendica uses the language setting of the visitors browser to determain the
-language for the UI. Most of the time this works, but there are some known
-quirks.
-
-One is that some browsers, like Safari, do the setting to "de-de" but friendica
-only has a "de" localisation.  A workaround would be to add a symbolic link
-from
-    $friendica/view/de-de
-pointing to
-    $friendica/view/de
-
 [1]:   https://www.transifex.com/projects/p/friendica/
 
index c39a0576514c2524ce778d6dd79ebac855a1693e..45080e44d0fd9a3f92e4d53417184634a7a1a957 100644 (file)
@@ -1657,35 +1657,6 @@ LOCK TABLES `tokens` WRITE;
 /*!40000 ALTER TABLE `tokens` ENABLE KEYS */;
 UNLOCK TABLES;
 
---
--- Table structure for table `unique_contacts`
---
-
-DROP TABLE IF EXISTS `unique_contacts`;
-/*!40101 SET @saved_cs_client     = @@character_set_client */;
-/*!40101 SET character_set_client = utf8 */;
-CREATE TABLE `unique_contacts` (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `url` varchar(255) NOT NULL DEFAULT '',
-  `nick` varchar(255) NOT NULL DEFAULT '',
-  `name` varchar(255) NOT NULL DEFAULT '',
-  `avatar` varchar(255) NOT NULL DEFAULT '',
-  `location` varchar(255) NOT NULL DEFAULT '',
-  `about` text NOT NULL,
-  PRIMARY KEY (`id`),
-  KEY `url` (`url`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-/*!40101 SET character_set_client = @saved_cs_client */;
-
---
--- Dumping data for table `unique_contacts`
---
-
-LOCK TABLES `unique_contacts` WRITE;
-/*!40000 ALTER TABLE `unique_contacts` DISABLE KEYS */;
-/*!40000 ALTER TABLE `unique_contacts` ENABLE KEYS */;
-UNLOCK TABLES;
-
 --
 -- Table structure for table `user`
 --
index 508de9a3234932503cd6a6ea0b0e74937d1bb9d5..fe6c0d82ee4562275a88beb8b301870b5c0b4543 100644 (file)
@@ -57,7 +57,7 @@ $a->config['system']['huburl'] = '[internal]';
 
 // allowed themes (change this from admin panel after installation)
 
-$a->config['system']['allowed_themes'] = 'dispy,quattro,vier,darkzero,duepuntozero,greenzero,purplezero,slackr,diabook';
+$a->config['system']['allowed_themes'] = 'quattro,vier,duepuntozero';
 
 // default system theme
 
index a3cbbfed15265c765a8bd6278e1f6c160e4f76fe..3799e0b189a98830aec305d5da37d2122f7310f8 100644 (file)
@@ -132,8 +132,8 @@ function terminate_friendship($user,$self,$contact) {
                diaspora_unshare($user,$contact);
        }
        elseif($contact['network'] === NETWORK_DFRN) {
-               require_once('include/items.php');
-               dfrn_deliver($user,$contact,'placeholder', 1);
+               require_once('include/dfrn.php');
+               dfrn::deliver($user,$contact,'placeholder', 1);
        }
 
 }
@@ -205,60 +205,49 @@ function get_contact_details_by_url($url, $uid = -1) {
                if ((($profile["addr"] == "") OR ($profile["name"] == "")) AND
                        in_array($profile["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS)))
                        proc_run('php',"include/update_gcontact.php", $profile["gid"]);
-
-       } else {
-               $r = q("SELECT `url`, `name`, `nick`, `avatar` AS `photo`, `location`, `about` FROM `unique_contacts` WHERE `url` = '%s'",
-                       dbesc(normalise_link($url)));
-
-               if (count($r)) {
-                       $profile = $r[0];
-                       $profile["keywords"] = "";
-                       $profile["gender"] = "";
-                       $profile["community"] = false;
-                       $profile["network"] = "";
-                       $profile["addr"] = "";
-               }
        }
 
        // Fetching further contact data from the contact table
-       $r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `addr`, `forum`, `prv`, `bd` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `network` = '%s'",
+       $r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `thumb`, `addr`, `forum`, `prv`, `bd`, `self` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `network` IN ('%s', '')",
                dbesc(normalise_link($url)), intval($uid), dbesc($profile["network"]));
 
-       if (!count($r))
-               $r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `addr`, `forum`, `prv`, `bd` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d",
+       if (!count($r) AND !isset($profile))
+               $r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `thumb`, `addr`, `forum`, `prv`, `bd`, `self` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d",
                        dbesc(normalise_link($url)), intval($uid));
 
-       if (!count($r))
-               $r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `addr`, `forum`, `prv`, `bd` FROM `contact` WHERE `nurl` = '%s' AND `uid` = 0",
+       if (!count($r) AND !isset($profile))
+               $r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `thumb`, `addr`, `forum`, `prv`, `bd` FROM `contact` WHERE `nurl` = '%s' AND `uid` = 0",
                        dbesc(normalise_link($url)));
 
        if ($r) {
-               if (isset($r[0]["url"]) AND $r[0]["url"])
+               if (!isset($profile["url"]) AND $r[0]["url"])
                        $profile["url"] = $r[0]["url"];
-               if (isset($r[0]["name"]) AND $r[0]["name"])
+               if (!isset($profile["name"]) AND $r[0]["name"])
                        $profile["name"] = $r[0]["name"];
-               if (isset($r[0]["nick"]) AND $r[0]["nick"] AND ($profile["nick"] == ""))
+               if (!isset($profile["nick"]) AND $r[0]["nick"])
                        $profile["nick"] = $r[0]["nick"];
-               if (isset($r[0]["addr"]) AND $r[0]["addr"] AND ($profile["addr"] == ""))
+               if (!isset($profile["addr"]) AND $r[0]["addr"])
                        $profile["addr"] = $r[0]["addr"];
-               if (isset($r[0]["photo"]) AND $r[0]["photo"])
+               if ((!isset($profile["photo"]) OR $r[0]["self"]) AND $r[0]["photo"])
                        $profile["photo"] = $r[0]["photo"];
-               if (isset($r[0]["location"]) AND $r[0]["location"])
+               if (!isset($profile["location"]) AND $r[0]["location"])
                        $profile["location"] = $r[0]["location"];
-               if (isset($r[0]["about"]) AND $r[0]["about"])
+               if (!isset($profile["about"]) AND $r[0]["about"])
                        $profile["about"] = $r[0]["about"];
-               if (isset($r[0]["keywords"]) AND $r[0]["keywords"])
+               if (!isset($profile["keywords"]) AND $r[0]["keywords"])
                        $profile["keywords"] = $r[0]["keywords"];
-               if (isset($r[0]["gender"]) AND $r[0]["gender"])
+               if (!isset($profile["gender"]) AND $r[0]["gender"])
                        $profile["gender"] = $r[0]["gender"];
                if (isset($r[0]["forum"]) OR isset($r[0]["prv"]))
                        $profile["community"] = ($r[0]["forum"] OR $r[0]["prv"]);
-               if (isset($r[0]["network"]) AND $r[0]["network"])
+               if (!isset($profile["network"]) AND $r[0]["network"])
                        $profile["network"] = $r[0]["network"];
-               if (isset($r[0]["addr"]) AND $r[0]["addr"])
+               if (!isset($profile["addr"]) AND $r[0]["addr"])
                        $profile["addr"] = $r[0]["addr"];
-               if (isset($r[0]["bd"]) AND $r[0]["bd"])
+               if (!isset($profile["bd"]) AND $r[0]["bd"])
                        $profile["bd"] = $r[0]["bd"];
+               if (isset($r[0]["thumb"]))
+                       $profile["thumb"] = $r[0]["thumb"];
                if ($r[0]["uid"] == 0)
                        $profile["cid"] = 0;
                else
@@ -415,6 +404,7 @@ function get_contact($url, $uid = 0) {
        $contactid = 0;
 
        // is it an address in the format user@server.tld?
+       /// @todo use gcontact and/or the addr field for a lookup
        if (!strstr($url, "http") OR strstr($url, "@")) {
                $data = probe_url($url);
                $url = $data["url"];
@@ -422,12 +412,12 @@ function get_contact($url, $uid = 0) {
                        return 0;
        }
 
-       $contact = q("SELECT `id`, `avatar-date` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d",
+       $contact = q("SELECT `id`, `avatar-date` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d ORDER BY `id` LIMIT 2",
                        dbesc(normalise_link($url)),
                        intval($uid));
 
        if (!$contact)
-               $contact = q("SELECT `id`, `avatar-date` FROM `contact` WHERE `alias` IN ('%s', '%s') AND `uid` = %d",
+               $contact = q("SELECT `id`, `avatar-date` FROM `contact` WHERE `alias` IN ('%s', '%s') AND `uid` = %d ORDER BY `id` LIMIT 1",
                                dbesc($url),
                                dbesc(normalise_link($url)),
                                intval($uid));
@@ -451,9 +441,7 @@ function get_contact($url, $uid = 0) {
        if (!in_array($data["network"], array(NETWORK_DFRN, NETWORK_OSTATUS, NETWORK_DIASPORA)))
                return 0;
 
-       // tempory programming. Can be deleted after 2015-02-07
-       if (($data["alias"] == "") AND (normalise_link($data["url"]) != normalise_link($url)))
-               $data["alias"] = normalise_link($url);
+       $url = $data["url"];
 
        if ($contactid == 0) {
                q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`,
@@ -482,7 +470,7 @@ function get_contact($url, $uid = 0) {
                        dbesc($data["poco"])
                );
 
-               $contact = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d",
+               $contact = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d ORDER BY `id` LIMIT 2",
                                dbesc(normalise_link($data["url"])),
                                intval($uid));
                if (!$contact)
@@ -491,25 +479,217 @@ function get_contact($url, $uid = 0) {
                $contactid = $contact[0]["id"];
        }
 
+       if ((count($contact) > 1) AND ($uid == 0) AND ($contactid != 0) AND ($url != ""))
+               q("DELETE FROM `contact` WHERE `nurl` = '%s' AND `id` != %d",
+                       dbesc(normalise_link($url)),
+                       intval($contactid));
+
        require_once("Photo.php");
 
-       $photos = import_profile_photo($data["photo"],$uid,$contactid);
+       update_contact_avatar($data["photo"],$uid,$contactid);
 
-       q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s',
-               `addr` = '%s', `alias` = '%s', `name` = '%s', `nick` = '%s',
-               `name-date` = '%s', `uri-date` = '%s', `avatar-date` = '%s' WHERE `id` = %d",
-               dbesc($photos[0]),
-               dbesc($photos[1]),
-               dbesc($photos[2]),
+       q("UPDATE `contact` SET `addr` = '%s', `alias` = '%s', `name` = '%s', `nick` = '%s',
+               `name-date` = '%s', `uri-date` = '%s' WHERE `id` = %d",
                dbesc($data["addr"]),
                dbesc($data["alias"]),
                dbesc($data["name"]),
                dbesc($data["nick"]),
                dbesc(datetime_convert()),
                dbesc(datetime_convert()),
-               dbesc(datetime_convert()),
                intval($contactid)
        );
 
        return $contactid;
 }
+
+/**
+ * @brief Returns posts from a given gcontact
+ *
+ * @param App $a argv application class
+ * @param int $gcontact_id Global contact
+ *
+ * @return string posts in HTML
+ */
+function posts_from_gcontact($a, $gcontact_id) {
+
+       require_once('include/conversation.php');
+
+       // There are no posts with "uid = 0" with connector networks
+       // This speeds up the query a lot
+       $r = q("SELECT `network` FROM `gcontact` WHERE `id` = %d", dbesc($gcontact_id));
+       if (in_array($r[0]["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS, "")))
+               $sql = "(`item`.`uid` = 0 OR  (`item`.`uid` = %d AND `item`.`private`))";
+       else
+               $sql = "`item`.`uid` = %d";
+
+       if(get_config('system', 'old_pager')) {
+               $r = q("SELECT COUNT(*) AS `total` FROM `item`
+                       WHERE `gcontact-id` = %d and $sql",
+                       intval($gcontact_id),
+                       intval(local_user()));
+
+               $a->set_pager_total($r[0]['total']);
+       }
+
+       $r = q("SELECT `item`.`uri`, `item`.*, `item`.`id` AS `item_id`,
+                       `author-name` AS `name`, `owner-avatar` AS `photo`,
+                       `owner-link` AS `url`, `owner-avatar` AS `thumb`
+               FROM `item` FORCE INDEX (`gcontactid_uid_created`)
+               WHERE `gcontact-id` = %d AND $sql AND
+                       NOT `deleted` AND NOT `moderated` AND `visible`
+               ORDER BY `item`.`created` DESC LIMIT %d, %d",
+               intval($gcontact_id),
+               intval(local_user()),
+               intval($a->pager['start']),
+               intval($a->pager['itemspage'])
+       );
+
+       $o = conversation($a,$r,'community',false);
+
+       if(!get_config('system', 'old_pager')) {
+               $o .= alt_pager($a,count($r));
+       } else {
+               $o .= paginate($a);
+       }
+
+       return $o;
+}
+
+/**
+ * @brief set the gcontact-id in all item entries
+ *
+ * This job has to be started multiple times until all entries are set.
+ * It isn't started in the update function since it would consume too much time and can be done in the background.
+ */
+function item_set_gcontact() {
+       define ('POST_UPDATE_VERSION', 1192);
+
+       // Was the script completed?
+       if (get_config("system", "post_update_version") >= POST_UPDATE_VERSION)
+               return;
+
+       // Check if the first step is done (Setting "gcontact-id" in the item table)
+       $r = q("SELECT `author-link`, `author-name`, `author-avatar`, `uid`, `network` FROM `item` WHERE `gcontact-id` = 0 LIMIT 1000");
+       if (!$r) {
+               // Are there unfinished entries in the thread table?
+               $r = q("SELECT COUNT(*) AS `total` FROM `thread`
+                       INNER JOIN `item` ON `item`.`id` =`thread`.`iid`
+                       WHERE `thread`.`gcontact-id` = 0 AND
+                               (`thread`.`uid` IN (SELECT `uid` from `user`) OR `thread`.`uid` = 0)");
+
+               if ($r AND ($r[0]["total"] == 0)) {
+                       set_config("system", "post_update_version", POST_UPDATE_VERSION);
+                       return false;
+               }
+
+               // Update the thread table from the item table
+               q("UPDATE `thread` INNER JOIN `item` ON `item`.`id`=`thread`.`iid`
+                               SET `thread`.`gcontact-id` = `item`.`gcontact-id`
+                       WHERE `thread`.`gcontact-id` = 0 AND
+                               (`thread`.`uid` IN (SELECT `uid` from `user`) OR `thread`.`uid` = 0)");
+
+               return false;
+       }
+
+       $item_arr = array();
+       foreach ($r AS $item) {
+               $index = $item["author-link"]."-".$item["uid"];
+               $item_arr[$index] = array("author-link" => $item["author-link"],
+                                               "uid" => $item["uid"],
+                                               "network" => $item["network"]);
+       }
+
+       // Set the "gcontact-id" in the item table and add a new gcontact entry if needed
+       foreach($item_arr AS $item) {
+               $gcontact_id = get_gcontact_id(array("url" => $item['author-link'], "network" => $item['network'],
+                                               "photo" => $item['author-avatar'], "name" => $item['author-name']));
+               q("UPDATE `item` SET `gcontact-id` = %d WHERE `uid` = %d AND `author-link` = '%s' AND `gcontact-id` = 0",
+                       intval($gcontact_id), intval($item["uid"]), dbesc($item["author-link"]));
+       }
+       return true;
+}
+
+/**
+ * @brief Returns posts from a given contact
+ *
+ * @param App $a argv application class
+ * @param int $contact_id contact
+ *
+ * @return string posts in HTML
+ */
+function posts_from_contact($a, $contact_id) {
+
+       require_once('include/conversation.php');
+
+       $r = q("SELECT `url` FROM `contact` WHERE `id` = %d", intval($contact_id));
+       if (!$r)
+               return false;
+
+       $contact = $r[0];
+
+       if(get_config('system', 'old_pager')) {
+               $r = q("SELECT COUNT(*) AS `total` FROM `item`
+                       WHERE `item`.`uid` = %d AND `author-link` IN ('%s', '%s')",
+                       intval(local_user()),
+                       dbesc(str_replace("https://", "http://", $contact["url"])),
+                       dbesc(str_replace("http://", "https://", $contact["url"])));
+
+               $a->set_pager_total($r[0]['total']);
+       }
+
+       $r = q("SELECT `item`.`uri`, `item`.*, `item`.`id` AS `item_id`,
+                       `author-name` AS `name`, `owner-avatar` AS `photo`,
+                       `owner-link` AS `url`, `owner-avatar` AS `thumb`
+               FROM `item` FORCE INDEX (`uid_contactid_created`)
+               WHERE `item`.`uid` = %d AND `contact-id` = %d
+                       AND `author-link` IN ('%s', '%s')
+                       AND NOT `deleted` AND NOT `moderated` AND `visible`
+               ORDER BY `item`.`created` DESC LIMIT %d, %d",
+               intval(local_user()),
+               intval($contact_id),
+               dbesc(str_replace("https://", "http://", $contact["url"])),
+               dbesc(str_replace("http://", "https://", $contact["url"])),
+               intval($a->pager['start']),
+               intval($a->pager['itemspage'])
+       );
+
+       $o .= conversation($a,$r,'community',false);
+
+       if(!get_config('system', 'old_pager'))
+               $o .= alt_pager($a,count($r));
+       else
+               $o .= paginate($a);
+
+       return $o;
+}
+
+/**
+ * @brief Returns a formatted location string from the given profile array
+ *
+ * @param array $profile Profile array (Generated from the "profile" table)
+ *
+ * @return string Location string
+ */
+function formatted_location($profile) {
+       $location = '';
+
+       if($profile['locality'])
+               $location .= $profile['locality'];
+
+       if($profile['region'] AND ($profile['locality'] != $profile['region'])) {
+               if($location)
+                       $location .= ', ';
+
+               $location .= $profile['region'];
+       }
+
+       if($profile['country-name']) {
+               if($location)
+                       $location .= ', ';
+
+               $location .= $profile['country-name'];
+       }
+
+       return $location;
+}
+?>
diff --git a/include/ForumManager.php b/include/ForumManager.php
new file mode 100644 (file)
index 0000000..49417d1
--- /dev/null
@@ -0,0 +1,190 @@
+<?php
+
+/**
+ * @file include/forum.php
+ * @brief Functions related to forum functionality *
+ */
+
+/**
+ * @brief This class handles functions related to the forum functionality
+ */
+class ForumManager {
+
+       /**
+        * @brief Function to list all forums a user is connected with
+        *
+        * @param int $uid of the profile owner
+        * @param boolean $showhidden
+        *      Show frorums which are not hidden
+        * @param boolean $lastitem
+        *      Sort by lastitem
+        * @param boolean $showprivate
+        *      Show private groups
+        *
+        * @returns array
+        *      'url'   => forum url
+        *      'name'  => forum name
+        *      'id'    => number of the key from the array
+        *      'micro' => contact photo in format micro
+        */
+       public static function get_list($uid, $showhidden = true, $lastitem, $showprivate = false) {
+
+               $forumlist = array();
+
+               $order = (($showhidden) ? '' : ' AND NOT `hidden` ');
+               $order .= (($lastitem) ? ' ORDER BY `last-item` DESC ' : ' ORDER BY `name` ASC ');
+               $select = '`forum` ';
+               if ($showprivate) {
+                       $select = '(`forum` OR `prv`)';
+               }
+
+               $contacts = q("SELECT `contact`.`id`, `contact`.`url`, `contact`.`name`, `contact`.`micro` FROM `contact`
+                               WHERE `network`= 'dfrn' AND $select AND `uid` = %d
+                               AND NOT `blocked` AND NOT `hidden` AND NOT `pending` AND NOT `archive`
+                               AND `success_update` > `failure_update`
+                               $order ",
+                               intval($uid)
+               );
+
+               if (!$contacts)
+                       return($forumlist);
+
+               foreach($contacts as $contact) {
+                       $forumlist[] = array(
+                               'url'   => $contact['url'],
+                               'name'  => $contact['name'],
+                               'id'    => $contact['id'],
+                               'micro' => $contact['micro'],
+                       );
+               }
+               return($forumlist);
+       }
+
+
+       /**
+        * @brief Forumlist widget
+        *
+        * Sidebar widget to show subcribed friendica forums. If activated
+        * in the settings, it appears at the notwork page sidebar
+        *
+        * @param int $uid The ID of the User
+        * @param int $cid
+        *      The contact id which is used to mark a forum as "selected"
+        * @return string
+        */
+       public static function widget($uid,$cid = 0) {
+
+               if(! intval(feature_enabled(local_user(),'forumlist_widget')))
+                       return;
+
+               $o = '';
+
+               //sort by last updated item
+               $lastitem = true;
+
+               $contacts = self::get_list($uid,true,$lastitem, true);
+               $total = count($contacts);
+               $visible_forums = 10;
+
+               if(count($contacts)) {
+
+                       $id = 0;
+
+                       foreach($contacts as $contact) {
+
+                               $selected = (($cid == $contact['id']) ? ' forum-selected' : '');
+
+                               $entry = array(
+                                       'url' => z_root() . '/network?f=&cid=' . $contact['id'],
+                                       'external_url' => z_root() . '/redir/' . $contact['id'],
+                                       'name' => $contact['name'],
+                                       'cid' => $contact['id'],
+                                       'selected'      => $selected,
+                                       'micro' => proxy_url($contact['micro'], false, PROXY_SIZE_MICRO),
+                                       'id' => ++$id,
+                               );
+                               $entries[] = $entry;
+                       }
+
+                       $tpl = get_markup_template('widget_forumlist.tpl');
+
+                       $o .= replace_macros($tpl,array(
+                               '$title'        => t('Forums'),
+                               '$forums'       => $entries,
+                               '$link_desc'    => t('External link to forum'),
+                               '$total'        => $total,
+                               '$visible_forums' => $visible_forums,
+                               '$showmore'     => t('show more'),
+                       ));
+               }
+
+               return $o;
+       }
+
+       /**
+        * @brief Format forumlist as contact block
+        *
+        * This function is used to show the forumlist in
+        * the advanced profile.
+        *
+        * @param int $uid The ID of the User
+        * @return string
+        *
+        */
+       public static function profile_advanced($uid) {
+
+               $profile = intval(feature_enabled($uid,'forumlist_profile'));
+               if(! $profile)
+                       return;
+
+               $o = '';
+
+               // place holder in case somebody wants configurability
+               $show_total = 9999;
+
+               //don't sort by last updated item
+               $lastitem = false;
+
+               $contacts = self::get_list($uid,false,$lastitem,false);
+
+               $total_shown = 0;
+
+               foreach($contacts as $contact) {
+                       $forumlist .= micropro($contact,false,'forumlist-profile-advanced');
+                       $total_shown ++;
+                       if($total_shown == $show_total)
+                               break;
+               }
+
+               if(count($contacts) > 0)
+                       $o .= $forumlist;
+                       return $o;
+       }
+
+       /**
+        * @brief count unread forum items
+        *
+        * Count unread items of connected forums and private groups
+        *
+        * @return array
+        *      'id' => contact id
+        *      'name' => contact/forum name
+        *      'count' => counted unseen forum items
+        *
+        */
+       public static function count_unseen_items() {
+               $r = q("SELECT `contact`.`id`, `contact`.`name`, COUNT(*) AS `count` FROM `item`
+                               INNER JOIN `contact` ON `item`.`contact-id` = `contact`.`id`
+                               WHERE `item`.`uid` = %d AND `item`.`visible` AND NOT `item`.`deleted` AND `item`.`unseen`
+                               AND `contact`.`network`= 'dfrn' AND (`contact`.`forum` OR `contact`.`prv`)
+                               AND NOT `contact`.`blocked` AND NOT `contact`.`hidden`
+                               AND NOT `contact`.`pending` AND NOT `contact`.`archive`
+                               AND `contact`.`success_update` > `failure_update`
+                               GROUP BY `contact`.`id` ",
+                       intval(local_user())
+               );
+
+               return $r;
+       }
+
+}
\ No newline at end of file
diff --git a/include/NotificationsManager.php b/include/NotificationsManager.php
new file mode 100644 (file)
index 0000000..5f8211e
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+/**
+ * @file include/NotificationsManager.php
+ */
+require_once('include/html2plain.php');
+require_once("include/datetime.php");
+require_once("include/bbcode.php");
+
+/**
+ * @brief Read and write notifications from/to database
+ */
+class NotificationsManager {
+    private $a;
+    
+    public function __construct() {
+        $this->a = get_app();
+    }
+    
+       /**
+        * @brief set some extra note properties
+        *
+        * @param array $notes array of note arrays from db
+        * @return array Copy of input array with added properties
+        * 
+        * Set some extra properties to note array from db:
+        *  - timestamp as int in default TZ
+        *  - date_rel : relative date string
+        *  - msg_html: message as html string
+        *  - msg_plain: message as plain text string
+        */
+    private function _set_extra($notes) {
+        $rets = array();
+        foreach($notes as $n) {
+            $local_time = datetime_convert('UTC',date_default_timezone_get(),$n['date']);
+            $n['timestamp'] = strtotime($local_time);
+            $n['date_rel'] = relative_date($n['date']);
+                       $n['msg_html'] = bbcode($n['msg'], false, false, false, false);
+                       $n['msg_plain'] = explode("\n",trim(html2plain($n['msg_html'], 0)))[0];
+                       
+            $rets[] = $n;
+        }
+        return $rets;
+    }
+
+
+    /**
+     * @brief get all notifications for local_user()
+     *
+     * @param array $filter optional Array "column name"=>value: filter query by columns values
+     * @param string $order optional Space separated list of column to sort by. prepend name with "+" to sort ASC, "-" to sort DESC. Default to "-date"
+     * @param string $limit optional Query limits
+     *
+     * @return array of results or false on errors
+     */
+    public function getAll($filter = array(), $order="-date", $limit="") {
+        $filter_str = array();
+        $filter_sql = "";
+        foreach($filter as $column => $value) {
+            $filter_str[] = sprintf("`%s` = '%s'", $column, dbesc($value));
+        }
+        if (count($filter_str)>0) {
+            $filter_sql = "AND ".implode(" AND ", $filter_str);
+        }
+        
+        $aOrder = explode(" ", $order);
+        $asOrder = array();
+        foreach($aOrder as $o) {
+            $dir = "asc";
+            if ($o[0]==="-") {
+                $dir = "desc";
+                $o = substr($o,1);
+            }
+            if ($o[0]==="+") {
+                $dir = "asc";
+                $o = substr($o,1);
+            }
+            $asOrder[] = "$o $dir";
+        }
+        $order_sql = implode(", ", $asOrder);
+        
+        if ($limit!="") $limit = " LIMIT ".$limit;
+        
+               $r = q("SELECT * FROM `notify` WHERE `uid` = %d $filter_sql ORDER BY $order_sql $limit",
+                       intval(local_user())
+               );
+        if ($r!==false && count($r)>0) return $this->_set_extra($r);
+        return false;
+    }
+    
+    /**
+     * @brief get one note for local_user() by $id value
+     *
+     * @param int $id
+     * @return array note values or null if not found
+     */
+    public function getByID($id) {
+        $r = q("SELECT * FROM `notify` WHERE `id` = %d AND `uid` = %d LIMIT 1",
+                intval($id),
+                intval(local_user())
+        );
+        if($r!==false && count($r)>0) {
+            return $this->_set_extra($r)[0];
+        }
+        return null;
+    }
+    
+    /**
+     * @brief set seen state of $note of local_user()
+     *
+     * @param array $note
+     * @param bool $seen optional true or false, default true
+     * @return bool true on success, false on errors
+     */
+    public function setSeen($note, $seen = true) {
+        return q("UPDATE `notify` SET `seen` = %d WHERE ( `link` = '%s' OR ( `parent` != 0 AND `parent` = %d AND `otype` = '%s' )) AND `uid` = %d",
+            intval($seen),
+            dbesc($note['link']),
+            intval($note['parent']),
+            dbesc($note['otype']),
+            intval(local_user())
+        );
+    }
+       
+    /**
+     * @brief set seen state of all notifications of local_user()
+     *
+     * @param bool $seen optional true or false. default true
+     * @return bool true on success, false on error
+     */
+    public function setAllSeen($seen = true) {
+       return q("UPDATE `notify` SET `seen` = %d WHERE `uid` = %d",
+            intval($seen),
+                       intval(local_user())
+               );
+    }
+}
index 30424747da82a33af4ee88c733e0b712f9967e0b..91fce55a86f868c4959291618fa4b8af77c4bba6 100644 (file)
@@ -720,65 +720,101 @@ function guess_image_type($filename, $fromcurl=false) {
 
 }
 
-function import_profile_photo($photo,$uid,$cid) {
+/**
+ * @brief Updates the avatar links in a contact only if needed
+ *
+ * @param string $avatar Link to avatar picture
+ * @param int $uid User id of contact owner
+ * @param int $cid Contact id
+ * @param bool $force force picture update
+ *
+ * @return array Returns array of the different avatar sizes
+ */
+function update_contact_avatar($avatar,$uid,$cid, $force = false) {
 
-    $a = get_app();
+       $r = q("SELECT `avatar`, `photo`, `thumb`, `micro` FROM `contact` WHERE `id` = %d LIMIT 1", intval($cid));
+       if (!$r)
+               return false;
+       else
+               $data = array($r[0]["photo"], $r[0]["thumb"], $r[0]["micro"]);
 
-    $r = q("select `resource-id` from photo where `uid` = %d and `contact-id` = %d and `scale` = 4 and `album` = 'Contact Photos' limit 1",
-       intval($uid),
-       intval($cid)
-    );
-    if(count($r) && strlen($r[0]['resource-id'])) {
-       $hash = $r[0]['resource-id'];
-    }
-    else {
-       $hash = photo_new_resource();
-    }
+       if (($r[0]["avatar"] != $avatar) OR $force) {
+               $photos = import_profile_photo($avatar,$uid,$cid, true);
 
-    $photo_failure = false;
+               if ($photos) {
+                       q("UPDATE `contact` SET `avatar` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' WHERE `id` = %d",
+                               dbesc($avatar), dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]),
+                               dbesc(datetime_convert()), intval($cid));
+                       return $photos;
+               }
+       }
 
-    $filename = basename($photo);
-    $img_str = fetch_url($photo,true);
+       return $data;
+}
 
-    $type = guess_image_type($photo,true);
-    $img = new Photo($img_str, $type);
-    if($img->is_valid()) {
+function import_profile_photo($photo,$uid,$cid, $quit_on_error = false) {
 
-       $img->scaleImageSquare(175);
+       $a = get_app();
 
-       $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4 );
+       $r = q("select `resource-id` from photo where `uid` = %d and `contact-id` = %d and `scale` = 4 and `album` = 'Contact Photos' limit 1",
+               intval($uid),
+               intval($cid)
+       );
+       if(count($r) && strlen($r[0]['resource-id'])) {
+               $hash = $r[0]['resource-id'];
+       } else {
+               $hash = photo_new_resource();
+       }
 
-       if($r === false)
-           $photo_failure = true;
+       $photo_failure = false;
 
-       $img->scaleImage(80);
+       $filename = basename($photo);
+       $img_str = fetch_url($photo,true);
 
-       $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5 );
+       if ($quit_on_error AND ($img_str == ""))
+               return false;
 
-       if($r === false)
-           $photo_failure = true;
+       $type = guess_image_type($photo,true);
+       $img = new Photo($img_str, $type);
+       if($img->is_valid()) {
 
-       $img->scaleImage(48);
+               $img->scaleImageSquare(175);
 
-       $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6 );
+               $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4 );
 
-       if($r === false)
-           $photo_failure = true;
+               if($r === false)
+                       $photo_failure = true;
 
-       $photo = $a->get_baseurl() . '/photo/' . $hash . '-4.' . $img->getExt();
-       $thumb = $a->get_baseurl() . '/photo/' . $hash . '-5.' . $img->getExt();
-       $micro = $a->get_baseurl() . '/photo/' . $hash . '-6.' . $img->getExt();
-    }
-    else
-       $photo_failure = true;
+               $img->scaleImage(80);
 
-    if($photo_failure) {
-       $photo = $a->get_baseurl() . '/images/person-175.jpg';
-       $thumb = $a->get_baseurl() . '/images/person-80.jpg';
-       $micro = $a->get_baseurl() . '/images/person-48.jpg';
-    }
+               $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5 );
+
+               if($r === false)
+                       $photo_failure = true;
+
+               $img->scaleImage(48);
+
+               $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6 );
 
-    return(array($photo,$thumb,$micro));
+               if($r === false)
+                       $photo_failure = true;
+
+               $photo = $a->get_baseurl() . '/photo/' . $hash . '-4.' . $img->getExt();
+               $thumb = $a->get_baseurl() . '/photo/' . $hash . '-5.' . $img->getExt();
+               $micro = $a->get_baseurl() . '/photo/' . $hash . '-6.' . $img->getExt();
+       } else
+               $photo_failure = true;
+
+       if($photo_failure AND $quit_on_error)
+               return false;
+
+       if($photo_failure) {
+               $photo = $a->get_baseurl() . '/images/person-175.jpg';
+               $thumb = $a->get_baseurl() . '/images/person-80.jpg';
+               $micro = $a->get_baseurl() . '/images/person-48.jpg';
+       }
+
+       return(array($photo,$thumb,$micro));
 
 }
 
@@ -792,15 +828,19 @@ function get_photo_info($url) {
 
                $filesize = strlen($img_str);
 
-               $tempfile = tempnam(get_temppath(), "cache");
+               if (function_exists("getimagesizefromstring"))
+                       $data = getimagesizefromstring($img_str);
+               else {
+                       $tempfile = tempnam(get_temppath(), "cache");
 
-               $a = get_app();
-               $stamp1 = microtime(true);
-               file_put_contents($tempfile, $img_str);
-               $a->save_timestamp($stamp1, "file");
+                       $a = get_app();
+                       $stamp1 = microtime(true);
+                       file_put_contents($tempfile, $img_str);
+                       $a->save_timestamp($stamp1, "file");
 
-               $data = getimagesize($tempfile);
-               unlink($tempfile);
+                       $data = getimagesize($tempfile);
+                       unlink($tempfile);
+               }
 
                if ($data)
                        $data["size"] = $filesize;
index cb192c77c2cd5c8c21580a51efe36920b8474076..ca6489b16ad37255311c4b327442824552413ac4 100644 (file)
@@ -235,7 +235,9 @@ function scrape_feed($url) {
        $a = get_app();
 
        $ret = array();
-       $s = fetch_url($url);
+       $cookiejar = tempnam(get_temppath(), 'cookiejar-scrape-feed-');
+       $s = fetch_url($url, false, $redirects, 0, Null, $cookiejar);
+       unlink($cookiejar);
 
        $headers = $a->get_curl_headers();
        $code = $a->get_curl_code();
@@ -559,10 +561,11 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
                                $pubkey = $hcard_key;
                }
        }
-
        if($diaspora && $diaspora_base && $diaspora_guid) {
-               if($mode == PROBE_DIASPORA || ! $notify) {
-                       $notify = $diaspora_base . 'receive/users/' . $diaspora_guid;
+               $diaspora_notify = $diaspora_base.'receive/users/'.$diaspora_guid;
+
+               if($mode == PROBE_DIASPORA || ! $notify || ($notify == $diaspora_notify)) {
+                       $notify = $diaspora_notify;
                        $batch  = $diaspora_base . 'receive/public' ;
                }
                if(strpos($url,'@'))
@@ -661,7 +664,9 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
                                $vcard['photo'] = $feedret['photo'];
                        require_once('library/simplepie/simplepie.inc');
                        $feed = new SimplePie();
-                       $xml = fetch_url($poll);
+                       $cookiejar = tempnam(get_temppath(), 'cookiejar-scrape-feed-');
+                       $xml = fetch_url($poll, false, $redirects, 0, Null, $cookiejar);
+                       unlink($cookiejar);
 
                        logger('probe_url: fetch feed: ' . $poll . ' returns: ' . $xml, LOGGER_DATA);
                        $a = get_app();
index 4ef3d05ea3959fb9ba46070436f7c33e2b034612..69181b7359bcb1ab68af4e84892782156771bf8c 100644 (file)
@@ -20,7 +20,7 @@ function group_select($selname,$selclass,$preselected = false,$size = 4) {
 
        $o .= "<select name=\"{$selname}[]\" id=\"$selclass\" class=\"$selclass\" multiple=\"multiple\" size=\"$size\" >\r\n";
 
-       $r = q("SELECT * FROM `group` WHERE `deleted` = 0 AND `uid` = %d ORDER BY `name` ASC",
+       $r = q("SELECT `id`, `name` FROM `group` WHERE NOT `deleted` AND `uid` = %d ORDER BY `name` ASC",
                intval(local_user())
        );
 
@@ -309,7 +309,7 @@ function populate_acl($user = null, $show_jotnets = false) {
                $pubmail_enabled = false;
 
                if(! $mail_disabled) {
-                       $r = q("SELECT * FROM `mailacct` WHERE `uid` = %d AND `server` != '' LIMIT 1",
+                       $r = q("SELECT `pubmail` FROM `mailacct` WHERE `uid` = %d AND `server` != '' LIMIT 1",
                                intval(local_user())
                        );
                        if(count($r)) {
@@ -407,7 +407,7 @@ function acl_lookup(&$a, $out_type = 'json') {
                $search = $_REQUEST['query'];
        }
 
-//     logger("Searching for ".$search." - type ".$type, LOGGER_DEBUG);
+       logger("Searching for ".$search." - type ".$type, LOGGER_DEBUG);
 
        if ($search!=""){
                $sql_extra = "AND `name` LIKE '%%".dbesc($search)."%%'";
@@ -503,7 +503,7 @@ function acl_lookup(&$a, $out_type = 'json') {
                }
        }
 
-       if ($type=='' || $type=='c'){
+       if ($type==''){
 
                $r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, forum FROM `contact`
                        WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 AND `archive` = 0 AND `notify` != ''
@@ -514,6 +514,17 @@ function acl_lookup(&$a, $out_type = 'json') {
                        dbesc(NETWORK_OSTATUS), dbesc(NETWORK_STATUSNET)
                );
        }
+       elseif ($type=='c'){
+
+               $r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, forum FROM `contact`
+                       WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 AND `archive` = 0 AND `notify` != ''
+                       AND NOT (`network` IN ('%s'))
+                       $sql_extra2
+                       ORDER BY `name` ASC ",
+                       intval(local_user()),
+                       dbesc(NETWORK_STATUSNET)
+               );
+       }
        elseif($type == 'm') {
                $r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag` FROM `contact`
                        WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 AND `archive` = 0
index f1294c690d0d1db79a42d9bb800e6e1f397b4080..e5ca8ca7878d573c7dfefc00361e81ed080d4289 100644 (file)
@@ -23,6 +23,7 @@
        require_once('include/message.php');
        require_once('include/group.php');
        require_once('include/like.php');
+       require_once('include/NotificationsManager.php');
 
 
        define('API_METHOD_ANY','*');
         */
        function api_call(&$a){
                GLOBAL $API, $called_api;
-
+               
                $type="json";
                if (strpos($a->query_string, ".xml")>0) $type="xml";
                if (strpos($a->query_string, ".json")>0) $type="json";
         *              Contact url or False if contact id is unknown
         */
        function api_unique_id_to_url($id){
-               $r = q("SELECT `url` FROM `unique_contacts` WHERE `id`=%d LIMIT 1",
+               $r = q("SELECT `url` FROM `gcontact` WHERE `id`=%d LIMIT 1",
                        intval($id));
                if ($r)
                        return ($r[0]["url"]);
                        $r = array();
 
                        if ($url != "")
-                               $r = q("SELECT * FROM `unique_contacts` WHERE `url`='%s' LIMIT 1", $url);
-                       elseif ($nick != "")
-                               $r = q("SELECT * FROM `unique_contacts` WHERE `nick`='%s' LIMIT 1", $nick);
+                               $r = q("SELECT * FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($url)));
 
                        if ($r) {
                                // If no nick where given, extract it from the address
                                        'id_str' => (string) $r[0]["id"],
                                        'name' => $r[0]["name"],
                                        'screen_name' => (($r[0]['nick']) ? $r[0]['nick'] : $r[0]['name']),
-                                       'location' => NULL,
-                                       'description' => NULL,
+                                       'location' => $r[0]["location"],
+                                       'description' => $r[0]["about"],
                                        'url' => $r[0]["url"],
                                        'protected' => false,
                                        'followers_count' => 0,
                                        'friends_count' => 0,
                                        'listed_count' => 0,
-                                       'created_at' => api_date(0),
+                                       'created_at' => api_date($r[0]["created"]),
                                        'favourites_count' => 0,
                                        'utc_offset' => 0,
                                        'time_zone' => 'UTC',
                                        'contributors_enabled' => false,
                                        'is_translator' => false,
                                        'is_translation_enabled' => false,
-                                       'profile_image_url' => $r[0]["avatar"],
-                                       'profile_image_url_https' => $r[0]["avatar"],
+                                       'profile_image_url' => $r[0]["photo"],
+                                       'profile_image_url_https' => $r[0]["photo"],
                                        'following' => false,
                                        'follow_request_sent' => false,
                                        'notifications' => false,
                                        'uid' => 0,
                                        'cid' => 0,
                                        'self' => 0,
-                                       'network' => '',
+                                       'network' => $r[0]["network"],
                                );
 
                                return $ret;
                        $uinfo[0]['nick'] = api_get_nick($uinfo[0]["url"]);
                }
 
-               // Fetching unique id
-               $r = q("SELECT id FROM `unique_contacts` WHERE `url`='%s' LIMIT 1", dbesc(normalise_link($uinfo[0]['url'])));
-
-               // If not there, then add it
-               if (count($r) == 0) {
-                       q("INSERT INTO `unique_contacts` (`url`, `name`, `nick`, `avatar`) VALUES ('%s', '%s', '%s', '%s')",
-                               dbesc(normalise_link($uinfo[0]['url'])), dbesc($uinfo[0]['name']),dbesc($uinfo[0]['nick']), dbesc($uinfo[0]['micro']));
-
-                       $r = q("SELECT `id` FROM `unique_contacts` WHERE `url`='%s' LIMIT 1", dbesc(normalise_link($uinfo[0]['url'])));
-               }
-
                $network_name = network_to_name($uinfo[0]['network'], $uinfo[0]['url']);
 
+               $gcontact_id  = get_gcontact_id(array("url" => $uinfo[0]['url'], "network" => $uinfo[0]['network'],
+                                                       "photo" => $uinfo[0]['micro'], "name" => $uinfo[0]['name']));
+
                $ret = Array(
-                       'id' => intval($r[0]['id']),
-                       'id_str' => (string) intval($r[0]['id']),
+                       'id' => intval($gcontact_id),
+                       'id_str' => (string) intval($gcontact_id),
                        'name' => (($uinfo[0]['name']) ? $uinfo[0]['name'] : $uinfo[0]['nick']),
                        'screen_name' => (($uinfo[0]['nick']) ? $uinfo[0]['nick'] : $uinfo[0]['name']),
                        'location' => ($usr) ? $usr[0]['default-location'] : $network_name,
 
        function api_item_get_user(&$a, $item) {
 
-               $author = q("SELECT * FROM `unique_contacts` WHERE `url`='%s' LIMIT 1",
-                       dbesc(normalise_link($item['author-link'])));
-
-               if (count($author) == 0) {
-                       q("INSERT INTO `unique_contacts` (`url`, `name`, `avatar`) VALUES ('%s', '%s', '%s')",
-                               dbesc(normalise_link($item["author-link"])), dbesc($item["author-name"]), dbesc($item["author-avatar"]));
-
-                       $author = q("SELECT `id` FROM `unique_contacts` WHERE `url`='%s' LIMIT 1",
-                               dbesc(normalise_link($item['author-link'])));
-               } else if ($item["author-link"].$item["author-name"] != $author[0]["url"].$author[0]["name"]) {
-                       $r = q("SELECT `id` FROM `unique_contacts` WHERE `name` = '%s' AND `avatar` = '%s' AND url = '%s'",
-                               dbesc($item["author-name"]), dbesc($item["author-avatar"]),
-                               dbesc(normalise_link($item["author-link"])));
-
-                       if (!$r)
-                               q("UPDATE `unique_contacts` SET `name` = '%s', `avatar` = '%s' WHERE `url` = '%s'",
-                                       dbesc($item["author-name"]), dbesc($item["author-avatar"]),
-                                       dbesc(normalise_link($item["author-link"])));
-               }
-
-               $owner = q("SELECT `id` FROM `unique_contacts` WHERE `url`='%s' LIMIT 1",
-                       dbesc(normalise_link($item['owner-link'])));
-
-               if (count($owner) == 0) {
-                       q("INSERT INTO `unique_contacts` (`url`, `name`, `avatar`) VALUES ('%s', '%s', '%s')",
-                               dbesc(normalise_link($item["owner-link"])), dbesc($item["owner-name"]), dbesc($item["owner-avatar"]));
+               // Make sure that there is an entry in the global contacts for author and owner
+               get_gcontact_id(array("url" => $item['author-link'], "network" => $item['network'],
+                                       "photo" => $item['author-avatar'], "name" => $item['author-name']));
 
-                       $owner = q("SELECT `id` FROM `unique_contacts` WHERE `url`='%s' LIMIT 1",
-                               dbesc(normalise_link($item['owner-link'])));
-               } else if ($item["owner-link"].$item["owner-name"] != $owner[0]["url"].$owner[0]["name"]) {
-                       $r = q("SELECT `id` FROM `unique_contacts` WHERE `name` = '%s' AND `avatar` = '%s' AND url = '%s'",
-                               dbesc($item["owner-name"]), dbesc($item["owner-avatar"]),
-                               dbesc(normalise_link($item["owner-link"])));
-
-                       if (!$r)
-                               q("UPDATE `unique_contacts` SET `name` = '%s', `avatar` = '%s' WHERE `url` = '%s'",
-                                       dbesc($item["owner-name"]), dbesc($item["owner-avatar"]),
-                                       dbesc(normalise_link($item["owner-link"])));
-               }
+               get_gcontact_id(array("url" => $item['owner-link'], "network" => $item['network'],
+                                       "photo" => $item['owner-avatar'], "name" => $item['owner-name']));
 
                // Comments in threads may appear as wall-to-wall postings.
                // So only take the owner at the top posting.
        }
 
 
+       /**
+        * @brief transform $data array in xml without a template
+        *
+        * @param array $data
+        * @return string xml string
+        */
+       function api_array_to_xml($data, $ename="") {
+               $attrs="";
+               $childs="";
+               if (count($data)==1 && !is_array($data[0])) {
+                       $ename = array_keys($data)[0];
+                       $v = $data[$ename];
+                       return "<$ename>$v</$ename>";
+               }
+               foreach($data as $k=>$v) {
+                       $k=trim($k,'$');
+                       if (!is_array($v)) {
+                               $attrs .= sprintf('%s="%s" ', $k, $v);
+                       } else {
+                               if (is_numeric($k)) $k=trim($ename,'s');
+                               $childs.=api_array_to_xml($v, $k);
+                       }
+               }
+               $res = $childs;
+               if ($ename!="") $res = "<$ename $attrs>$res</$ename>";
+               return $res;
+       }
+
        /**
         *  load api $templatename for $type and replace $data array
         */
                        case "rss":
                        case "xml":
                                $data = array_xmlify($data);
-                               $tpl = get_markup_template("api_".$templatename."_".$type.".tpl");
-                               if(! $tpl) {
-                                       header ("Content-Type: text/xml");
-                                       echo '<?xml version="1.0" encoding="UTF-8"?>'."\n".'<status><error>not implemented</error></status>';
-                                       killme();
+                               if ($templatename==="<auto>") {
+                                       $ret = api_array_to_xml($data); 
+                               } else {
+                                       $tpl = get_markup_template("api_".$templatename."_".$type.".tpl");
+                                       if(! $tpl) {
+                                               header ("Content-Type: text/xml");
+                                               echo '<?xml version="1.0" encoding="UTF-8"?>'."\n".'<status><error>not implemented</error></status>';
+                                               killme();
+                                       }
+                                       $ret = replace_macros($tpl, $data);
                                }
-                               $ret = replace_macros($tpl, $data);
                                break;
                        case "json":
                                $ret = $data;
 
                if((strpos($txt,'<') !== false) || (strpos($txt,'>') !== false)) {
 
-                       require_once('library/HTMLPurifier.auto.php');
-
                        $txt = html2bb_video($txt);
                        $config = HTMLPurifier_Config::createDefault();
                        $config->set('Cache.DefinitionImpl', null);
                if(requestdata('htmlstatus')) {
                        $txt = requestdata('htmlstatus');
                        if((strpos($txt,'<') !== false) || (strpos($txt,'>') !== false)) {
-
-                               require_once('library/HTMLPurifier.auto.php');
-
                                $txt = html2bb_video($txt);
 
                                $config = HTMLPurifier_Config::createDefault();
                                $in_reply_to_status_id= intval($lastwall['parent']);
                                $in_reply_to_status_id_str = (string) intval($lastwall['parent']);
 
-                               $r = q("SELECT * FROM `unique_contacts` WHERE `url` = '%s'", dbesc(normalise_link($lastwall['item-author'])));
+                               $r = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s'", dbesc(normalise_link($lastwall['item-author'])));
                                if ($r) {
                                        if ($r[0]['nick'] == "")
                                                $r[0]['nick'] = api_get_nick($r[0]["url"]);
                                        $in_reply_to_status_id = intval($lastwall['parent']);
                                        $in_reply_to_status_id_str = (string) intval($lastwall['parent']);
 
-                                       $r = q("SELECT * FROM `unique_contacts` WHERE `url` = '%s'", dbesc(normalise_link($reply[0]['item-author'])));
+                                       $r = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s'", dbesc(normalise_link($reply[0]['item-author'])));
                                        if ($r) {
                                                if ($r[0]['nick'] == "")
                                                        $r[0]['nick'] = api_get_nick($r[0]["url"]);
                $userlist = array();
 
                if (isset($_GET["q"])) {
-                       $r = q("SELECT id FROM `unique_contacts` WHERE `name`='%s'", dbesc($_GET["q"]));
+                       $r = q("SELECT id FROM `gcontact` WHERE `name`='%s'", dbesc($_GET["q"]));
                        if (!count($r))
-                               $r = q("SELECT `id` FROM `unique_contacts` WHERE `nick`='%s'", dbesc($_GET["q"]));
+                               $r = q("SELECT `id` FROM `gcontact` WHERE `nick`='%s'", dbesc($_GET["q"]));
 
                        if (count($r)) {
                                foreach ($r AS $user) {
                        WHERE `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0
                        AND `contact`.`id` = `item`.`contact-id`
                        AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
+                       AND NOT `item`.`private` AND `item`.`allow_cid` = '' AND `item`.`allow`.`gid` = ''
+                       AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
                        $sql_extra
                        AND `item`.`id`=%d",
                        intval($id)
                                $_REQUEST["source"] = api_source();
 
                        item_post($a);
-               }
+               } else
+                       throw new ForbiddenException();
 
                // this should output the last post (the one we just posted).
                $called_api = null;
                                        intval(api_user()),
                                        intval($in_reply_to_status_id));
                                if ($r) {
-                                       $r = q("SELECT * FROM `unique_contacts` WHERE `url` = '%s'", dbesc(normalise_link($r[0]['author-link'])));
+                                       $r = q("SELECT * FROM `gcontact` WHERE `url` = '%s'", dbesc(normalise_link($r[0]['author-link'])));
 
                                        if ($r) {
                                                if ($r[0]['nick'] == "")
 
                $stringify_ids = (x($_REQUEST,'stringify_ids')?$_REQUEST['stringify_ids']:false);
 
-               $r = q("SELECT `unique_contacts`.`id` FROM `contact`, `unique_contacts` WHERE `contact`.`nurl` = `unique_contacts`.`url` AND `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `pending` $sql_extra",
+               $r = q("SELECT `gcontact`.`id` FROM `contact`, `gcontact` WHERE `contact`.`nurl` = `gcontact`.`nurl` AND `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `pending` $sql_extra",
                        intval(api_user())
                );
 
 
                //}
 
-               if ($nick != "") {
-                       q("UPDATE `unique_contacts` SET `nick` = '%s' WHERE `nick` != '%s' AND url = '%s'",
-                               dbesc($nick), dbesc($nick), dbesc(normalise_link($profile)));
+               if ($nick != "")
                        return($nick);
-               }
 
                return(false);
        }
        api_register_func('api/friendica/activity/unattendno', 'api_friendica_activity', true, API_METHOD_POST);
        api_register_func('api/friendica/activity/unattendmaybe', 'api_friendica_activity', true, API_METHOD_POST);
 
+       /**
+        * @brief Returns notifications
+        *
+        * @param App $a
+        * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
+        * @return string
+       */
+       function api_friendica_notification(&$a, $type) {
+               if (api_user()===false) throw new ForbiddenException();
+               if ($a->argc!==3) throw new BadRequestException("Invalid argument count");
+               $nm = new NotificationsManager();
+               
+               $notes = $nm->getAll(array(), "+seen -date", 50);
+               return api_apply_template("<auto>", $type, array('$notes' => $notes));
+       }
+       
+       /**
+        * @brief Set notification as seen and returns associated item (if possible)
+        *
+        * POST request with 'id' param as notification id
+        * 
+        * @param App $a
+        * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
+        * @return string
+        */
+       function api_friendica_notification_seen(&$a, $type){
+               if (api_user()===false) throw new ForbiddenException();
+               if ($a->argc!==4) throw new BadRequestException("Invalid argument count");
+               
+               $id = (x($_REQUEST, 'id') ? intval($_REQUEST['id']) : 0);
+               
+               $nm = new NotificationsManager();               
+               $note = $nm->getByID($id);
+               if (is_null($note)) throw new BadRequestException("Invalid argument");
+               
+               $nm->setSeen($note);
+               if ($note['otype']=='item') {
+                       // would be really better with an ItemsManager and $im->getByID() :-P
+                       $r = q("SELECT * FROM `item` WHERE `id`=%d AND `uid`=%d",
+                               intval($note['iid']),
+                               intval(local_user())
+                       );
+                       if ($r!==false) {
+                               // we found the item, return it to the user
+                               $user_info = api_get_user($a);
+                               $ret = api_format_items($r,$user_info);
+                               $data = array('$statuses' => $ret);
+                               return api_apply_template("timeline", $type, $data);
+                       }
+                       // the item can't be found, but we set the note as seen, so we count this as a success
+               } 
+               return api_apply_template('<auto>', $type, array('status' => "success"));
+       }
+       
+       api_register_func('api/friendica/notification/seen', 'api_friendica_notification_seen', true, API_METHOD_POST);
+       api_register_func('api/friendica/notification', 'api_friendica_notification', true, API_METHOD_GET);
+       
+
 /*
 To.Do:
     [pagename] => api/1.1/statuses/lookup.json
diff --git a/include/autoloader.php b/include/autoloader.php
new file mode 100644 (file)
index 0000000..6caa082
--- /dev/null
@@ -0,0 +1,69 @@
+<?php\r
+/**\r
+ * @file include/autoloader.php\r
+ */\r
+\r
+/**\r
+ * @brief composer-derived autoloader init\r
+ **/\r
+class FriendicaAutoloaderInit\r
+{\r
+    private static $loader;\r
+\r
+    public static function loadClassLoader($class)\r
+    {\r
+        if ('Composer\Autoload\ClassLoader' === $class) {\r
+            require __DIR__ . '/autoloader/ClassLoader.php';\r
+        }\r
+    }\r
+\r
+    public static function getLoader()\r
+    {\r
+        if (null !== self::$loader) {\r
+            return self::$loader;\r
+        }\r
+\r
+        spl_autoload_register(array('FriendicaAutoloaderInit', 'loadClassLoader'), true, true);\r
+        self::$loader = $loader = new \Composer\Autoload\ClassLoader();\r
+        spl_autoload_unregister(array('FriendicaAutoloaderInit', 'loadClassLoader'));\r
+\r
+        // library \r
+        $map = require __DIR__ . '/autoloader/autoload_namespaces.php';\r
+        foreach ($map as $namespace => $path) {\r
+            $loader->set($namespace, $path);\r
+        }\r
+\r
+        $map = require __DIR__ . '/autoloader/autoload_psr4.php';\r
+        foreach ($map as $namespace => $path) {\r
+            $loader->setPsr4($namespace, $path);\r
+        }\r
+\r
+        $classMap = require __DIR__ . '/autoloader/autoload_classmap.php';\r
+        if ($classMap) {\r
+            $loader->addClassMap($classMap);\r
+        }\r
+        \r
+        $loader->register(true);\r
+        \r
+        $includeFiles = require __DIR__ . '/autoloader/autoload_files.php';\r
+        foreach ($includeFiles as $fileIdentifier => $file) {\r
+            friendicaRequire($fileIdentifier, $file);\r
+        }\r
+        \r
+\r
+        return $loader;\r
+    }\r
+}\r
+\r
+function friendicaRequire($fileIdentifier, $file)\r
+{\r
+    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {\r
+        require $file;\r
+\r
+        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;\r
+    }\r
+}\r
+\r
+\r
+\r
+return FriendicaAutoloaderInit::getLoader();\r
diff --git a/include/autoloader/ClassLoader.php b/include/autoloader/ClassLoader.php
new file mode 100644 (file)
index 0000000..d916d80
--- /dev/null
@@ -0,0 +1,413 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE.composer
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0 class loader
+ *
+ * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
+ *
+ *     $loader = new \Composer\Autoload\ClassLoader();
+ *
+ *     // register classes with namespaces
+ *     $loader->add('Symfony\Component', __DIR__.'/component');
+ *     $loader->add('Symfony',           __DIR__.'/framework');
+ *
+ *     // activate the autoloader
+ *     $loader->register();
+ *
+ *     // to enable searching the include path (eg. for PEAR packages)
+ *     $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class ClassLoader
+{
+    // PSR-4
+    private $prefixLengthsPsr4 = array();
+    private $prefixDirsPsr4 = array();
+    private $fallbackDirsPsr4 = array();
+
+    // PSR-0
+    private $prefixesPsr0 = array();
+    private $fallbackDirsPsr0 = array();
+
+    private $useIncludePath = false;
+    private $classMap = array();
+
+    private $classMapAuthoritative = false;
+
+    public function getPrefixes()
+    {
+        if (!empty($this->prefixesPsr0)) {
+            return call_user_func_array('array_merge', $this->prefixesPsr0);
+        }
+
+        return array();
+    }
+
+    public function getPrefixesPsr4()
+    {
+        return $this->prefixDirsPsr4;
+    }
+
+    public function getFallbackDirs()
+    {
+        return $this->fallbackDirsPsr0;
+    }
+
+    public function getFallbackDirsPsr4()
+    {
+        return $this->fallbackDirsPsr4;
+    }
+
+    public function getClassMap()
+    {
+        return $this->classMap;
+    }
+
+    /**
+     * @param array $classMap Class to filename map
+     */
+    public function addClassMap(array $classMap)
+    {
+        if ($this->classMap) {
+            $this->classMap = array_merge($this->classMap, $classMap);
+        } else {
+            $this->classMap = $classMap;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix, either
+     * appending or prepending to the ones previously set for this prefix.
+     *
+     * @param string       $prefix  The prefix
+     * @param array|string $paths   The PSR-0 root directories
+     * @param bool         $prepend Whether to prepend the directories
+     */
+    public function add($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            if ($prepend) {
+                $this->fallbackDirsPsr0 = array_merge(
+                    (array) $paths,
+                    $this->fallbackDirsPsr0
+                );
+            } else {
+                $this->fallbackDirsPsr0 = array_merge(
+                    $this->fallbackDirsPsr0,
+                    (array) $paths
+                );
+            }
+
+            return;
+        }
+
+        $first = $prefix[0];
+        if (!isset($this->prefixesPsr0[$first][$prefix])) {
+            $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+            return;
+        }
+        if ($prepend) {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                (array) $paths,
+                $this->prefixesPsr0[$first][$prefix]
+            );
+        } else {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                $this->prefixesPsr0[$first][$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace, either
+     * appending or prepending to the ones previously set for this namespace.
+     *
+     * @param string       $prefix  The prefix/namespace, with trailing '\\'
+     * @param array|string $paths   The PSR-0 base directories
+     * @param bool         $prepend Whether to prepend the directories
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function addPsr4($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            // Register directories for the root namespace.
+            if ($prepend) {
+                $this->fallbackDirsPsr4 = array_merge(
+                    (array) $paths,
+                    $this->fallbackDirsPsr4
+                );
+            } else {
+                $this->fallbackDirsPsr4 = array_merge(
+                    $this->fallbackDirsPsr4,
+                    (array) $paths
+                );
+            }
+        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+            // Register directories for a new namespace.
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        } elseif ($prepend) {
+            // Prepend directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                (array) $paths,
+                $this->prefixDirsPsr4[$prefix]
+            );
+        } else {
+            // Append directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                $this->prefixDirsPsr4[$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix,
+     * replacing any others previously set for this prefix.
+     *
+     * @param string       $prefix The prefix
+     * @param array|string $paths  The PSR-0 base directories
+     */
+    public function set($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr0 = (array) $paths;
+        } else {
+            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace,
+     * replacing any others previously set for this namespace.
+     *
+     * @param string       $prefix The prefix/namespace, with trailing '\\'
+     * @param array|string $paths  The PSR-4 base directories
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function setPsr4($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr4 = (array) $paths;
+        } else {
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Turns on searching the include path for class files.
+     *
+     * @param bool $useIncludePath
+     */
+    public function setUseIncludePath($useIncludePath)
+    {
+        $this->useIncludePath = $useIncludePath;
+    }
+
+    /**
+     * Can be used to check if the autoloader uses the include path to check
+     * for classes.
+     *
+     * @return bool
+     */
+    public function getUseIncludePath()
+    {
+        return $this->useIncludePath;
+    }
+
+    /**
+     * Turns off searching the prefix and fallback directories for classes
+     * that have not been registered with the class map.
+     *
+     * @param bool $classMapAuthoritative
+     */
+    public function setClassMapAuthoritative($classMapAuthoritative)
+    {
+        $this->classMapAuthoritative = $classMapAuthoritative;
+    }
+
+    /**
+     * Should class lookup fail if not found in the current class map?
+     *
+     * @return bool
+     */
+    public function isClassMapAuthoritative()
+    {
+        return $this->classMapAuthoritative;
+    }
+
+    /**
+     * Registers this instance as an autoloader.
+     *
+     * @param bool $prepend Whether to prepend the autoloader or not
+     */
+    public function register($prepend = false)
+    {
+        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+    }
+
+    /**
+     * Unregisters this instance as an autoloader.
+     */
+    public function unregister()
+    {
+        spl_autoload_unregister(array($this, 'loadClass'));
+    }
+
+    /**
+     * Loads the given class or interface.
+     *
+     * @param  string    $class The name of the class
+     * @return bool|null True if loaded, null otherwise
+     */
+    public function loadClass($class)
+    {
+        if ($file = $this->findFile($class)) {
+            includeFile($file);
+
+            return true;
+        }
+    }
+
+    /**
+     * Finds the path to the file where the class is defined.
+     *
+     * @param string $class The name of the class
+     *
+     * @return string|false The path if found, false otherwise
+     */
+    public function findFile($class)
+    {
+        // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
+        if ('\\' == $class[0]) {
+            $class = substr($class, 1);
+        }
+
+        // class map lookup
+        if (isset($this->classMap[$class])) {
+            return $this->classMap[$class];
+        }
+        if ($this->classMapAuthoritative) {
+            return false;
+        }
+
+        $file = $this->findFileWithExtension($class, '.php');
+
+        // Search for Hack files if we are running on HHVM
+        if ($file === null && defined('HHVM_VERSION')) {
+            $file = $this->findFileWithExtension($class, '.hh');
+        }
+
+        if ($file === null) {
+            // Remember that this class does not exist.
+            return $this->classMap[$class] = false;
+        }
+
+        return $file;
+    }
+
+    private function findFileWithExtension($class, $ext)
+    {
+        // PSR-4 lookup
+        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+        $first = $class[0];
+        if (isset($this->prefixLengthsPsr4[$first])) {
+            foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
+                if (0 === strpos($class, $prefix)) {
+                    foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
+                        if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-4 fallback dirs
+        foreach ($this->fallbackDirsPsr4 as $dir) {
+            if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 lookup
+        if (false !== $pos = strrpos($class, '\\')) {
+            // namespaced class name
+            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+        } else {
+            // PEAR-like class name
+            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+        }
+
+        if (isset($this->prefixesPsr0[$first])) {
+            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+                if (0 === strpos($class, $prefix)) {
+                    foreach ($dirs as $dir) {
+                        if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-0 fallback dirs
+        foreach ($this->fallbackDirsPsr0 as $dir) {
+            if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 include paths.
+        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+            return $file;
+        }
+    }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+    include $file;
+}
diff --git a/include/autoloader/LICENSE.composer b/include/autoloader/LICENSE.composer
new file mode 100644 (file)
index 0000000..b365b1f
--- /dev/null
@@ -0,0 +1,19 @@
+Copyright (c) 2015 Nils Adermann, Jordi Boggiano\r
+\r
+Permission is hereby granted, free of charge, to any person obtaining a copy\r
+of this software and associated documentation files (the Software), to deal\r
+in the Software without restriction, including without limitation the rights\r
+to use, copy, modify, merge, publish, distribute, sublicense, andor sell\r
+copies of the Software, and to permit persons to whom the Software is furnished\r
+to do so, subject to the following conditions\r
+\r
+The above copyright notice and this permission notice shall be included in all\r
+copies or substantial portions of the Software.\r
+\r
+THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
+THE SOFTWARE.
\ No newline at end of file
diff --git a/include/autoloader/autoload_classmap.php b/include/autoloader/autoload_classmap.php
new file mode 100644 (file)
index 0000000..3efd09f
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(dirname(dirname(__FILE__)))."/library";
+$baseDir = dirname($vendorDir);
+
+return array(
+);
diff --git a/include/autoloader/autoload_files.php b/include/autoloader/autoload_files.php
new file mode 100644 (file)
index 0000000..859135c
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+
+// autoload_files.php @generated by Composer
+
+$vendorDir = dirname(dirname(dirname(__FILE__)))."/library";
+$baseDir = dirname($vendorDir);
+
+return array(
+    '2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
+);
diff --git a/include/autoloader/autoload_namespaces.php b/include/autoloader/autoload_namespaces.php
new file mode 100644 (file)
index 0000000..315a349
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(dirname(dirname(__FILE__)))."/library";
+$baseDir = dirname($vendorDir);
+
+return array(
+    'HTMLPurifier' => array($vendorDir . '/ezyang/htmlpurifier/library'),
+);
diff --git a/include/autoloader/autoload_psr4.php b/include/autoloader/autoload_psr4.php
new file mode 100644 (file)
index 0000000..fe93afe
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(dirname(__FILE__)))."/library";
+$baseDir = dirname($vendorDir);
+
+return array(
+);
index b68a7c5b188ec74c7e181fd9056a2f8e76084fe5..6a44e19ec446e87bcef9b83f53cab95d4af85299 100644 (file)
@@ -3,6 +3,7 @@ require_once("include/oembed.php");
 require_once('include/event.php');
 require_once('include/map.php');
 require_once('mod/proxy.php');
+require_once('include/Contact.php');
 
 function bb_PictureCacheExt($matches) {
        if (strpos($matches[3], "data:image/") === 0)
@@ -541,8 +542,23 @@ function bb_ShareAttributes($share, $simplehtml) {
                $reldate = (($posted) ? " " . relative_date($posted) : '');
        }
 
-       $userid = GetProfileUsername($profile,$author, false);
-       $userid_compact = GetProfileUsername($profile,$author, true);
+       $data = get_contact_details_by_url($profile);
+
+       if (isset($data["name"]) AND isset($data["addr"]))
+               $userid_compact = $data["name"]." (".$data["addr"].")";
+       else
+               $userid_compact = GetProfileUsername($profile,$author, true);
+
+       if (isset($data["addr"]))
+               $userid = $data["addr"];
+       else
+               $userid = GetProfileUsername($profile,$author, false);
+
+       if (isset($data["name"]))
+               $author = $data["name"];
+
+       if (isset($data["photo"]))
+               $avatar = $data["photo"];
 
        $preshare = trim($share[1]);
 
index 05bcea6212d7769359febba441b6b79c36bfc161..b27274e9a946671f321062bf0d78827b6f8ac784 100644 (file)
@@ -19,7 +19,7 @@ if(! function_exists('load_config')) {
 function load_config($family) {
        global $a;
 
-       $r = q("SELECT * FROM `config` WHERE `cat` = '%s'", dbesc($family));
+       $r = q("SELECT `v`, `k` FROM `config` WHERE `cat` = '%s'", dbesc($family));
        if(count($r)) {
                foreach($r as $rr) {
                        $k = $rr['k'];
@@ -170,7 +170,7 @@ function set_config($family,$key,$value) {
 if(! function_exists('load_pconfig')) {
 function load_pconfig($uid,$family) {
        global $a;
-       $r = q("SELECT * FROM `pconfig` WHERE `cat` = '%s' AND `uid` = %d",
+       $r = q("SELECT `v`,`k` FROM `pconfig` WHERE `cat` = '%s' AND `uid` = %d",
                dbesc($family),
                intval($uid)
        );
index 7eae1e052e0523552f05f0f6ef64eec37215065a..6c33be84fb33971d1f9a167e7607e244487268c0 100644 (file)
@@ -811,16 +811,16 @@ function best_link_url($item,&$sparkle,$ssl_state = false) {
        if((local_user()) && (local_user() == $item['uid'])) {
                if(isset($a->contacts) && x($a->contacts,$clean_url)) {
                        if($a->contacts[$clean_url]['network'] === NETWORK_DFRN) {
-                               $best_url = $a->get_baseurl($ssl_state) . '/redir/' . $a->contacts[$clean_url]['id'];
+                               $best_url = 'redir/'.$a->contacts[$clean_url]['id'];
                                $sparkle = true;
                        } else
                                $best_url = $a->contacts[$clean_url]['url'];
                }
        } elseif (local_user()) {
-               $r = q("SELECT `id`, `network` FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `nurl` = '%s'",
+               $r = q("SELECT `id` FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `nurl` = '%s' LIMIT 1",
                        dbesc(NETWORK_DFRN), intval(local_user()), dbesc(normalise_link($clean_url)));
                if ($r) {
-                       $best_url = $a->get_baseurl($ssl_state).'/redir/'.$r[0]['id'];
+                       $best_url = 'redir/'.$r[0]['id'];
                        $sparkle = true;
                }
        }
@@ -876,7 +876,7 @@ function item_photo_menu($item){
                if(local_user() && local_user() == $item['uid'] && link_compare($item['url'],$item['author-link'])) {
                        $cid = $item['contact-id'];
                } else {
-                       $r = q("SELECT `id`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' ORDER BY `uid` DESC LIMIT 1",
+                       $r = q("SELECT `id`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' LIMIT 1",
                                intval(local_user()), dbesc(normalise_link($item['author-link'])));
                        if ($r) {
                                $cid = $r[0]["id"];
index a6e81f6bfd2710b2ecbedd8fff3751071697c74a..3acf711dd163056234b822a1eb27f39387108a7b 100644 (file)
@@ -30,7 +30,6 @@ function cron_run(&$argv, &$argc){
 
        require_once('include/session.php');
        require_once('include/datetime.php');
-       require_once('library/simplepie/simplepie.inc');
        require_once('include/items.php');
        require_once('include/Contact.php');
        require_once('include/email.php');
@@ -133,9 +132,8 @@ function cron_run(&$argv, &$argc){
        // Check every conversation
        check_conversations(false);
 
-       // Follow your friends from your legacy OStatus account
-       // Doesn't work
-       // ostatus_check_follow_friends();
+       // Set the gcontact-id in the item table if missing
+       item_set_gcontact();
 
        // update nodeinfo data
        nodeinfo_cron();
@@ -159,75 +157,14 @@ function cron_run(&$argv, &$argc){
                proc_run('php','include/expire.php');
        }
 
-       $last = get_config('system','cache_last_cleared');
-
-       if($last) {
-               $next = $last + (3600); // Once per hour
-               $clear_cache = ($next <= time());
-       } else
-               $clear_cache = true;
-
-       if ($clear_cache) {
-               // clear old cache
-               Cache::clear();
-
-               // clear old item cache files
-               clear_cache();
-
-               // clear cache for photos
-               clear_cache($a->get_basepath(), $a->get_basepath()."/photo");
-
-               // clear smarty cache
-               clear_cache($a->get_basepath()."/view/smarty3/compiled", $a->get_basepath()."/view/smarty3/compiled");
-
-               // clear cache for image proxy
-               if (!get_config("system", "proxy_disabled")) {
-                       clear_cache($a->get_basepath(), $a->get_basepath()."/proxy");
-
-                       $cachetime = get_config('system','proxy_cache_time');
-                       if (!$cachetime) $cachetime = PROXY_DEFAULT_TIME;
-
-                       q('DELETE FROM `photo` WHERE `uid` = 0 AND `resource-id` LIKE "pic:%%" AND `created` < NOW() - INTERVAL %d SECOND', $cachetime);
-               }
-
-               // Maximum table size in megabyte
-               $max_tablesize = intval(get_config('system','optimize_max_tablesize')) * 1000000;
-               if ($max_tablesize == 0)
-                       $max_tablesize = 100 * 1000000; // Default are 100 MB
-
-               // Minimum fragmentation level in percent
-               $fragmentation_level = intval(get_config('system','optimize_fragmentation')) / 100;
-               if ($fragmentation_level == 0)
-                       $fragmentation_level = 0.3; // Default value is 30%
-
-               // Optimize some tables that need to be optimized
-               $r = q("SHOW TABLE STATUS");
-               foreach($r as $table) {
+       // Clear cache entries
+       cron_clear_cache($a);
 
-                       // Don't optimize tables that are too large
-                       if ($table["Data_length"] > $max_tablesize)
-                               continue;
+       // Repair missing Diaspora values in contacts
+       cron_repair_diaspora($a);
 
-                       // Don't optimize empty tables
-                       if ($table["Data_length"] == 0)
-                               continue;
-
-                       // Calculate fragmentation
-                       $fragmentation = $table["Data_free"] / $table["Data_length"];
-
-                       logger("Table ".$table["Name"]." - Fragmentation level: ".round($fragmentation * 100, 2), LOGGER_DEBUG);
-
-                       // Don't optimize tables that needn't to be optimized
-                       if ($fragmentation < $fragmentation_level)
-                               continue;
-
-                       // So optimize it
-                       logger("Optimize Table ".$table["Name"], LOGGER_DEBUG);
-                       q("OPTIMIZE TABLE `%s`", dbesc($table["Name"]));
-               }
-
-               set_config('system','cache_last_cleared', time());
-       }
+       // Repair entries in the database
+       cron_repair_database();
 
        $manual_id  = 0;
        $generation = 0;
@@ -373,6 +310,132 @@ function cron_run(&$argv, &$argc){
        return;
 }
 
+/**
+ * @brief Clear cache entries
+ *
+ * @param App $a
+ */
+function cron_clear_cache(&$a) {
+
+       $last = get_config('system','cache_last_cleared');
+
+       if($last) {
+               $next = $last + (3600); // Once per hour
+               $clear_cache = ($next <= time());
+       } else
+               $clear_cache = true;
+
+       if (!$clear_cache)
+               return;
+
+       // clear old cache
+       Cache::clear();
+
+       // clear old item cache files
+       clear_cache();
+
+       // clear cache for photos
+       clear_cache($a->get_basepath(), $a->get_basepath()."/photo");
+
+       // clear smarty cache
+       clear_cache($a->get_basepath()."/view/smarty3/compiled", $a->get_basepath()."/view/smarty3/compiled");
+
+       // clear cache for image proxy
+       if (!get_config("system", "proxy_disabled")) {
+               clear_cache($a->get_basepath(), $a->get_basepath()."/proxy");
+
+               $cachetime = get_config('system','proxy_cache_time');
+               if (!$cachetime) $cachetime = PROXY_DEFAULT_TIME;
+
+               q('DELETE FROM `photo` WHERE `uid` = 0 AND `resource-id` LIKE "pic:%%" AND `created` < NOW() - INTERVAL %d SECOND', $cachetime);
+       }
+
+       // Delete the cached OEmbed entries that are older than one year
+       q("DELETE FROM `oembed` WHERE `created` < NOW() - INTERVAL 1 YEAR");
+
+       // Delete the cached "parse_url" entries that are older than one year
+       q("DELETE FROM `parsed_url` WHERE `created` < NOW() - INTERVAL 1 YEAR");
+
+       // Maximum table size in megabyte
+       $max_tablesize = intval(get_config('system','optimize_max_tablesize')) * 1000000;
+       if ($max_tablesize == 0)
+               $max_tablesize = 100 * 1000000; // Default are 100 MB
+
+       // Minimum fragmentation level in percent
+       $fragmentation_level = intval(get_config('system','optimize_fragmentation')) / 100;
+       if ($fragmentation_level == 0)
+               $fragmentation_level = 0.3; // Default value is 30%
+
+       // Optimize some tables that need to be optimized
+       $r = q("SHOW TABLE STATUS");
+       foreach($r as $table) {
+
+               // Don't optimize tables that are too large
+               if ($table["Data_length"] > $max_tablesize)
+                       continue;
+
+               // Don't optimize empty tables
+               if ($table["Data_length"] == 0)
+                       continue;
+
+               // Calculate fragmentation
+               $fragmentation = $table["Data_free"] / $table["Data_length"];
+
+               logger("Table ".$table["Name"]." - Fragmentation level: ".round($fragmentation * 100, 2), LOGGER_DEBUG);
+
+               // Don't optimize tables that needn't to be optimized
+               if ($fragmentation < $fragmentation_level)
+                       continue;
+
+               // So optimize it
+               logger("Optimize Table ".$table["Name"], LOGGER_DEBUG);
+               q("OPTIMIZE TABLE `%s`", dbesc($table["Name"]));
+       }
+
+       set_config('system','cache_last_cleared', time());
+}
+
+/**
+ * @brief Repair missing values in Diaspora contacts
+ *
+ * @param App $a
+ */
+function cron_repair_diaspora(&$a) {
+       $r = q("SELECT `id`, `url` FROM `contact`
+               WHERE `network` = '%s' AND (`batch` = '' OR `notify` = '' OR `poll` = '' OR pubkey = '')
+                       ORDER BY RAND() LIMIT 50", dbesc(NETWORK_DIASPORA));
+       if ($r) {
+               foreach ($r AS $contact) {
+                       if (poco_reachable($contact["url"])) {
+                               $data = probe_url($contact["url"]);
+                               if ($data["network"] == NETWORK_DIASPORA) {
+                                       logger("Repair contact ".$contact["id"]." ".$contact["url"], LOGGER_DEBUG);
+                                       q("UPDATE `contact` SET `batch` = '%s', `notify` = '%s', `poll` = '%s', pubkey = '%s' WHERE `id` = %d",
+                                               dbesc($data["batch"]), dbesc($data["notify"]), dbesc($data["poll"]), dbesc($data["pubkey"]),
+                                               intval($contact["id"]));
+                               }
+                       }
+               }
+       }
+}
+
+/**
+ * @brief Do some repairs in database entries
+ *
+ */
+function cron_repair_database() {
+
+       // Set the parent if it wasn't set. (Shouldn't happen - but does sometimes)
+       // This call is very "cheap" so we can do it at any time without a problem
+       q("UPDATE `item` INNER JOIN `item` AS `parent` ON `parent`.`uri` = `item`.`parent-uri` AND `parent`.`uid` = `item`.`uid` SET `item`.`parent` = `parent`.`id` WHERE `item`.`parent` = 0");
+
+       /// @todo
+       /// - remove thread entries without item
+       /// - remove sign entries without item
+       /// - remove children when parent got lost
+       /// - set contact-id in item when not present
+}
+
 if (array_search(__file__,get_included_files())===0){
        cron_run($_SERVER["argv"],$_SERVER["argc"]);
        killme();
index a05af5e38fe87f6b6c48eeb886dbd0e508c2d33e..89305a2406f1238375944c436ec2e0f21522f126 100644 (file)
@@ -1,8 +1,17 @@
 <?php
+/**
+ * @file include/datetime.php
+ * @brief Some functions for date and time related tasks.
+ */
 
-// two-level sort for timezones.
 
-if(! function_exists('timezone_cmp')) {
+/**
+ * @brief Two-level sort for timezones.
+ *
+ * @param string $a
+ * @param string $b
+ * @return int
+ */
 function timezone_cmp($a, $b) {
        if(strstr($a,'/') && strstr($b,'/')) {
                if ( t($a) == t($b)) return 0;
@@ -11,11 +20,16 @@ function timezone_cmp($a, $b) {
        if(strstr($a,'/')) return -1;
        if(strstr($b,'/')) return  1;
        if ( t($a) == t($b)) return 0;
+
        return ( t($a) < t($b)) ? -1 : 1;
-}}
+}
 
-// emit a timezone selector grouped (primarily) by continent
-if(! function_exists('select_timezone')) {
+/**
+ * @brief Emit a timezone selector grouped (primarily) by continent
+ * 
+ * @param string $current Timezone
+ * @return string Parsed HTML output
+ */
 function select_timezone($current = 'America/Los_Angeles') {
 
        $timezone_identifiers = DateTimeZone::listIdentifiers();
@@ -52,13 +66,25 @@ function select_timezone($current = 'America/Los_Angeles') {
        }
        $o .= '</optgroup></select>';
        return $o;
-}}
+}
+
 
-// return a select using 'field_select_raw' template, with timezones
-// groupped (primarily) by continent
-// arguments follow convetion as other field_* template array:
-// 'name', 'label', $value, 'help'
-if (!function_exists('field_timezone')){
+
+/**
+ * @brief Generating a Timezone selector
+ * 
+ * Return a select using 'field_select_raw' template, with timezones
+ * groupped (primarily) by continent
+ * arguments follow convetion as other field_* template array:
+ * 'name', 'label', $value, 'help'
+ * 
+ * @param string $name Name of the selector
+ * @param string $label Label for the selector
+ * @param string $current Timezone
+ * @param string $help Help text
+ * 
+ * @return string Parsed HTML
+ */
 function field_timezone($name='timezone', $label='', $current = 'America/Los_Angeles', $help){
        $options = select_timezone($current);
        $options = str_replace('<select id="timezone_select" name="timezone">','', $options);
@@ -69,15 +95,19 @@ function field_timezone($name='timezone', $label='', $current = 'America/Los_Ang
                '$field' => array($name, $label, $current, $help, $options),
        ));
 
-}}
-
-// General purpose date parse/convert function.
-// $from = source timezone
-// $to   = dest timezone
-// $s    = some parseable date/time string
-// $fmt  = output format
+}
 
-if(! function_exists('datetime_convert')) {
+/**
+ * @brief General purpose date parse/convert function.
+ *
+ * @param string $from Source timezone
+ * @param string $to Dest timezone
+ * @param string $s Some parseable date/time string
+ * @param string $fmt Output format recognised from php's DateTime class
+ *   http://www.php.net/manual/en/datetime.format.php
+ * 
+ * @return string Formatted date according to given format
+ */
 function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d H:i:s") {
 
        // Defaults to UTC if nothing is set, but throws an exception if set to empty string.
@@ -123,14 +153,20 @@ function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d
        }
 
        $d->setTimeZone($to_obj);
-       return($d->format($fmt));
-}}
 
+       return($d->format($fmt));
+}
 
-// wrapper for date selector, tailored for use in birthday fields
 
+/**
+ * @brief Wrapper for date selector, tailored for use in birthday fields.
+ *
+ * @param string $dob Date of Birth
+ * @return string
+ */
 function dob($dob) {
        list($year,$month,$day) = sscanf($dob,'%4d-%2d-%2d');
+
        $f = get_config('system','birthday_input_format');
        if(! $f)
                $f = 'ymd';
@@ -138,62 +174,69 @@ function dob($dob) {
                $value = '';
        else
                $value = (($year) ? datetime_convert('UTC','UTC',$dob,'Y-m-d') : datetime_convert('UTC','UTC',$dob,'m-d'));
+
        $o = '<input type="text" name="dob" value="' . $value . '" placeholder="' . t('YYYY-MM-DD or MM-DD') . '" />';
+
 //     if ($dob && $dob != '0000-00-00')
 //             $o = datesel($f,mktime(0,0,0,0,0,1900),mktime(),mktime(0,0,0,$month,$day,$year),'dob');
 //     else
 //             $o = datesel($f,mktime(0,0,0,0,0,1900),mktime(),false,'dob');
+
        return $o;
 }
 
 /**
- * returns a date selector
- * @param $format
- *  format string, e.g. 'ymd' or 'mdy'. Not currently supported
- * @param $min
- *  unix timestamp of minimum date
- * @param $max
- *  unix timestap of maximum date
- * @param $default
- *  unix timestamp of default date
- * @param $id
- *  id and name of datetimepicker (defaults to "datetimepicker")
+ * @brief Returns a date selector
+ * 
+ * @param string $format
+ *  Format string, e.g. 'ymd' or 'mdy'. Not currently supported
+ * @param string $min
+ *  Unix timestamp of minimum date
+ * @param string $max
+ *  Unix timestap of maximum date
+ * @param string $default
+ *  Unix timestamp of default date
+ * @param string $id
+ *  ID and name of datetimepicker (defaults to "datetimepicker")
+ * 
+ * @return string Parsed HTML output.
  */
-if(! function_exists('datesel')) {
 function datesel($format, $min, $max, $default, $id = 'datepicker') {
        return datetimesel($format,$min,$max,$default,$id,true,false, '','');
-}}
+}
 
 /**
- * returns a time selector
- * @param $format
- *  format string, e.g. 'ymd' or 'mdy'. Not currently supported
+ * @brief Returns a time selector
+ * 
+ * @param string $format
+ *  Format string, e.g. 'ymd' or 'mdy'. Not currently supported
  * @param $h
- *  already selected hour
+ *  Already selected hour
  * @param $m
- *  already selected minute
- * @param $id
- *  id and name of datetimepicker (defaults to "timepicker")
+ *  Already selected minute
+ * @param string $id
+ *  ID and name of datetimepicker (defaults to "timepicker")
+ * 
+ * @return string Parsed HTML output.
  */
-if(! function_exists('timesel')) {
 function timesel($format, $h, $m, $id='timepicker') {
        return datetimesel($format,new DateTime(),new DateTime(),new DateTime("$h:$m"),$id,false,true);
-}}
+}
 
 /**
  * @brief Returns a datetime selector.
  *
- * @param $format
+ * @param string $format
  *  format string, e.g. 'ymd' or 'mdy'. Not currently supported
- * @param $min
+ * @param string $min
  *  unix timestamp of minimum date
- * @param $max
+ * @param string $max
  *  unix timestap of maximum date
- * @param $default
+ * @param string $default
  *  unix timestamp of default date
  * @param string $id
  *  id and name of datetimepicker (defaults to "datetimepicker")
- * @param boolean $pickdate
+ * @param bool $pickdate
  *  true to show date picker (default)
  * @param boolean $picktime
  *  true to show time picker (default)
@@ -201,17 +244,15 @@ function timesel($format, $h, $m, $id='timepicker') {
  *  set minimum date from picker with id $minfrom (none by default)
  * @param $maxfrom
  *  set maximum date from picker with id $maxfrom (none by default)
- * @param boolean $required default false
+ * @param bool $required default false
+ * 
  * @return string Parsed HTML output.
  *
  * @todo Once browser support is better this could probably be replaced with
  * native HTML5 date picker.
  */
-if(! function_exists('datetimesel')) {
 function datetimesel($format, $min, $max, $default, $id = 'datetimepicker', $pickdate = true, $picktime = true, $minfrom = '', $maxfrom = '', $required = false) {
 
-       $a = get_app();
-
        // First day of the week (0 = Sunday)
        $firstDay = get_pconfig(local_user(),'system','first_day_of_week');
        if ($firstDay === false) $firstDay=0;
@@ -224,43 +265,58 @@ function datetimesel($format, $min, $max, $default, $id = 'datetimepicker', $pic
 
        $o = '';
        $dateformat = '';
+
        if($pickdate) $dateformat .= 'Y-m-d';
        if($pickdate && $picktime) $dateformat .= ' ';
        if($picktime) $dateformat .= 'H:i';
+
        $minjs = $min ? ",minDate: new Date({$min->getTimestamp()}*1000), yearStart: " . $min->format('Y') : '';
        $maxjs = $max ? ",maxDate: new Date({$max->getTimestamp()}*1000), yearEnd: " . $max->format('Y') : '';
 
        $input_text = $default ? 'value="' . date($dateformat, $default->getTimestamp()) . '"' : '';
        $defaultdatejs = $default ? ",defaultDate: new Date({$default->getTimestamp()}*1000)" : '';
+
        $pickers = '';
        if(!$pickdate) $pickers .= ',datepicker: false';
        if(!$picktime) $pickers .= ',timepicker: false';
+
        $extra_js = '';
        $pickers .= ",dayOfWeekStart: ".$firstDay.",lang:'".$lang."'";
        if($minfrom != '')
                $extra_js .= "\$('#$minfrom').data('xdsoft_datetimepicker').setOptions({onChangeDateTime: function (currentDateTime) { \$('#$id').data('xdsoft_datetimepicker').setOptions({minDate: currentDateTime})}})";
        if($maxfrom != '')
                $extra_js .= "\$('#$maxfrom').data('xdsoft_datetimepicker').setOptions({onChangeDateTime: function (currentDateTime) { \$('#$id').data('xdsoft_datetimepicker').setOptions({maxDate: currentDateTime})}})";
+
        $readable_format = $dateformat;
        $readable_format = str_replace('Y','yyyy',$readable_format);
        $readable_format = str_replace('m','mm',$readable_format);
        $readable_format = str_replace('d','dd',$readable_format);
        $readable_format = str_replace('H','HH',$readable_format);
        $readable_format = str_replace('i','MM',$readable_format);
+
        $o .= "<div class='date'><input type='text' placeholder='$readable_format' name='$id' id='$id' $input_text />";
        $o .= '</div>';
        $o .= "<script type='text/javascript'>";
        $o .= "\$(function () {var picker = \$('#$id').datetimepicker({step:5,format:'$dateformat' $minjs $maxjs $pickers $defaultdatejs}); $extra_js})";
        $o .= "</script>";
-       return $o;
-}}
 
-// implements "3 seconds ago" etc.
-// based on $posted_date, (UTC).
-// Results relative to current timezone
-// Limited to range of timestamps
+       return $o;
+}
 
-if(! function_exists('relative_date')) {
+/**
+ * @brief Returns a relative date string.
+ *
+ * Implements "3 seconds ago" etc.
+ * Based on $posted_date, (UTC).
+ * Results relative to current timezone.
+ * Limited to range of timestamps.
+ *
+ * @param string $posted_date
+ * @param string $format (optional) Parsed with sprintf()
+ *    <tt>%1$d %2$s ago</tt>, e.g. 22 hours ago, 1 minute ago
+ * 
+ * @return string with relative date
+ */
 function relative_date($posted_date,$format = null) {
 
        $localtime = datetime_convert('UTC',date_default_timezone_get(),$posted_date);
@@ -300,23 +356,33 @@ function relative_date($posted_date,$format = null) {
                        // translators - e.g. 22 hours ago, 1 minute ago
                        if(! $format)
                                $format = t('%1$d %2$s ago');
-                       return sprintf( $format,$r, (($r == 1) ? $str[0] : $str[1]));
-        }
-    }
-}}
-
 
+                       return sprintf( $format,$r, (($r == 1) ? $str[0] : $str[1]));
+               }
+       }
+}
 
-// Returns age in years, given a date of birth,
-// the timezone of the person whose date of birth is provided,
-// and the timezone of the person viewing the result.
-// Why? Bear with me. Let's say I live in Mittagong, Australia, and my
-// birthday is on New Year's. You live in San Bruno, California.
-// When exactly are you going to see my age increase?
-// A: 5:00 AM Dec 31 San Bruno time. That's precisely when I start
-// celebrating and become a year older. If you wish me happy birthday
-// on January 1 (San Bruno time), you'll be a day late.
-
+/**
+ * @brief Returns timezone correct age in years.
+ *
+ * Returns the age in years, given a date of birth, the timezone of the person
+ * whose date of birth is provided, and the timezone of the person viewing the
+ * result.
+ *
+ * Why? Bear with me. Let's say I live in Mittagong, Australia, and my birthday
+ * is on New Year's. You live in San Bruno, California.
+ * When exactly are you going to see my age increase?
+ *
+ * A: 5:00 AM Dec 31 San Bruno time. That's precisely when I start celebrating
+ * and become a year older. If you wish me happy birthday on January 1
+ * (San Bruno time), you'll be a day late.
+ *
+ * @param string $dob Date of Birth
+ * @param string $owner_tz (optional) Timezone of the person of interest
+ * @param string $viewer_tz (optional) Timezone of the person viewing
+ * 
+ * @return int Age in years
+ */
 function age($dob,$owner_tz = '',$viewer_tz = '') {
        if(! intval($dob))
                return 0;
@@ -333,64 +399,79 @@ function age($dob,$owner_tz = '',$viewer_tz = '') {
 
        if(($curr_month < $month) || (($curr_month == $month) && ($curr_day < $day)))
                $year_diff--;
+
        return $year_diff;
 }
 
-
-
-// Get days in month
-// get_dim($year, $month);
-// returns number of days.
-// $month[1] = 'January'; 
-//   to match human usage.
-
-if(! function_exists('get_dim')) {
+/**
+ * @brief Get days of a month in a given year.
+ *
+ * Returns number of days in the month of the given year.
+ * $m = 1 is 'January' to match human usage.
+ *
+ * @param int $y Year
+ * @param int $m Month (1=January, 12=December)
+ * 
+ * @return int Number of days in the given month
+ */
 function get_dim($y,$m) {
 
-  $dim = array( 0,
-    31, 28, 31, 30, 31, 30,
-    31, 31, 30, 31, 30, 31);
-  if($m != 2)
-    return $dim[$m];
-  if(((($y % 4) == 0) && (($y % 100) != 0)) || (($y % 400) == 0))
-    return 29;
-  return $dim[2];
-}}
+       $dim = array( 0,
+               31, 28, 31, 30, 31, 30,
+               31, 31, 30, 31, 30, 31);
 
+       if($m != 2)
+               return $dim[$m];
 
-// Returns the first day in month for a given month, year
-// get_first_dim($year,$month)
-// returns 0 = Sunday through 6 = Saturday
-// Months start at 1.
-
-if(! function_exists('get_first_dim')) {
-function get_first_dim($y,$m) {
-  $d = sprintf('%04d-%02d-01 00:00', intval($y), intval($m));
-  return datetime_convert('UTC','UTC',$d,'w');
-}}
-
-// output a calendar for the given month, year.
-// if $links are provided (array), e.g. $links[12] => 'http://mylink' , 
-// date 12 will be linked appropriately. Today's date is also noted by
-// altering td class.
-// Months count from 1.
+       if(((($y % 4) == 0) && (($y % 100) != 0)) || (($y % 400) == 0))
+               return 29;
 
+       return $dim[2];
+}
 
-/// @TODO Provide (prev,next) links, define class variations for different size calendars
+/**
+ * @brief Returns the first day in month for a given month, year.
+ *
+ * Months start at 1.
+ *
+ * @param int $y Year
+ * @param int $m Month (1=January, 12=December)
+ * 
+ * @return string day 0 = Sunday through 6 = Saturday
+ */
+function get_first_dim($y,$m) {
+       $d = sprintf('%04d-%02d-01 00:00', intval($y), intval($m));
 
+       return datetime_convert('UTC','UTC',$d,'w');
+}
 
-if(! function_exists('cal')) {
+/**
+ * @brief Output a calendar for the given month, year.
+ *
+ * If $links are provided (array), e.g. $links[12] => 'http://mylink' ,
+ * date 12 will be linked appropriately. Today's date is also noted by
+ * altering td class.
+ * Months count from 1.
+ *
+ * @param int $y Year
+ * @param int $m Month
+ * @param bool $links (default false)
+ * @param string $class
+ * 
+ * @return string
+ *
+ * @todo Provide (prev,next) links, define class variations for different size calendars
+ */
 function cal($y = 0,$m = 0, $links = false, $class='') {
 
 
        // month table - start at 1 to match human usage.
 
        $mtab = array(' ',
-         'January','February','March',
-         'April','May','June',
-         'July','August','September',
-         'October','November','December'
+               'January','February','March',
+               'April','May','June',
+               'July','August','September',
+               'October','November','December'
        );
 
        $thisyear = datetime_convert('UTC',date_default_timezone_get(),'now','Y');
@@ -400,54 +481,63 @@ function cal($y = 0,$m = 0, $links = false, $class='') {
        if(! $m)
                $m = intval($thismonth);
 
-  $dn = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
-  $f = get_first_dim($y,$m);
-  $l = get_dim($y,$m);
-  $d = 1;
-  $dow = 0;
-  $started = false;
+       $dn = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
+       $f = get_first_dim($y,$m);
+       $l = get_dim($y,$m);
+       $d = 1;
+       $dow = 0;
+       $started = false;
 
-  if(($y == $thisyear) && ($m == $thismonth))
-    $tddate = intval(datetime_convert('UTC',date_default_timezone_get(),'now','j'));
+       if(($y == $thisyear) && ($m == $thismonth))
+               $tddate = intval(datetime_convert('UTC',date_default_timezone_get(),'now','j'));
 
        $str_month = day_translate($mtab[$m]);
-  $o = '<table class="calendar' . $class . '">';
-  $o .= "<caption>$str_month $y</caption><tr>";
-  for($a = 0; $a < 7; $a ++)
-     $o .= '<th>' . mb_substr(day_translate($dn[$a]),0,3,'UTF-8') . '</th>';
-  $o .= '</tr><tr>';
-
-  while($d <= $l) {
-    if(($dow == $f) && (! $started))
-      $started = true;
-    $today = (((isset($tddate)) && ($tddate == $d)) ? "class=\"today\" " : '');
-    $o .= "<td $today>";
-       $day = str_replace(' ','&nbsp;',sprintf('%2.2d', $d));
-    if($started) {
-      if(is_array($links) && isset($links[$d]))
-        $o .=  "<a href=\"{$links[$d]}\">$day</a>";
-      else
-        $o .= $day;
-      $d ++;
-    }
-    else
-      $o .= '&nbsp;';
-    $o .= '</td>';
-    $dow ++;
-    if(($dow == 7) && ($d <= $l)) {
-      $dow = 0;
-      $o .= '</tr><tr>';
-    }
-  }
-  if($dow)
-    for($a = $dow; $a < 7; $a ++)
-       $o .= '<td>&nbsp;</td>';
-  $o .= '</tr></table>'."\r\n";
-
-  return $o;
-}}
+       $o = '<table class="calendar' . $class . '">';
+       $o .= "<caption>$str_month $y</caption><tr>";
+       for($a = 0; $a < 7; $a ++)
+               $o .= '<th>' . mb_substr(day_translate($dn[$a]),0,3,'UTF-8') . '</th>';
+
+       $o .= '</tr><tr>';
+
+       while($d <= $l) {
+               if(($dow == $f) && (! $started))
+                       $started = true;
+
+               $today = (((isset($tddate)) && ($tddate == $d)) ? "class=\"today\" " : '');
+               $o .= "<td $today>";
+               $day = str_replace(' ','&nbsp;',sprintf('%2.2d', $d));
+               if($started) {
+                       if(is_array($links) && isset($links[$d]))
+                               $o .=  "<a href=\"{$links[$d]}\">$day</a>";
+                       else
+                               $o .= $day;
 
+                       $d ++;
+               } else {
+                       $o .= '&nbsp;';
+               }
+
+               $o .= '</td>';
+               $dow ++;
+               if(($dow == 7) && ($d <= $l)) {
+                       $dow = 0;
+                       $o .= '</tr><tr>';
+               }
+       }
+       if($dow)
+               for($a = $dow; $a < 7; $a ++)
+                       $o .= '<td>&nbsp;</td>';
 
+       $o .= '</tr></table>'."\r\n";
+
+       return $o;
+}
+
+/**
+ * @brief Create a birthday event.
+ *
+ * Update the year and the birthday.
+ */
 function update_contact_birthdays() {
 
        // This only handles foreign or alien networks where a birthday has been provided.
@@ -474,8 +564,6 @@ function update_contact_birthdays() {
                        $bdtext = sprintf( t('%s\'s birthday'), $rr['name']);
                        $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $rr['url'] . ']' . $rr['name'] . '[/url]') ;
 
-
-
                        $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`,`adjust`)
                                VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d' ) ",
                                intval($rr['uid']),
index 851eb23284685f2ffb932a512b9d32946d8550fb..ddf036f2c1cbe7edfedae0361d99e7523a9fecae 100644 (file)
@@ -453,6 +453,7 @@ function db_definition() {
                                        "keywords" => array("type" => "text", "not null" => "1"),
                                        "gender" => array("type" => "varchar(32)", "not null" => "1", "default" => ""),
                                        "attag" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
+                                       "avatar" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
                                        "photo" => array("type" => "text", "not null" => "1"),
                                        "thumb" => array("type" => "text", "not null" => "1"),
                                        "micro" => array("type" => "text", "not null" => "1"),
@@ -666,9 +667,14 @@ function db_definition() {
                                        "about" => array("type" => "text", "not null" => "1"),
                                        "keywords" => array("type" => "text", "not null" => "1"),
                                        "gender" => array("type" => "varchar(32)", "not null" => "1", "default" => ""),
+                                       "birthday" => array("type" => "varchar(32)", "not null" => "1", "default" => "0000-00-00"),
                                        "community" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
+                                       "hide" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
+                                       "nsfw" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
                                        "network" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
                                        "addr" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
+                                       "notify" => array("type" => "text", "not null" => "1"),
+                                       "alias" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
                                        "generation" => array("type" => "tinyint(3)", "not null" => "1", "default" => "0"),
                                        "server_url" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
                                        ),
@@ -742,21 +748,6 @@ function db_definition() {
                                        "nurl" => array("nurl"),
                                        )
                        );
-       $database["guid"] = array(
-                       "fields" => array(
-                                       "id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
-                                       "guid" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
-                                       "plink" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
-                                       "uri" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
-                                       "network" => array("type" => "varchar(32)", "not null" => "1", "default" => ""),
-                                       ),
-                       "indexes" => array(
-                                       "PRIMARY" => array("id"),
-                                       "guid" => array("guid"),
-                                       "plink" => array("plink"),
-                                       "uri" => array("uri"),
-                                       )
-                       );
        $database["hook"] = array(
                        "fields" => array(
                                        "id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
@@ -795,6 +786,7 @@ function db_definition() {
                                        "uri" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
                                        "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
                                        "contact-id" => array("type" => "int(11)", "not null" => "1", "default" => "0"),
+                                       "gcontact-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
                                        "type" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
                                        "wall" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
                                        "gravity" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"),
@@ -871,6 +863,7 @@ function db_definition() {
                                        "uid_thrparent" => array("uid","thr-parent"),
                                        "uid_parenturi" => array("uid","parent-uri"),
                                        "uid_contactid_created" => array("uid","contact-id","created"),
+                                       "gcontactid_uid_created" => array("gcontact-id","uid","created"),
                                        "wall_body" => array("wall","body(6)"),
                                        "uid_visible_moderated_created" => array("uid","visible","moderated","created"),
                                        "uid_uri" => array("uid","uri"),
@@ -1014,6 +1007,30 @@ function db_definition() {
                                        "receiver-uid" => array("receiver-uid"),
                                        )
                        );
+       $database["oembed"] = array(
+                       "fields" => array(
+                                       "url" => array("type" => "varchar(255)", "not null" => "1", "primary" => "1"),
+                                       "content" => array("type" => "text", "not null" => "1"),
+                                       "created" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"),
+                                       ),
+                       "indexes" => array(
+                                       "PRIMARY" => array("url"),
+                                       "created" => array("created"),
+                                       )
+                       );
+       $database["parsed_url"] = array(
+                       "fields" => array(
+                                       "url" => array("type" => "varchar(255)", "not null" => "1", "primary" => "1"),
+                                       "guessing" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0", "primary" => "1"),
+                                       "oembed" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0", "primary" => "1"),
+                                       "content" => array("type" => "text", "not null" => "1"),
+                                       "created" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"),
+                                       ),
+                       "indexes" => array(
+                                       "PRIMARY" => array("url", "guessing", "oembed"),
+                                       "created" => array("created"),
+                                       )
+                       );
        $database["pconfig"] = array(
                        "fields" => array(
                                        "id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
@@ -1287,6 +1304,7 @@ function db_definition() {
                                        "iid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "primary" => "1"),
                                        "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"),
                                        "contact-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
+                                       "gcontact-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"),
                                        "created" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"),
                                        "edited" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"),
                                        "commented" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"),
@@ -1316,6 +1334,8 @@ function db_definition() {
                                        "uid_network_created" => array("uid","network","created"),
                                        "uid_contactid_commented" => array("uid","contact-id","commented"),
                                        "uid_contactid_created" => array("uid","contact-id","created"),
+                                       "uid_gcontactid_commented" => array("uid","gcontact-id","commented"),
+                                       "uid_gcontactid_created" => array("uid","gcontact-id","created"),
                                        "wall_private_received" => array("wall","private","received"),
                                        "uid_created" => array("uid","created"),
                                        "uid_commented" => array("uid","commented"),
@@ -1334,21 +1354,6 @@ function db_definition() {
                                        "PRIMARY" => array("id"),
                                        )
                        );
-       $database["unique_contacts"] = array(
-                       "fields" => array(
-                                       "id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
-                                       "url" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
-                                       "nick" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
-                                       "name" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
-                                       "avatar" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
-                                       "location" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
-                                       "about" => array("type" => "text", "not null" => "1"),
-                                       ),
-                       "indexes" => array(
-                                       "PRIMARY" => array("id"),
-                                       "url" => array("url"),
-                                       )
-                       );
        $database["user"] = array(
                        "fields" => array(
                                        "uid" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"),
index a8887336194d0083d2d9cdee547d61dacfab2017..021ceb9968a8496e738685021af242f60102aaa4 100644 (file)
@@ -5,6 +5,7 @@ require_once('include/html2plain.php');
 require_once("include/Scrape.php");
 require_once('include/diaspora.php');
 require_once("include/ostatus.php");
+require_once("include/dfrn.php");
 
 function delivery_run(&$argv, &$argc){
        global $a, $db;
@@ -264,8 +265,6 @@ function delivery_run(&$argv, &$argc){
                if(count($r))
                        $contact = $r[0];
 
-               $hubxml = feed_hublinks();
-
                if($contact['self'])
                        continue;
 
@@ -278,138 +277,54 @@ function delivery_run(&$argv, &$argc){
                        case NETWORK_DFRN:
                                logger('notifier: '.$target_item["guid"].' dfrndelivery: ' . $contact['name']);
 
-                               $feed_template = get_markup_template('atom_feed.tpl');
-                               $mail_template = get_markup_template('atom_mail.tpl');
-
-                               $atom = '';
-
-
-                               $birthday = feed_birthday($owner['uid'],$owner['timezone']);
-
-                               if(strlen($birthday))
-                                       $birthday = '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>';
-
-                               $atom .= replace_macros($feed_template, array(
-                                               '$version'      => xmlify(FRIENDICA_VERSION),
-                                               '$feed_id'      => xmlify($a->get_baseurl() . '/profile/' . $owner['nickname'] ),
-                                               '$feed_title'   => xmlify($owner['name']),
-                                               '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', $updated . '+00:00' , ATOM_TIME)) ,
-                                               '$hub'          => $hubxml,
-                                               '$salmon'       => '',  // private feed, we don't use salmon here
-                                               '$name'         => xmlify($owner['name']),
-                                               '$profile_page' => xmlify($owner['url']),
-                                               '$photo'        => xmlify($owner['photo']),
-                                               '$thumb'        => xmlify($owner['thumb']),
-                                               '$picdate'      => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
-                                               '$uridate'      => xmlify(datetime_convert('UTC','UTC',$owner['uri-date']    . '+00:00' , ATOM_TIME)) ,
-                                               '$namdate'      => xmlify(datetime_convert('UTC','UTC',$owner['name-date']   . '+00:00' , ATOM_TIME)) ,
-                                               '$birthday'     => $birthday,
-                                               '$community'    => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
-                               ));
-
-                               if($mail) {
-                                       $public_message = false;  // mail is  not public
-
-                                       $body = fix_private_photos($item['body'],$owner['uid'],null,$message[0]['contact-id']);
-
-                                       $atom .= replace_macros($mail_template, array(
-                                               '$name'         => xmlify($owner['name']),
-                                               '$profile_page' => xmlify($owner['url']),
-                                               '$thumb'        => xmlify($owner['thumb']),
-                                               '$item_id'      => xmlify($item['uri']),
-                                               '$subject'      => xmlify($item['title']),
-                                               '$created'      => xmlify(datetime_convert('UTC', 'UTC', $item['created'] . '+00:00' , ATOM_TIME)),
-                                               '$content'      => xmlify($body),
-                                               '$parent_id'    => xmlify($item['parent-uri'])
-                                       ));
-                               } elseif($fsuggest) {
-                                       $public_message = false;  // suggestions are not public
-
-                                       $sugg_template = get_markup_template('atom_suggest.tpl');
-
-                                       $atom .= replace_macros($sugg_template, array(
-                                               '$name'         => xmlify($item['name']),
-                                               '$url'          => xmlify($item['url']),
-                                               '$photo'        => xmlify($item['photo']),
-                                               '$request'      => xmlify($item['request']),
-                                               '$note'         => xmlify($item['note'])
-                                       ));
-
-                                       // We don't need this any more
-
-                                       q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1",
-                                               intval($item['id'])
-                                       );
-                               } elseif($relocate) {
-                                       $public_message = false;  // suggestions are not public
-
-                                       $sugg_template = get_markup_template('atom_relocate.tpl');
-
-                                       /* get site pubkey. this could be a new installation with no site keys*/
-                                       $pubkey = get_config('system','site_pubkey');
-                                       if(! $pubkey) {
-                                               $res = new_keypair(1024);
-                                               set_config('system','site_prvkey', $res['prvkey']);
-                                               set_config('system','site_pubkey', $res['pubkey']);
-                                       }
-
-                                       $rp = q("SELECT `resource-id` , `scale`, type FROM `photo`
-                                                       WHERE `profile` = 1 AND `uid` = %d ORDER BY scale;", $uid);
-                                       $photos = array();
-                                       $ext = Photo::supportedTypes();
-                                       foreach($rp as $p){
-                                               $photos[$p['scale']] = $a->get_baseurl().'/photo/'.$p['resource-id'].'-'.$p['scale'].'.'.$ext[$p['type']];
-                                       }
-                                       unset($rp, $ext);
-
-                                       $atom .= replace_macros($sugg_template, array(
-                                                               '$name' => xmlify($owner['name']),
-                                                               '$photo' => xmlify($photos[4]),
-                                                               '$thumb' => xmlify($photos[5]),
-                                                               '$micro' => xmlify($photos[6]),
-                                                               '$url' => xmlify($owner['url']),
-                                                               '$request' => xmlify($owner['request']),
-                                                               '$confirm' => xmlify($owner['confirm']),
-                                                               '$notify' => xmlify($owner['notify']),
-                                                               '$poll' => xmlify($owner['poll']),
-                                                               '$sitepubkey' => xmlify(get_config('system','site_pubkey')),
-                                                               //'$pubkey' => xmlify($owner['pubkey']),
-                                                               //'$prvkey' => xmlify($owner['prvkey']),
-                                               ));
-                                       unset($photos);
-                               } elseif($followup) {
+                               if ($mail) {
+                                       $item['body'] = fix_private_photos($item['body'],$owner['uid'],null,$message[0]['contact-id']);
+                                       $atom = dfrn::mail($item, $owner);
+                               } elseif ($fsuggest) {
+                                       $atom = dfrn::fsuggest($item, $owner);
+                                       q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item['id']));
+                               } elseif ($relocate)
+                                       $atom = dfrn::relocate($owner, $uid);
+                               elseif($followup) {
+                                       $msgitems = array();
                                        foreach($items as $item) {  // there is only one item
-                                               if(! $item['parent'])
+                                               if(!$item['parent'])
                                                        continue;
                                                if($item['id'] == $item_id) {
                                                        logger('followup: item: ' . print_r($item,true), LOGGER_DATA);
-                                                       $atom .= atom_entry($item,'text',null,$owner,false);
+                                                       $msgitems[] = $item;
                                                }
                                        }
+                                       $atom = dfrn::entries($msgitems,$owner);
                                } else {
+                                       $msgitems = array();
                                        foreach($items as $item) {
-                                               if(! $item['parent'])
+                                               if(!$item['parent'])
                                                        continue;
 
                                                // private emails may be in included in public conversations. Filter them.
-                                               if(($public_message) && $item['private'] == 1)
+                                               if(($public_message) && $item['private'])
                                                        continue;
 
                                                $item_contact = get_item_contact($item,$icontacts);
-                                               if(! $item_contact)
+                                               if(!$item_contact)
                                                        continue;
 
                                                if($normal_mode) {
-                                                       if($item_id == $item['id'] || $item['id'] == $item['parent'])
-                                                               $atom .= atom_entry($item,'text',null,$owner,true,(($top_level) ? $contact['id'] : 0));
-                                               } else
-                                                       $atom .= atom_entry($item,'text',null,$owner,true);
+                                                       if($item_id == $item['id'] || $item['id'] == $item['parent']) {
+                                                               $item["entry:comment-allow"] = true;
+                                                               $item["entry:cid"] = (($top_level) ? $contact['id'] : 0);
+                                                               $msgitems[] = $item;
+                                                       }
+                                               } else {
+                                                       $item["entry:comment-allow"] = true;
+                                                       $msgitems[] = $item;
+                                               }
                                        }
+                                       $atom = dfrn::entries($msgitems,$owner);
                                }
 
-                               $atom .= '</feed>' . "\r\n";
-
-                               logger('notifier: '.$contact["url"].' '.$target_item["guid"].' entry: '.$atom, LOGGER_DEBUG);
+                               logger('notifier entry: '.$contact["url"].' '.$target_item["guid"].' entry: '.$atom, LOGGER_DEBUG);
 
                                logger('notifier: ' . $atom, LOGGER_DATA);
                                $basepath =  implode('/', array_slice(explode('/',$contact['url']),0,3));
@@ -458,15 +373,14 @@ function delivery_run(&$argv, &$argc){
                                                if (($x[0]['page-flags'] == PAGE_SOAPBOX) AND $top_level)
                                                        break;
 
-                                               require_once('library/simplepie/simplepie.inc');
                                                logger('mod-delivery: local delivery');
-                                               local_delivery($x[0],$atom);
+                                               dfrn::import($atom, $x[0]);
                                                break;
                                        }
                                }
 
                                if(! was_recently_delayed($contact['id']))
-                                       $deliver_status = dfrn_deliver($owner,$contact,$atom);
+                                       $deliver_status = dfrn::deliver($owner,$contact,$atom);
                                else
                                        $deliver_status = (-1);
 
diff --git a/include/dfrn.php b/include/dfrn.php
new file mode 100644 (file)
index 0000000..a96d283
--- /dev/null
@@ -0,0 +1,2422 @@
+<?php
+/**
+ * @file include/dfrn.php
+ * @brief The implementation of the dfrn protocol
+ *
+ * https://github.com/friendica/friendica/wiki/Protocol
+ */
+
+require_once("include/Contact.php");
+require_once("include/ostatus.php");
+require_once("include/enotify.php");
+require_once("include/threads.php");
+require_once("include/socgraph.php");
+require_once("include/items.php");
+require_once("include/tags.php");
+require_once("include/files.php");
+require_once("include/event.php");
+require_once("include/text.php");
+require_once("include/oembed.php");
+require_once("include/html2bbcode.php");
+
+/**
+ * @brief This class contain functions to create and send DFRN XML files
+ *
+ */
+class dfrn {
+
+       const DFRN_TOP_LEVEL = 0;       // Top level posting
+       const DFRN_REPLY = 1;           // Regular reply that is stored locally
+       const DFRN_REPLY_RC = 2;        // Reply that will be relayed
+
+       /**
+        * @brief Generates the atom entries for delivery.php
+        *
+        * This function is used whenever content is transmitted via DFRN.
+        *
+        * @param array $items Item elements
+        * @param array $owner Owner record
+        *
+        * @return string DFRN entries
+        */
+       public static function entries($items,$owner) {
+
+               $doc = new DOMDocument('1.0', 'utf-8');
+               $doc->formatOutput = true;
+
+               $root = self::add_header($doc, $owner, "dfrn:owner", "", false);
+
+               if(! count($items))
+                       return trim($doc->saveXML());
+
+               foreach($items as $item) {
+                       $entry = self::entry($doc, "text", $item, $owner, $item["entry:comment-allow"], $item["entry:cid"]);
+                       $root->appendChild($entry);
+               }
+
+               return(trim($doc->saveXML()));
+       }
+
+       /**
+        * @brief Generate an atom feed for the given user
+        *
+        * This function is called when another server is pulling data from the user feed.
+        *
+        * @param string $dfrn_id DFRN ID from the requesting party
+        * @param string $owner_nick Owner nick name
+        * @param string $last_update Date of the last update
+        * @param int $direction Can be -1, 0 or 1.
+        *
+        * @return string DFRN feed entries
+        */
+       public static function feed($dfrn_id, $owner_nick, $last_update, $direction = 0) {
+
+               $a = get_app();
+
+               $sitefeed    = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
+               $public_feed = (($dfrn_id) ? false : true);
+               $starred     = false;   // not yet implemented, possible security issues
+               $converse    = false;
+
+               if($public_feed && $a->argc > 2) {
+                       for($x = 2; $x < $a->argc; $x++) {
+                               if($a->argv[$x] == 'converse')
+                                       $converse = true;
+                               if($a->argv[$x] == 'starred')
+                                       $starred = true;
+                               if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
+                                       $category = $a->argv[$x+1];
+                       }
+               }
+
+
+
+               // default permissions - anonymous user
+
+               $sql_extra = " AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid`  = '' AND `item`.`deny_gid`  = '' ";
+
+               $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
+                       FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
+                       WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
+                       dbesc($owner_nick)
+               );
+
+               if(! count($r))
+                       killme();
+
+               $owner = $r[0];
+               $owner_id = $owner['user_uid'];
+               $owner_nick = $owner['nickname'];
+
+               $sql_post_table = "";
+               $visibility = "";
+
+               if(! $public_feed) {
+
+                       $sql_extra = '';
+                       switch($direction) {
+                               case (-1):
+                                       $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
+                                       $my_id = $dfrn_id;
+                                       break;
+                               case 0:
+                                       $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
+                                       $my_id = '1:' . $dfrn_id;
+                                       break;
+                               case 1:
+                                       $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
+                                       $my_id = '0:' . $dfrn_id;
+                                       break;
+                               default:
+                                       return false;
+                                       break; // NOTREACHED
+                       }
+
+                       $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
+                               intval($owner_id)
+                       );
+
+                       if(! count($r))
+                               killme();
+
+                       $contact = $r[0];
+                       require_once('include/security.php');
+                       $groups = init_groups_visitor($contact['id']);
+
+                       if(count($groups)) {
+                               for($x = 0; $x < count($groups); $x ++)
+                                       $groups[$x] = '<' . intval($groups[$x]) . '>' ;
+                               $gs = implode('|', $groups);
+                       } else
+                               $gs = '<<>>' ; // Impossible to match
+
+                       $sql_extra = sprintf("
+                               AND ( `allow_cid` = '' OR     `allow_cid` REGEXP '<%d>' )
+                               AND ( `deny_cid`  = '' OR NOT `deny_cid`  REGEXP '<%d>' )
+                               AND ( `allow_gid` = '' OR     `allow_gid` REGEXP '%s' )
+                               AND ( `deny_gid`  = '' OR NOT `deny_gid`  REGEXP '%s')
+                       ",
+                               intval($contact['id']),
+                               intval($contact['id']),
+                               dbesc($gs),
+                               dbesc($gs)
+                       );
+               }
+
+               if($public_feed)
+                       $sort = 'DESC';
+               else
+                       $sort = 'ASC';
+
+               $date_field = "`changed`";
+               $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
+
+               if(! strlen($last_update))
+                       $last_update = 'now -30 days';
+
+               if(isset($category)) {
+                       $sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
+                                       dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
+                       //$sql_extra .= file_tag_file_query('item',$category,'category');
+               }
+
+               if($public_feed) {
+                       if(! $converse)
+                               $sql_extra .= " AND `contact`.`self` = 1 ";
+               }
+
+               $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
+
+               //      AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
+               //      dbesc($check_date),
+
+               $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
+                       `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
+                       `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
+                       `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
+                       `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
+                       `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
+                       FROM `item` $sql_post_table
+                       INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
+                       AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
+                       LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
+                       WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
+                       AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
+                       $sql_extra
+                       ORDER BY $sql_order LIMIT 0, 300",
+                       intval($owner_id),
+                       dbesc($check_date),
+                       dbesc($sort)
+               );
+
+               // Will check further below if this actually returned results.
+               // We will provide an empty feed if that is the case.
+
+               $items = $r;
+
+               $doc = new DOMDocument('1.0', 'utf-8');
+               $doc->formatOutput = true;
+
+               $alternatelink = $owner['url'];
+
+               if(isset($category))
+                       $alternatelink .= "/category/".$category;
+
+               if ($public_feed)
+                       $author = "dfrn:owner";
+               else
+                       $author = "author";
+
+               $root = self::add_header($doc, $owner, $author, $alternatelink, true);
+
+               // This hook can't work anymore
+               //      call_hooks('atom_feed', $atom);
+
+               if(! count($items)) {
+                       $atom = trim($doc->saveXML());
+
+                       call_hooks('atom_feed_end', $atom);
+
+                       return $atom;
+               }
+
+               foreach($items as $item) {
+
+                       // prevent private email from leaking.
+                       if($item['network'] === NETWORK_MAIL)
+                               continue;
+
+                       // public feeds get html, our own nodes use bbcode
+
+                       if($public_feed) {
+                               $type = 'html';
+                               // catch any email that's in a public conversation and make sure it doesn't leak
+                               if($item['private'])
+                                       continue;
+                       } else
+                               $type = 'text';
+
+                       $entry = self::entry($doc, $type, $item, $owner, true);
+                       $root->appendChild($entry);
+
+               }
+
+               $atom = trim($doc->saveXML());
+
+               call_hooks('atom_feed_end', $atom);
+
+               return $atom;
+       }
+
+       /**
+        * @brief Create XML text for DFRN mails
+        *
+        * @param array $item message elements
+        * @param array $owner Owner record
+        *
+        * @return string DFRN mail
+        */
+       public static function mail($item, $owner) {
+               $doc = new DOMDocument('1.0', 'utf-8');
+               $doc->formatOutput = true;
+
+               $root = self::add_header($doc, $owner, "dfrn:owner", "", false);
+
+               $mail = $doc->createElement("dfrn:mail");
+               $sender = $doc->createElement("dfrn:sender");
+
+               xml_add_element($doc, $sender, "dfrn:name", $owner['name']);
+               xml_add_element($doc, $sender, "dfrn:uri", $owner['url']);
+               xml_add_element($doc, $sender, "dfrn:avatar", $owner['thumb']);
+
+               $mail->appendChild($sender);
+
+               xml_add_element($doc, $mail, "dfrn:id", $item['uri']);
+               xml_add_element($doc, $mail, "dfrn:in-reply-to", $item['parent-uri']);
+               xml_add_element($doc, $mail, "dfrn:sentdate", datetime_convert('UTC', 'UTC', $item['created'] . '+00:00' , ATOM_TIME));
+               xml_add_element($doc, $mail, "dfrn:subject", $item['title']);
+               xml_add_element($doc, $mail, "dfrn:content", $item['body']);
+
+               $root->appendChild($mail);
+
+               return(trim($doc->saveXML()));
+       }
+
+       /**
+        * @brief Create XML text for DFRN friend suggestions
+        *
+        * @param array $item suggestion elements
+        * @param array $owner Owner record
+        *
+        * @return string DFRN suggestions
+        */
+       public static function fsuggest($item, $owner) {
+               $doc = new DOMDocument('1.0', 'utf-8');
+               $doc->formatOutput = true;
+
+               $root = self::add_header($doc, $owner, "dfrn:owner", "", false);
+
+               $suggest = $doc->createElement("dfrn:suggest");
+
+               xml_add_element($doc, $suggest, "dfrn:url", $item['url']);
+               xml_add_element($doc, $suggest, "dfrn:name", $item['name']);
+               xml_add_element($doc, $suggest, "dfrn:photo", $item['photo']);
+               xml_add_element($doc, $suggest, "dfrn:request", $item['request']);
+               xml_add_element($doc, $suggest, "dfrn:note", $item['note']);
+
+               $root->appendChild($suggest);
+
+               return(trim($doc->saveXML()));
+       }
+
+       /**
+        * @brief Create XML text for DFRN relocations
+        *
+        * @param array $owner Owner record
+        * @param int $uid User ID
+        *
+        * @return string DFRN relocations
+        */
+       public static function relocate($owner, $uid) {
+
+               /* get site pubkey. this could be a new installation with no site keys*/
+               $pubkey = get_config('system','site_pubkey');
+               if(! $pubkey) {
+                       $res = new_keypair(1024);
+                       set_config('system','site_prvkey', $res['prvkey']);
+                       set_config('system','site_pubkey', $res['pubkey']);
+               }
+
+               $rp = q("SELECT `resource-id` , `scale`, type FROM `photo`
+                               WHERE `profile` = 1 AND `uid` = %d ORDER BY scale;", $uid);
+               $photos = array();
+               $ext = Photo::supportedTypes();
+
+               foreach($rp as $p)
+                       $photos[$p['scale']] = app::get_baseurl().'/photo/'.$p['resource-id'].'-'.$p['scale'].'.'.$ext[$p['type']];
+
+               unset($rp, $ext);
+
+               $doc = new DOMDocument('1.0', 'utf-8');
+               $doc->formatOutput = true;
+
+               $root = self::add_header($doc, $owner, "dfrn:owner", "", false);
+
+               $relocate = $doc->createElement("dfrn:relocate");
+
+               xml_add_element($doc, $relocate, "dfrn:url", $owner['url']);
+               xml_add_element($doc, $relocate, "dfrn:name", $owner['name']);
+               xml_add_element($doc, $relocate, "dfrn:photo", $photos[4]);
+               xml_add_element($doc, $relocate, "dfrn:thumb", $photos[5]);
+               xml_add_element($doc, $relocate, "dfrn:micro", $photos[6]);
+               xml_add_element($doc, $relocate, "dfrn:request", $owner['request']);
+               xml_add_element($doc, $relocate, "dfrn:confirm", $owner['confirm']);
+               xml_add_element($doc, $relocate, "dfrn:notify", $owner['notify']);
+               xml_add_element($doc, $relocate, "dfrn:poll", $owner['poll']);
+               xml_add_element($doc, $relocate, "dfrn:sitepubkey", get_config('system','site_pubkey'));
+
+               $root->appendChild($relocate);
+
+               return(trim($doc->saveXML()));
+       }
+
+       /**
+        * @brief Adds the header elements for the DFRN protocol
+        *
+        * @param object $doc XML document
+        * @param array $owner Owner record
+        * @param string $authorelement Element name for the author
+        * @param string $alternatelink link to profile or category
+        * @param bool $public Is it a header for public posts?
+        *
+        * @return object XML root object
+        */
+       private function add_header($doc, $owner, $authorelement, $alternatelink = "", $public = false) {
+
+               if ($alternatelink == "")
+                       $alternatelink = $owner['url'];
+
+               $root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed');
+               $doc->appendChild($root);
+
+               $root->setAttribute("xmlns:thr", NAMESPACE_THREAD);
+               $root->setAttribute("xmlns:at", NAMESPACE_TOMB);
+               $root->setAttribute("xmlns:media", NAMESPACE_MEDIA);
+               $root->setAttribute("xmlns:dfrn", NAMESPACE_DFRN);
+               $root->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY);
+               $root->setAttribute("xmlns:georss", NAMESPACE_GEORSS);
+               $root->setAttribute("xmlns:poco", NAMESPACE_POCO);
+               $root->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS);
+               $root->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET);
+
+               xml_add_element($doc, $root, "id", app::get_baseurl()."/profile/".$owner["nick"]);
+               xml_add_element($doc, $root, "title", $owner["name"]);
+
+               $attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION);
+               xml_add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes);
+
+               $attributes = array("rel" => "license", "href" => "http://creativecommons.org/licenses/by/3.0/");
+               xml_add_element($doc, $root, "link", "", $attributes);
+
+               $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $alternatelink);
+               xml_add_element($doc, $root, "link", "", $attributes);
+
+
+               if ($public) {
+                       // DFRN itself doesn't uses this. But maybe someone else wants to subscribe to the public feed.
+                       ostatus_hublinks($doc, $root);
+
+                       $attributes = array("rel" => "salmon", "href" => app::get_baseurl()."/salmon/".$owner["nick"]);
+                       xml_add_element($doc, $root, "link", "", $attributes);
+
+                       $attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-replies", "href" => app::get_baseurl()."/salmon/".$owner["nick"]);
+                       xml_add_element($doc, $root, "link", "", $attributes);
+
+                       $attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-mention", "href" => app::get_baseurl()."/salmon/".$owner["nick"]);
+                       xml_add_element($doc, $root, "link", "", $attributes);
+               }
+
+               if ($owner['page-flags'] == PAGE_COMMUNITY)
+                       xml_add_element($doc, $root, "dfrn:community", 1);
+
+               /// @todo We need a way to transmit the different page flags like "PAGE_PRVGROUP"
+
+               xml_add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME));
+
+               $author = self::add_author($doc, $owner, $authorelement, $public);
+               $root->appendChild($author);
+
+               return $root;
+       }
+
+       /**
+        * @brief Adds the author element in the header for the DFRN protocol
+        *
+        * @param object $doc XML document
+        * @param array $owner Owner record
+        * @param string $authorelement Element name for the author
+        *
+        * @return object XML author object
+        */
+       private function add_author($doc, $owner, $authorelement, $public) {
+
+               $author = $doc->createElement($authorelement);
+
+               $namdate = datetime_convert('UTC', 'UTC', $owner['name-date'].'+00:00' , ATOM_TIME);
+               $uridate = datetime_convert('UTC', 'UTC', $owner['uri-date'].'+00:00', ATOM_TIME);
+               $picdate = datetime_convert('UTC', 'UTC', $owner['avatar-date'].'+00:00', ATOM_TIME);
+
+               $attributes = array("dfrn:updated" => $namdate);
+               xml_add_element($doc, $author, "name", $owner["name"], $attributes);
+
+               $attributes = array("dfrn:updated" => $namdate);
+               xml_add_element($doc, $author, "uri", app::get_baseurl().'/profile/'.$owner["nickname"], $attributes);
+
+               $attributes = array("dfrn:updated" => $namdate);
+               xml_add_element($doc, $author, "dfrn:handle", $owner["addr"], $attributes);
+
+               $attributes = array("rel" => "photo", "type" => "image/jpeg", "dfrn:updated" => $picdate,
+                                       "media:width" => 175, "media:height" => 175, "href" => $owner['photo']);
+               xml_add_element($doc, $author, "link", "", $attributes);
+
+               $attributes = array("rel" => "avatar", "type" => "image/jpeg", "dfrn:updated" => $picdate,
+                                       "media:width" => 175, "media:height" => 175, "href" => $owner['photo']);
+               xml_add_element($doc, $author, "link", "", $attributes);
+
+               $birthday = feed_birthday($owner['user_uid'], $owner['timezone']);
+
+               if ($birthday)
+                       xml_add_element($doc, $author, "dfrn:birthday", $birthday);
+
+               // The following fields will only be generated if this isn't for a public feed
+               if ($public)
+                       return $author;
+
+               // Only show contact details when we are allowed to
+               $r = q("SELECT `profile`.`about`, `profile`.`name`, `profile`.`homepage`, `user`.`nickname`, `user`.`timezone`,
+                               `profile`.`locality`, `profile`.`region`, `profile`.`country-name`, `profile`.`pub_keywords`, `profile`.`dob`
+                       FROM `profile`
+                               INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
+                               WHERE `profile`.`is-default` AND NOT `user`.`hidewall` AND `user`.`uid` = %d",
+                       intval($owner['user_uid']));
+               if ($r) {
+                       $profile = $r[0];
+                       xml_add_element($doc, $author, "poco:displayName", $profile["name"]);
+                       xml_add_element($doc, $author, "poco:updated", $namdate);
+
+                       if (trim($profile["dob"]) != "0000-00-00")
+                               xml_add_element($doc, $author, "poco:birthday", "0000-".date("m-d", strtotime($profile["dob"])));
+
+                       xml_add_element($doc, $author, "poco:note", $profile["about"]);
+                       xml_add_element($doc, $author, "poco:preferredUsername", $profile["nickname"]);
+
+                       $savetz = date_default_timezone_get();
+                       date_default_timezone_set($profile["timezone"]);
+                       xml_add_element($doc, $author, "poco:utcOffset", date("P"));
+                       date_default_timezone_set($savetz);
+
+                       if (trim($profile["homepage"]) != "") {
+                               $urls = $doc->createElement("poco:urls");
+                               xml_add_element($doc, $urls, "poco:type", "homepage");
+                               xml_add_element($doc, $urls, "poco:value", $profile["homepage"]);
+                               xml_add_element($doc, $urls, "poco:primary", "true");
+                               $author->appendChild($urls);
+                       }
+
+                       if (trim($profile["pub_keywords"]) != "") {
+                               $keywords = explode(",", $profile["pub_keywords"]);
+
+                               foreach ($keywords AS $keyword)
+                                       xml_add_element($doc, $author, "poco:tags", trim($keyword));
+
+                       }
+
+                       /// @todo When we are having the XMPP address in the profile we should propagate it here
+                       $xmpp = "";
+                       if (trim($xmpp) != "") {
+                               $ims = $doc->createElement("poco:ims");
+                               xml_add_element($doc, $ims, "poco:type", "xmpp");
+                               xml_add_element($doc, $ims, "poco:value", $xmpp);
+                               xml_add_element($doc, $ims, "poco:primary", "true");
+                               $author->appendChild($ims);
+                       }
+
+                       if (trim($profile["locality"].$profile["region"].$profile["country-name"]) != "") {
+                               $element = $doc->createElement("poco:address");
+
+                               xml_add_element($doc, $element, "poco:formatted", formatted_location($profile));
+
+                               if (trim($profile["locality"]) != "")
+                                       xml_add_element($doc, $element, "poco:locality", $profile["locality"]);
+
+                               if (trim($profile["region"]) != "")
+                                       xml_add_element($doc, $element, "poco:region", $profile["region"]);
+
+                               if (trim($profile["country-name"]) != "")
+                                       xml_add_element($doc, $element, "poco:country", $profile["country-name"]);
+
+                               $author->appendChild($element);
+                       }
+               }
+
+               return $author;
+       }
+
+       /**
+        * @brief Adds the author elements in the "entry" elements of the DFRN protocol
+        *
+        * @param object $doc XML document
+        * @param string $element Element name for the author
+        * @param string $contact_url Link of the contact
+        * @param array $items Item elements
+        *
+        * @return object XML author object
+        */
+       private function add_entry_author($doc, $element, $contact_url, $item) {
+
+               $contact = get_contact_details_by_url($contact_url, $item["uid"]);
+
+               $author = $doc->createElement($element);
+               xml_add_element($doc, $author, "name", $contact["name"]);
+               xml_add_element($doc, $author, "uri", $contact["url"]);
+               xml_add_element($doc, $author, "dfrn:handle", $contact["addr"]);
+
+               /// @Todo
+               /// - Check real image type and image size
+               /// - Check which of these boths elements we should use
+               $attributes = array(
+                               "rel" => "photo",
+                               "type" => "image/jpeg",
+                               "media:width" => 80,
+                               "media:height" => 80,
+                               "href" => $contact["photo"]);
+               xml_add_element($doc, $author, "link", "", $attributes);
+
+               $attributes = array(
+                               "rel" => "avatar",
+                               "type" => "image/jpeg",
+                               "media:width" => 80,
+                               "media:height" => 80,
+                               "href" => $contact["photo"]);
+               xml_add_element($doc, $author, "link", "", $attributes);
+
+               return $author;
+       }
+
+       /**
+        * @brief Adds the activity elements
+        *
+        * @param object $doc XML document
+        * @param string $element Element name for the activity
+        * @param string $activity activity value
+        *
+        * @return object XML activity object
+        */
+       private function create_activity($doc, $element, $activity) {
+
+               if($activity) {
+                       $entry = $doc->createElement($element);
+
+                       $r = parse_xml_string($activity, false);
+                       if(!$r)
+                               return false;
+                       if($r->type)
+                               xml_add_element($doc, $entry, "activity:object-type", $r->type);
+                       if($r->id)
+                               xml_add_element($doc, $entry, "id", $r->id);
+                       if($r->title)
+                               xml_add_element($doc, $entry, "title", $r->title);
+                       if($r->link) {
+                               if(substr($r->link,0,1) === '<') {
+                                       if(strstr($r->link,'&') && (! strstr($r->link,'&amp;')))
+                                               $r->link = str_replace('&','&amp;', $r->link);
+
+                                       $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
+
+                                       // XML does need a single element as root element so we add a dummy element here
+                                       $data = parse_xml_string("<dummy>".$r->link."</dummy>", false);
+                                       if (is_object($data)) {
+                                               foreach ($data->link AS $link) {
+                                                       $attributes = array();
+                                                       foreach ($link->attributes() AS $parameter => $value)
+                                                               $attributes[$parameter] = $value;
+                                                       xml_add_element($doc, $entry, "link", "", $attributes);
+                                               }
+                                       }
+                               } else {
+                                       $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $r->link);
+                                       xml_add_element($doc, $entry, "link", "", $attributes);
+                               }
+                       }
+                       if($r->content)
+                               xml_add_element($doc, $entry, "content", bbcode($r->content), array("type" => "html"));
+
+                       return $entry;
+               }
+
+               return false;
+       }
+
+       /**
+        * @brief Adds the elements for attachments
+        *
+        * @param object $doc XML document
+        * @param object $root XML root
+        * @param array $item Item element
+        *
+        * @return object XML attachment object
+        */
+       private function get_attachment($doc, $root, $item) {
+               $arr = explode('[/attach],',$item['attach']);
+               if(count($arr)) {
+                       foreach($arr as $r) {
+                               $matches = false;
+                               $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
+                               if($cnt) {
+                                       $attributes = array("rel" => "enclosure",
+                                                       "href" => $matches[1],
+                                                       "type" => $matches[3]);
+
+                                       if(intval($matches[2]))
+                                               $attributes["length"] = intval($matches[2]);
+
+                                       if(trim($matches[4]) != "")
+                                               $attributes["title"] = trim($matches[4]);
+
+                                       xml_add_element($doc, $root, "link", "", $attributes);
+                               }
+                       }
+               }
+       }
+
+       /**
+        * @brief Adds the "entry" elements for the DFRN protocol
+        *
+        * @param object $doc XML document
+        * @param string $type "text" or "html"
+        * @param array $item Item element
+        * @param array $owner Owner record
+        * @param bool $comment Trigger the sending of the "comment" element
+        * @param int $cid Contact ID of the recipient
+        *
+        * @return object XML entry object
+        */
+       private function entry($doc, $type, $item, $owner, $comment = false, $cid = 0) {
+
+               $mentioned = array();
+
+               if(!$item['parent'])
+                       return;
+
+               if($item['deleted']) {
+                       $attributes = array("ref" => $item['uri'], "when" => datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME));
+                       return xml_create_element($doc, "at:deleted-entry", "", $attributes);
+               }
+
+               $entry = $doc->createElement("entry");
+
+               if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
+                       $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
+               else
+                       $body = $item['body'];
+
+               if ($type == 'html') {
+                       $htmlbody = $body;
+
+                       if ($item['title'] != "")
+                               $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
+
+                       $htmlbody = bbcode($htmlbody, false, false, 7);
+               }
+
+               $author = self::add_entry_author($doc, "author", $item["author-link"], $item);
+               $entry->appendChild($author);
+
+               $dfrnowner = self::add_entry_author($doc, "dfrn:owner", $item["owner-link"], $item);
+               $entry->appendChild($dfrnowner);
+
+               if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
+                       $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
+                       $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
+                       $attributes = array("ref" => $parent_item, "type" => "text/html",
+                                               "href" => app::get_baseurl().'/display/'.$parent[0]['guid'],
+                                               "dfrn:diaspora_guid" => $parent[0]['guid']);
+                       xml_add_element($doc, $entry, "thr:in-reply-to", "", $attributes);
+               }
+
+               xml_add_element($doc, $entry, "id", $item["uri"]);
+               xml_add_element($doc, $entry, "title", $item["title"]);
+
+               xml_add_element($doc, $entry, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME));
+               xml_add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME));
+
+               // "dfrn:env" is used to read the content
+               xml_add_element($doc, $entry, "dfrn:env", base64url_encode($body, true));
+
+               // The "content" field is not read by the receiver. We could remove it when the type is "text"
+               // We keep it at the moment, maybe there is some old version that doesn't read "dfrn:env"
+               xml_add_element($doc, $entry, "content", (($type === 'html') ? $htmlbody : $body), array("type" => $type));
+
+               // We save this value in "plink". Maybe we should read it from there as well?
+               xml_add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html",
+                                                               "href" => app::get_baseurl()."/display/".$item["guid"]));
+
+               // "comment-allow" is some old fashioned stuff for old Friendica versions.
+               // It is included in the rewritten code for completeness
+               if ($comment)
+                       xml_add_element($doc, $entry, "dfrn:comment-allow", intval($item['last-child']));
+
+               if($item['location'])
+                       xml_add_element($doc, $entry, "dfrn:location", $item['location']);
+
+               if($item['coord'])
+                       xml_add_element($doc, $entry, "georss:point", $item['coord']);
+
+               if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
+                       xml_add_element($doc, $entry, "dfrn:private", (($item['private']) ? $item['private'] : 1));
+
+               if($item['extid'])
+                       xml_add_element($doc, $entry, "dfrn:extid", $item['extid']);
+
+               if($item['bookmark'])
+                       xml_add_element($doc, $entry, "dfrn:bookmark", "true");
+
+               if($item['app'])
+                       xml_add_element($doc, $entry, "statusnet:notice_info", "", array("local_id" => $item['id'], "source" => $item['app']));
+
+               xml_add_element($doc, $entry, "dfrn:diaspora_guid", $item["guid"]);
+
+               // The signed text contains the content in Markdown, the sender handle and the signatur for the content
+               // It is needed for relayed comments to Diaspora.
+               if($item['signed_text']) {
+                       $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
+                       xml_add_element($doc, $entry, "dfrn:diaspora_signature", $sign);
+               }
+
+               xml_add_element($doc, $entry, "activity:verb", construct_verb($item));
+
+               if ($item['object-type'] != "")
+                       xml_add_element($doc, $entry, "activity:object-type", $item['object-type']);
+               elseif ($item['id'] == $item['parent'])
+                       xml_add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE);
+               else
+                       xml_add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_COMMENT);
+
+               $actobj = self::create_activity($doc, "activity:object", $item['object']);
+               if ($actobj)
+                       $entry->appendChild($actobj);
+
+               $actarg = self::create_activity($doc, "activity:target", $item['target']);
+               if ($actarg)
+                       $entry->appendChild($actarg);
+
+               $tags = item_getfeedtags($item);
+
+               if(count($tags)) {
+                       foreach($tags as $t)
+                               if (($type != 'html') OR ($t[0] != "@"))
+                                       xml_add_element($doc, $entry, "category", "", array("scheme" => "X-DFRN:".$t[0].":".$t[1], "term" => $t[2]));
+               }
+
+               if(count($tags))
+                       foreach($tags as $t)
+                               if ($t[0] == "@")
+                                       $mentioned[$t[1]] = $t[1];
+
+               foreach ($mentioned AS $mention) {
+                       $r = q("SELECT `forum`, `prv` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s'",
+                               intval($owner["uid"]),
+                               dbesc(normalise_link($mention)));
+                       if ($r[0]["forum"] OR $r[0]["prv"])
+                               xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned",
+                                                                                       "ostatus:object-type" => ACTIVITY_OBJ_GROUP,
+                                                                                       "href" => $mention));
+                       else
+                               xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned",
+                                                                                       "ostatus:object-type" => ACTIVITY_OBJ_PERSON,
+                                                                                       "href" => $mention));
+               }
+
+               self::get_attachment($doc, $entry, $item);
+
+               return $entry;
+       }
+
+       /**
+        * @brief Delivers the atom content to the contacts
+        *
+        * @param array $owner Owner record
+        * @param array $contactr Contact record of the receiver
+        * @param string $atom Content that will be transmitted
+        * @param bool $dissolve (to be documented)
+        *
+        * @return int Deliver status. -1 means an error.
+        */
+       public static function deliver($owner,$contact,$atom, $dissolve = false) {
+
+               $a = get_app();
+
+               $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
+
+               if($contact['duplex'] && $contact['dfrn-id'])
+                       $idtosend = '0:' . $orig_id;
+               if($contact['duplex'] && $contact['issued-id'])
+                       $idtosend = '1:' . $orig_id;
+
+
+               $rino = get_config('system','rino_encrypt');
+               $rino = intval($rino);
+               // use RINO1 if mcrypt isn't installed and RINO2 was selected
+               if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
+
+               logger("Local rino version: ". $rino, LOGGER_DEBUG);
+
+               $ssl_val = intval(get_config('system','ssl_policy'));
+               $ssl_policy = '';
+
+               switch($ssl_val){
+                       case SSL_POLICY_FULL:
+                               $ssl_policy = 'full';
+                               break;
+                       case SSL_POLICY_SELFSIGN:
+                               $ssl_policy = 'self';
+                               break;
+                       case SSL_POLICY_NONE:
+                       default:
+                               $ssl_policy = 'none';
+                               break;
+               }
+
+               $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
+
+               logger('dfrn_deliver: ' . $url);
+
+               $xml = fetch_url($url);
+
+               $curl_stat = $a->get_curl_code();
+               if(! $curl_stat)
+                       return(-1); // timed out
+
+               logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
+
+               if(! $xml)
+                       return 3;
+
+               if(strpos($xml,'<?xml') === false) {
+                       logger('dfrn_deliver: no valid XML returned');
+                       logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
+                       return 3;
+               }
+
+               $res = parse_xml_string($xml);
+
+               if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
+                       return (($res->status) ? $res->status : 3);
+
+               $postvars     = array();
+               $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
+               $challenge    = hex2bin((string) $res->challenge);
+               $perm         = (($res->perm) ? $res->perm : null);
+               $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
+               $rino_remote_version = intval($res->rino);
+               $page         = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
+
+               logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
+
+               if($owner['page-flags'] == PAGE_PRVGROUP)
+                       $page = 2;
+
+               $final_dfrn_id = '';
+
+               if($perm) {
+                       if((($perm == 'rw') && (! intval($contact['writable'])))
+                               || (($perm == 'r') && (intval($contact['writable'])))) {
+                               q("update contact set writable = %d where id = %d",
+                                       intval(($perm == 'rw') ? 1 : 0),
+                                       intval($contact['id'])
+                               );
+                               $contact['writable'] = (string) 1 - intval($contact['writable']);
+                       }
+               }
+
+               if(($contact['duplex'] && strlen($contact['pubkey']))
+                       || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
+                       || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
+                       openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
+                       openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
+               } else {
+                       openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
+                       openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
+               }
+
+               $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
+
+               if(strpos($final_dfrn_id,':') == 1)
+                       $final_dfrn_id = substr($final_dfrn_id,2);
+
+               if($final_dfrn_id != $orig_id) {
+                       logger('dfrn_deliver: wrong dfrn_id.');
+                       // did not decode properly - cannot trust this site
+                       return 3;
+               }
+
+               $postvars['dfrn_id']      = $idtosend;
+               $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
+               if($dissolve)
+                       $postvars['dissolve'] = '1';
+
+
+               if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
+                       $postvars['data'] = $atom;
+                       $postvars['perm'] = 'rw';
+               } else {
+                       $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
+                       $postvars['perm'] = 'r';
+               }
+
+               $postvars['ssl_policy'] = $ssl_policy;
+
+               if($page)
+                       $postvars['page'] = $page;
+
+
+               if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
+                       logger('rino version: '. $rino_remote_version);
+
+                       switch($rino_remote_version) {
+                               case 1:
+                                       // Deprecated rino version!
+                                       $key = substr(random_string(),0,16);
+                                       $data = aes_encrypt($postvars['data'],$key);
+                                       break;
+                               case 2:
+                                       // RINO 2 based on php-encryption
+                                       try {
+                                               $key = Crypto::createNewRandomKey();
+                                       } catch (CryptoTestFailed $ex) {
+                                               logger('Cannot safely create a key');
+                                               return -1;
+                                       } catch (CannotPerformOperation $ex) {
+                                               logger('Cannot safely create a key');
+                                               return -1;
+                                       }
+                                       try {
+                                               $data = Crypto::encrypt($postvars['data'], $key);
+                                       } catch (CryptoTestFailed $ex) {
+                                               logger('Cannot safely perform encryption');
+                                               return -1;
+                                       } catch (CannotPerformOperation $ex) {
+                                               logger('Cannot safely perform encryption');
+                                               return -1;
+                                       }
+                                       break;
+                               default:
+                                       logger("rino: invalid requested verision '$rino_remote_version'");
+                                       return -1;
+                       }
+
+                       $postvars['rino'] = $rino_remote_version;
+                       $postvars['data'] = bin2hex($data);
+
+                       #logger('rino: sent key = ' . $key, LOGGER_DEBUG);
+
+
+                       if($dfrn_version >= 2.1) {
+                               if(($contact['duplex'] && strlen($contact['pubkey']))
+                                       || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
+                                       || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey'])))
+
+                                       openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
+                               else
+                                       openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
+
+                       } else {
+                               if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY))
+                                       openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
+                               else
+                                       openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
+
+                       }
+
+                       logger('md5 rawkey ' . md5($postvars['key']));
+
+                       $postvars['key'] = bin2hex($postvars['key']);
+               }
+
+
+               logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
+
+               $xml = post_url($contact['notify'],$postvars);
+
+               logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
+
+               $curl_stat = $a->get_curl_code();
+               if((! $curl_stat) || (! strlen($xml)))
+                       return(-1); // timed out
+
+               if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
+                       return(-1);
+
+               if(strpos($xml,'<?xml') === false) {
+                       logger('dfrn_deliver: phase 2: no valid XML returned');
+                       logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
+                       return 3;
+               }
+
+               if($contact['term-date'] != '0000-00-00 00:00:00') {
+                       logger("dfrn_deliver: $url back from the dead - removing mark for death");
+                       require_once('include/Contact.php');
+                       unmark_for_death($contact);
+               }
+
+               $res = parse_xml_string($xml);
+
+               return $res->status;
+       }
+
+       /**
+        * @brief Add new birthday event for this person
+        *
+        * @param array $contact Contact record
+        * @param string $birthday Birthday of the contact
+        *
+        */
+       private function birthday_event($contact, $birthday) {
+
+               logger("updating birthday: ".$birthday." for contact ".$contact["id"]);
+
+               $bdtext = sprintf(t("%s\'s birthday"), $contact["name"]);
+               $bdtext2 = sprintf(t("Happy Birthday %s"), " [url=".$contact["url"]."]".$contact["name"]."[/url]") ;
+
+
+               $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
+                       VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s') ",
+                       intval($contact["uid"]),
+                       intval($contact["id"]),
+                       dbesc(datetime_convert()),
+                       dbesc(datetime_convert()),
+                       dbesc(datetime_convert("UTC","UTC", $birthday)),
+                       dbesc(datetime_convert("UTC","UTC", $birthday." + 1 day ")),
+                       dbesc($bdtext),
+                       dbesc($bdtext2),
+                       dbesc("birthday")
+               );
+       }
+
+       /**
+        * @brief Fetch the author data from head or entry items
+        *
+        * @param object $xpath XPath object
+        * @param object $context In which context should the data be searched
+        * @param array $importer Record of the importer user mixed with contact of the content
+        * @param string $element Element name from which the data is fetched
+        * @param bool $onlyfetch Should the data only be fetched or should it update the contact record as well
+        *
+        * @return Returns an array with relevant data of the author
+        */
+       private function fetchauthor($xpath, $context, $importer, $element, $onlyfetch) {
+
+               $author = array();
+               $author["name"] = $xpath->evaluate($element."/atom:name/text()", $context)->item(0)->nodeValue;
+               $author["link"] = $xpath->evaluate($element."/atom:uri/text()", $context)->item(0)->nodeValue;
+
+               $r = q("SELECT `id`, `uid`, `network`, `avatar-date`, `name-date`, `uri-date`, `addr`,
+                               `name`, `nick`, `about`, `location`, `keywords`, `bdyear`, `bd`
+                               FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'",
+                       intval($importer["uid"]), dbesc(normalise_link($author["link"])), dbesc(NETWORK_STATUSNET));
+               if ($r) {
+                       $contact = $r[0];
+                       $author["contact-id"] = $r[0]["id"];
+                       $author["network"] = $r[0]["network"];
+               } else {
+                       $author["contact-id"] = $importer["id"];
+                       $author["network"] = $importer["network"];
+                       $onlyfetch = true;
+               }
+
+               // Until now we aren't serving different sizes - but maybe later
+               $avatarlist = array();
+               // @todo check if "avatar" or "photo" would be the best field in the specification
+               $avatars = $xpath->query($element."/atom:link[@rel='avatar']", $context);
+               foreach($avatars AS $avatar) {
+                       $href = "";
+                       $width = 0;
+                       foreach($avatar->attributes AS $attributes) {
+                               if ($attributes->name == "href")
+                                       $href = $attributes->textContent;
+                               if ($attributes->name == "width")
+                                       $width = $attributes->textContent;
+                               if ($attributes->name == "updated")
+                                       $contact["avatar-date"] = $attributes->textContent;
+                       }
+                       if (($width > 0) AND ($href != ""))
+                               $avatarlist[$width] = $href;
+               }
+               if (count($avatarlist) > 0) {
+                       krsort($avatarlist);
+                       $author["avatar"] = current($avatarlist);
+               }
+
+               if ($r AND !$onlyfetch) {
+
+                       // When was the last change to name or uri?
+                       $name_element = $xpath->query($element."/atom:name", $context)->item(0);
+                       foreach($name_element->attributes AS $attributes)
+                               if ($attributes->name == "updated")
+                                       $contact["name-date"] = $attributes->textContent;
+
+                       $link_element = $xpath->query($element."/atom:link", $context)->item(0);
+                       foreach($link_element->attributes AS $attributes)
+                               if ($attributes->name == "updated")
+                                       $contact["uri-date"] = $attributes->textContent;
+
+                       // Update contact data
+                       $value = $xpath->evaluate($element."/dfrn:handle/text()", $context)->item(0)->nodeValue;
+                       if ($value != "")
+                               $contact["addr"] = $value;
+
+                       $value = $xpath->evaluate($element."/poco:displayName/text()", $context)->item(0)->nodeValue;
+                       if ($value != "")
+                               $contact["name"] = $value;
+
+                       $value = $xpath->evaluate($element."/poco:preferredUsername/text()", $context)->item(0)->nodeValue;
+                       if ($value != "")
+                               $contact["nick"] = $value;
+
+                       $value = $xpath->evaluate($element."/poco:note/text()", $context)->item(0)->nodeValue;
+                       if ($value != "")
+                               $contact["about"] = $value;
+
+                       $value = $xpath->evaluate($element."/poco:address/poco:formatted/text()", $context)->item(0)->nodeValue;
+                       if ($value != "")
+                               $contact["location"] = $value;
+
+                       /// @todo Add support for the following fields that we don't support by now in the contact table:
+                       /// - poco:utcOffset
+                       /// - poco:ims
+                       /// - poco:urls
+                       /// - poco:locality
+                       /// - poco:region
+                       /// - poco:country
+
+                       // Save the keywords into the contact table
+                       $tags = array();
+                       $tagelements = $xpath->evaluate($element."/poco:tags/text()", $context);
+                       foreach($tagelements AS $tag)
+                               $tags[$tag->nodeValue] = $tag->nodeValue;
+
+                       if (count($tags))
+                               $contact["keywords"] = implode(", ", $tags);
+
+                       // "dfrn:birthday" contains the birthday converted to UTC
+                       $old_bdyear = $contact["bdyear"];
+
+                       $birthday = $xpath->evaluate($element."/dfrn:birthday/text()", $context)->item(0)->nodeValue;
+
+                       if (strtotime($birthday) > time()) {
+                               $bd_timestamp = strtotime($birthday);
+
+                               $contact["bdyear"] = date("Y", $bd_timestamp);
+                       }
+
+                       // "poco:birthday" is the birthday in the format "yyyy-mm-dd"
+                       $value = $xpath->evaluate($element."/poco:birthday/text()", $context)->item(0)->nodeValue;
+
+                       if (!in_array($value, array("", "0000-00-00"))) {
+                               $bdyear = date("Y");
+                               $value = str_replace("0000", $bdyear, $value);
+
+                               if (strtotime($value) < time()) {
+                                       $value = str_replace($bdyear, $bdyear + 1, $value);
+                                       $bdyear = $bdyear + 1;
+                               }
+
+                               $contact["bd"] = $value;
+                       }
+
+                       if ($old_bdyear != $contact["bdyear"])
+                               self::birthday_event($contact, $birthday);
+
+                       // Get all field names
+                       $fields = array();
+                       foreach ($r[0] AS $field => $data)
+                               $fields[$field] = $data;
+
+                       unset($fields["id"]);
+                       unset($fields["uid"]);
+                       unset($fields["avatar-date"]);
+                       unset($fields["name-date"]);
+                       unset($fields["uri-date"]);
+
+                        // Update check for this field has to be done differently
+                       $datefields = array("name-date", "uri-date");
+                       foreach ($datefields AS $field)
+                               if (strtotime($contact[$field]) > strtotime($r[0][$field])) {
+                                       logger("Difference for contact ".$contact["id"]." in field '".$field."'. Old value: '".$contact[$field]."', new value '".$r[0][$field]."'", LOGGER_DEBUG);
+                                       $update = true;
+                               }
+
+                       foreach ($fields AS $field => $data)
+                               if ($contact[$field] != $r[0][$field]) {
+                                       logger("Difference for contact ".$contact["id"]." in field '".$field."'. Old value: '".$contact[$field]."', new value '".$r[0][$field]."'", LOGGER_DEBUG);
+                                       $update = true;
+                               }
+
+                       if ($update) {
+                               logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG);
+
+                               q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s',
+                                       `addr` = '%s', `keywords` = '%s', `bdyear` = '%s', `bd` = '%s',
+                                       `name-date`  = '%s', `uri-date` = '%s'
+                                       WHERE `id` = %d AND `network` = '%s'",
+                                       dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]),
+                                       dbesc($contact["addr"]), dbesc($contact["keywords"]), dbesc($contact["bdyear"]),
+                                       dbesc($contact["bd"]), dbesc($contact["name-date"]), dbesc($contact["uri-date"]),
+                                       intval($contact["id"]), dbesc($contact["network"]));
+                       }
+
+                       update_contact_avatar($author["avatar"], $importer["uid"], $contact["id"],
+                                               (strtotime($contact["avatar-date"]) > strtotime($r[0]["avatar-date"])));
+
+                       // The generation is a sign for the reliability of the provided data.
+                       // It is used in the socgraph.php to prevent that old contact data
+                       // that was relayed over several servers can overwrite contact
+                       // data that we received directly.
+                       $contact["generation"] = 2;
+                       $contact["photo"] = $author["avatar"];
+                       update_gcontact($contact);
+               }
+
+               return($author);
+       }
+
+       /**
+        * @brief Transforms activity objects into an XML string
+        *
+        * @param object $xpath XPath object
+        * @param object $activity Activity object
+        * @param text $element element name
+        *
+        * @return string XML string
+        */
+       private function transform_activity($xpath, $activity, $element) {
+               if (!is_object($activity))
+                       return "";
+
+               $obj_doc = new DOMDocument("1.0", "utf-8");
+               $obj_doc->formatOutput = true;
+
+               $obj_element = $obj_doc->createElementNS(NAMESPACE_ATOM1, $element);
+
+               $activity_type = $xpath->query("activity:object-type/text()", $activity)->item(0)->nodeValue;
+               xml_add_element($obj_doc, $obj_element, "type", $activity_type);
+
+               $id = $xpath->query("atom:id", $activity)->item(0);
+               if (is_object($id))
+                       $obj_element->appendChild($obj_doc->importNode($id, true));
+
+               $title = $xpath->query("atom:title", $activity)->item(0);
+               if (is_object($title))
+                       $obj_element->appendChild($obj_doc->importNode($title, true));
+
+               $links = $xpath->query("atom:link", $activity);
+               if (is_object($links))
+                       foreach ($links AS $link)
+                               $obj_element->appendChild($obj_doc->importNode($link, true));
+
+               $content = $xpath->query("atom:content", $activity)->item(0);
+               if (is_object($content))
+                       $obj_element->appendChild($obj_doc->importNode($content, true));
+
+               $obj_doc->appendChild($obj_element);
+
+               $objxml = $obj_doc->saveXML($obj_element);
+
+               // @todo This isn't totally clean. We should find a way to transform the namespaces
+               $objxml = str_replace("<".$element.' xmlns="http://www.w3.org/2005/Atom">', "<".$element.">", $objxml);
+               return($objxml);
+       }
+
+       /**
+        * @brief Processes the mail elements
+        *
+        * @param object $xpath XPath object
+        * @param object $mail mail elements
+        * @param array $importer Record of the importer user mixed with contact of the content
+        */
+       private function process_mail($xpath, $mail, $importer) {
+
+               logger("Processing mails");
+
+               $msg = array();
+               $msg["uid"] = $importer["importer_uid"];
+               $msg["from-name"] = $xpath->query("dfrn:sender/dfrn:name/text()", $mail)->item(0)->nodeValue;
+               $msg["from-url"] = $xpath->query("dfrn:sender/dfrn:uri/text()", $mail)->item(0)->nodeValue;
+               $msg["from-photo"] = $xpath->query("dfrn:sender/dfrn:avatar/text()", $mail)->item(0)->nodeValue;
+               $msg["contact-id"] = $importer["id"];
+               $msg["uri"] = $xpath->query("dfrn:id/text()", $mail)->item(0)->nodeValue;
+               $msg["parent-uri"] = $xpath->query("dfrn:in-reply-to/text()", $mail)->item(0)->nodeValue;
+               $msg["created"] = $xpath->query("dfrn:sentdate/text()", $mail)->item(0)->nodeValue;
+               $msg["title"] = $xpath->query("dfrn:subject/text()", $mail)->item(0)->nodeValue;
+               $msg["body"] = $xpath->query("dfrn:content/text()", $mail)->item(0)->nodeValue;
+               $msg["seen"] = 0;
+               $msg["replied"] = 0;
+
+               dbesc_array($msg);
+
+               $r = dbq("INSERT INTO `mail` (`".implode("`, `", array_keys($msg))."`) VALUES ('".implode("', '", array_values($msg))."')");
+
+               // send notifications.
+
+               $notif_params = array(
+                       "type" => NOTIFY_MAIL,
+                       "notify_flags" => $importer["notify-flags"],
+                       "language" => $importer["language"],
+                       "to_name" => $importer["username"],
+                       "to_email" => $importer["email"],
+                       "uid" => $importer["importer_uid"],
+                       "item" => $msg,
+                       "source_name" => $msg["from-name"],
+                       "source_link" => $importer["url"],
+                       "source_photo" => $importer["thumb"],
+                       "verb" => ACTIVITY_POST,
+                       "otype" => "mail"
+               );
+
+               notification($notif_params);
+
+               logger("Mail is processed, notification was sent.");
+       }
+
+       /**
+        * @brief Processes the suggestion elements
+        *
+        * @param object $xpath XPath object
+        * @param object $suggestion suggestion elements
+        * @param array $importer Record of the importer user mixed with contact of the content
+        */
+       private function process_suggestion($xpath, $suggestion, $importer) {
+
+               logger("Processing suggestions");
+
+               $suggest = array();
+               $suggest["uid"] = $importer["importer_uid"];
+               $suggest["cid"] = $importer["id"];
+               $suggest["url"] = $xpath->query("dfrn:url/text()", $suggestion)->item(0)->nodeValue;
+               $suggest["name"] = $xpath->query("dfrn:name/text()", $suggestion)->item(0)->nodeValue;
+               $suggest["photo"] = $xpath->query("dfrn:photo/text()", $suggestion)->item(0)->nodeValue;
+               $suggest["request"] = $xpath->query("dfrn:request/text()", $suggestion)->item(0)->nodeValue;
+               $suggest["body"] = $xpath->query("dfrn:note/text()", $suggestion)->item(0)->nodeValue;
+
+               // Does our member already have a friend matching this description?
+
+               $r = q("SELECT `id` FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
+                       dbesc($suggest["name"]),
+                       dbesc(normalise_link($suggest["url"])),
+                       intval($suggest["uid"])
+               );
+               if(count($r))
+                       return false;
+
+               // Do we already have an fcontact record for this person?
+
+               $fid = 0;
+               $r = q("SELECT `id` FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
+                       dbesc($suggest["url"]),
+                       dbesc($suggest["name"]),
+                       dbesc($suggest["request"])
+               );
+               if(count($r)) {
+                       $fid = $r[0]["id"];
+
+                       // OK, we do. Do we already have an introduction for this person ?
+                       $r = q("SELECT `id` FROM `intro` WHERE `uid` = %d AND `fid` = %d LIMIT 1",
+                               intval($suggest["uid"]),
+                               intval($fid)
+                       );
+                       if(count($r))
+                               return false;
+               }
+               if(!$fid)
+                       $r = q("INSERT INTO `fcontact` (`name`,`url`,`photo`,`request`) VALUES ('%s', '%s', '%s', '%s')",
+                       dbesc($suggest["name"]),
+                       dbesc($suggest["url"]),
+                       dbesc($suggest["photo"]),
+                       dbesc($suggest["request"])
+               );
+               $r = q("SELECT `id` FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
+                       dbesc($suggest["url"]),
+                       dbesc($suggest["name"]),
+                       dbesc($suggest["request"])
+               );
+               if(count($r))
+                       $fid = $r[0]["id"];
+               else
+                       // database record did not get created. Quietly give up.
+                       return false;
+
+
+               $hash = random_string();
+
+               $r = q("INSERT INTO `intro` (`uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked`)
+                       VALUES(%d, %d, %d, '%s', '%s', '%s', %d)",
+                       intval($suggest["uid"]),
+                       intval($fid),
+                       intval($suggest["cid"]),
+                       dbesc($suggest["body"]),
+                       dbesc($hash),
+                       dbesc(datetime_convert()),
+                       intval(0)
+               );
+
+               notification(array(
+                       "type"         => NOTIFY_SUGGEST,
+                       "notify_flags" => $importer["notify-flags"],
+                       "language"     => $importer["language"],
+                       "to_name"      => $importer["username"],
+                       "to_email"     => $importer["email"],
+                       "uid"          => $importer["importer_uid"],
+                       "item"         => $suggest,
+                       "link"         => App::get_baseurl()."/notifications/intros",
+                       "source_name"  => $importer["name"],
+                       "source_link"  => $importer["url"],
+                       "source_photo" => $importer["photo"],
+                       "verb"         => ACTIVITY_REQ_FRIEND,
+                       "otype"        => "intro"
+               ));
+
+               return true;
+
+       }
+
+       /**
+        * @brief Processes the relocation elements
+        *
+        * @param object $xpath XPath object
+        * @param object $relocation relocation elements
+        * @param array $importer Record of the importer user mixed with contact of the content
+        */
+       private function process_relocation($xpath, $relocation, $importer) {
+
+               logger("Processing relocations");
+
+               $relocate = array();
+               $relocate["uid"] = $importer["importer_uid"];
+               $relocate["cid"] = $importer["id"];
+               $relocate["url"] = $xpath->query("dfrn:url/text()", $relocation)->item(0)->nodeValue;
+               $relocate["name"] = $xpath->query("dfrn:name/text()", $relocation)->item(0)->nodeValue;
+               $relocate["photo"] = $xpath->query("dfrn:photo/text()", $relocation)->item(0)->nodeValue;
+               $relocate["thumb"] = $xpath->query("dfrn:thumb/text()", $relocation)->item(0)->nodeValue;
+               $relocate["micro"] = $xpath->query("dfrn:micro/text()", $relocation)->item(0)->nodeValue;
+               $relocate["request"] = $xpath->query("dfrn:request/text()", $relocation)->item(0)->nodeValue;
+               $relocate["confirm"] = $xpath->query("dfrn:confirm/text()", $relocation)->item(0)->nodeValue;
+               $relocate["notify"] = $xpath->query("dfrn:notify/text()", $relocation)->item(0)->nodeValue;
+               $relocate["poll"] = $xpath->query("dfrn:poll/text()", $relocation)->item(0)->nodeValue;
+               $relocate["sitepubkey"] = $xpath->query("dfrn:sitepubkey/text()", $relocation)->item(0)->nodeValue;
+
+               // update contact
+               $r = q("SELECT `photo`, `url` FROM `contact` WHERE `id` = %d AND `uid` = %d;",
+                       intval($importer["id"]),
+                       intval($importer["importer_uid"]));
+               if (!$r)
+                       return false;
+
+               $old = $r[0];
+
+               $x = q("UPDATE `contact` SET
+                                       `name` = '%s',
+                                       `photo` = '%s',
+                                       `thumb` = '%s',
+                                       `micro` = '%s',
+                                       `url` = '%s',
+                                       `nurl` = '%s',
+                                       `request` = '%s',
+                                       `confirm` = '%s',
+                                       `notify` = '%s',
+                                       `poll` = '%s',
+                                       `site-pubkey` = '%s'
+                       WHERE `id` = %d AND `uid` = %d;",
+                                       dbesc($relocate["name"]),
+                                       dbesc($relocate["photo"]),
+                                       dbesc($relocate["thumb"]),
+                                       dbesc($relocate["micro"]),
+                                       dbesc($relocate["url"]),
+                                       dbesc(normalise_link($relocate["url"])),
+                                       dbesc($relocate["request"]),
+                                       dbesc($relocate["confirm"]),
+                                       dbesc($relocate["notify"]),
+                                       dbesc($relocate["poll"]),
+                                       dbesc($relocate["sitepubkey"]),
+                                       intval($importer["id"]),
+                                       intval($importer["importer_uid"]));
+
+               if ($x === false)
+                       return false;
+
+               // update items
+               $fields = array(
+                       'owner-link' => array($old["url"], $relocate["url"]),
+                       'author-link' => array($old["url"], $relocate["url"]),
+                       'owner-avatar' => array($old["photo"], $relocate["photo"]),
+                       'author-avatar' => array($old["photo"], $relocate["photo"]),
+                       );
+               foreach ($fields as $n=>$f){
+                       $x = q("UPDATE `item` SET `%s` = '%s' WHERE `%s` = '%s' AND `uid` = %d",
+                                       $n, dbesc($f[1]),
+                                       $n, dbesc($f[0]),
+                                       intval($importer["importer_uid"]));
+                               if ($x === false)
+                                       return false;
+                       }
+
+               /// @TODO
+               /// merge with current record, current contents have priority
+               /// update record, set url-updated
+               /// update profile photos
+               /// schedule a scan?
+               return true;
+       }
+
+       /**
+        * @brief Updates an item
+        *
+        * @param array $current the current item record
+        * @param array $item the new item record
+        * @param array $importer Record of the importer user mixed with contact of the content
+        * @param int $entrytype Is it a toplevel entry, a comment or a relayed comment?
+        */
+       private function update_content($current, $item, $importer, $entrytype) {
+               $changed = false;
+
+               if (edited_timestamp_is_newer($current, $item)) {
+
+                       // do not accept (ignore) an earlier edit than one we currently have.
+                       if(datetime_convert("UTC","UTC",$item["edited"]) < $current["edited"])
+                               return(false);
+
+                       $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
+                               dbesc($item["title"]),
+                               dbesc($item["body"]),
+                               dbesc($item["tag"]),
+                               dbesc(datetime_convert("UTC","UTC",$item["edited"])),
+                               dbesc(datetime_convert()),
+                               dbesc($item["uri"]),
+                               intval($importer["importer_uid"])
+                       );
+                       create_tags_from_itemuri($item["uri"], $importer["importer_uid"]);
+                       update_thread_uri($item["uri"], $importer["importer_uid"]);
+
+                       $changed = true;
+
+                       if ($entrytype == DFRN_REPLY_RC)
+                               proc_run("php", "include/notifier.php","comment-import", $current["id"]);
+               }
+
+               // update last-child if it changes
+               if($item["last-child"] AND ($item["last-child"] != $current["last-child"])) {
+                       $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
+                               dbesc(datetime_convert()),
+                               dbesc($item["parent-uri"]),
+                               intval($importer["importer_uid"])
+                       );
+                       $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
+                               intval($item["last-child"]),
+                               dbesc(datetime_convert()),
+                               dbesc($item["uri"]),
+                               intval($importer["importer_uid"])
+                       );
+               }
+               return $changed;
+       }
+
+       /**
+        * @brief Detects the entry type of the item
+        *
+        * @param array $importer Record of the importer user mixed with contact of the content
+        * @param array $item the new item record
+        *
+        * @return int Is it a toplevel entry, a comment or a relayed comment?
+        */
+       private function get_entry_type($importer, $item) {
+               if ($item["parent-uri"] != $item["uri"]) {
+                       $community = false;
+
+                       if($importer["page-flags"] == PAGE_COMMUNITY || $importer["page-flags"] == PAGE_PRVGROUP) {
+                               $sql_extra = "";
+                               $community = true;
+                               logger("possible community action");
+                       } else
+                               $sql_extra = " AND `contact`.`self` AND `item`.`wall` ";
+
+                       // was the top-level post for this action written by somebody on this site?
+                       // Specifically, the recipient?
+
+                       $is_a_remote_action = false;
+
+                       $r = q("SELECT `item`.`parent-uri` FROM `item`
+                               WHERE `item`.`uri` = '%s'
+                               LIMIT 1",
+                               dbesc($item["parent-uri"])
+                       );
+                       if($r && count($r)) {
+                               $r = q("SELECT `item`.`forum_mode`, `item`.`wall` FROM `item`
+                                       INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
+                                       WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' OR `item`.`thr-parent` = '%s')
+                                       AND `item`.`uid` = %d
+                                       $sql_extra
+                                       LIMIT 1",
+                                       dbesc($r[0]["parent-uri"]),
+                                       dbesc($r[0]["parent-uri"]),
+                                       dbesc($r[0]["parent-uri"]),
+                                       intval($importer["importer_uid"])
+                               );
+                               if($r && count($r))
+                                       $is_a_remote_action = true;
+                       }
+
+                       // Does this have the characteristics of a community or private group action?
+                       // If it's an action to a wall post on a community/prvgroup page it's a
+                       // valid community action. Also forum_mode makes it valid for sure.
+                       // If neither, it's not.
+
+                       if($is_a_remote_action && $community) {
+                               if((!$r[0]["forum_mode"]) && (!$r[0]["wall"])) {
+                                       $is_a_remote_action = false;
+                                       logger("not a community action");
+                               }
+                       }
+
+                       if ($is_a_remote_action)
+                               return DFRN_REPLY_RC;
+                       else
+                               return DFRN_REPLY;
+
+               } else
+                       return DFRN_TOP_LEVEL;
+
+       }
+
+       /**
+        * @brief Send a "poke"
+        *
+        * @param array $item the new item record
+        * @param array $importer Record of the importer user mixed with contact of the content
+        * @param int $posted_id The record number of item record that was just posted
+        */
+       private function do_poke($item, $importer, $posted_id) {
+               $verb = urldecode(substr($item["verb"],strpos($item["verb"], "#")+1));
+               if(!$verb)
+                       return;
+               $xo = parse_xml_string($item["object"],false);
+
+               if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
+
+                       // somebody was poked/prodded. Was it me?
+                       foreach($xo->link as $l) {
+                               $atts = $l->attributes();
+                               switch($atts["rel"]) {
+                                       case "alternate":
+                                               $Blink = $atts["href"];
+                                               break;
+                                       default:
+                                               break;
+                               }
+                       }
+
+                       if($Blink && link_compare($Blink,App::get_baseurl()."/profile/".$importer["nickname"])) {
+
+                               // send a notification
+                               notification(array(
+                                       "type"         => NOTIFY_POKE,
+                                       "notify_flags" => $importer["notify-flags"],
+                                       "language"     => $importer["language"],
+                                       "to_name"      => $importer["username"],
+                                       "to_email"     => $importer["email"],
+                                       "uid"          => $importer["importer_uid"],
+                                       "item"         => $item,
+                                       "link"         => App::get_baseurl()."/display/".urlencode(get_item_guid($posted_id)),
+                                       "source_name"  => stripslashes($item["author-name"]),
+                                       "source_link"  => $item["author-link"],
+                                       "source_photo" => ((link_compare($item["author-link"],$importer["url"]))
+                                               ? $importer["thumb"] : $item["author-avatar"]),
+                                       "verb"         => $item["verb"],
+                                       "otype"        => "person",
+                                       "activity"     => $verb,
+                                       "parent"       => $item["parent"]
+                               ));
+                       }
+               }
+       }
+
+       /**
+        * @brief Processes several actions, depending on the verb
+        *
+        * @param int $entrytype Is it a toplevel entry, a comment or a relayed comment?
+        * @param array $importer Record of the importer user mixed with contact of the content
+        * @param array $item the new item record
+        * @param bool $is_like Is the verb a "like"?
+        *
+        * @return bool Should the processing of the entries be continued?
+        */
+       private function process_verbs($entrytype, $importer, &$item, &$is_like) {
+               if (($entrytype == DFRN_TOP_LEVEL)) {
+                       // The filling of the the "contact" variable is done for legcy reasons
+                       // The functions below are partly used by ostatus.php as well - where we have this variable
+                       $r = q("SELECT * FROM `contact` WHERE `id` = %d", intval($importer["id"]));
+                       $contact = $r[0];
+                       $nickname = $contact["nick"];
+
+                       // Big question: Do we need these functions? They were part of the "consume_feed" function.
+                       // This function once was responsible for DFRN and OStatus.
+                       if(activity_match($item["verb"],ACTIVITY_FOLLOW)) {
+                               logger("New follower");
+                               new_follower($importer, $contact, $item, $nickname);
+                               return false;
+                       }
+                       if(activity_match($item["verb"],ACTIVITY_UNFOLLOW))  {
+                               logger("Lost follower");
+                               lose_follower($importer, $contact, $item);
+                               return false;
+                       }
+                       if(activity_match($item["verb"],ACTIVITY_REQ_FRIEND)) {
+                               logger("New friend request");
+                               new_follower($importer, $contact, $item, $nickname, true);
+                               return false;
+                       }
+                       if(activity_match($item["verb"],ACTIVITY_UNFRIEND))  {
+                               logger("Lost sharer");
+                               lose_sharer($importer, $contact, $item);
+                               return false;
+                       }
+               } else {
+                       if(($item["verb"] === ACTIVITY_LIKE)
+                               || ($item["verb"] === ACTIVITY_DISLIKE)
+                               || ($item["verb"] === ACTIVITY_ATTEND)
+                               || ($item["verb"] === ACTIVITY_ATTENDNO)
+                               || ($item["verb"] === ACTIVITY_ATTENDMAYBE)) {
+                               $is_like = true;
+                               $item["type"] = "activity";
+                               $item["gravity"] = GRAVITY_LIKE;
+                               // only one like or dislike per person
+                               // splitted into two queries for performance issues
+                               $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
+                                       intval($item["uid"]),
+                                       dbesc($item["author-link"]),
+                                       dbesc($item["verb"]),
+                                       dbesc($item["parent-uri"])
+                               );
+                               if($r && count($r))
+                                       return false;
+
+                               $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
+                                       intval($item["uid"]),
+                                       dbesc($item["author-link"]),
+                                       dbesc($item["verb"]),
+                                       dbesc($item["parent-uri"])
+                               );
+                               if($r && count($r))
+                                       return false;
+                       } else
+                               $is_like = false;
+
+                       if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) {
+
+                               $xo = parse_xml_string($item["object"],false);
+                               $xt = parse_xml_string($item["target"],false);
+
+                               if($xt->type == ACTIVITY_OBJ_NOTE) {
+                                       $r = q("SELECT `id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
+                                               dbesc($xt->id),
+                                               intval($importer["importer_uid"])
+                                       );
+
+                                       if(!count($r))
+                                               return false;
+
+                                       // extract tag, if not duplicate, add to parent item
+                                       if($xo->content) {
+                                               if(!(stristr($r[0]["tag"],trim($xo->content)))) {
+                                                       q("UPDATE `item` SET `tag` = '%s' WHERE `id` = %d",
+                                                               dbesc($r[0]["tag"] . (strlen($r[0]["tag"]) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
+                                                               intval($r[0]["id"])
+                                                       );
+                                                       create_tags_from_item($r[0]["id"]);
+                                               }
+                                       }
+                               }
+                       }
+               }
+               return true;
+       }
+
+       /**
+        * @brief Processes the link elements
+        *
+        * @param object $links link elements
+        * @param array $item the item record
+        */
+       private function parse_links($links, &$item) {
+               $rel = "";
+               $href = "";
+               $type = "";
+               $length = "0";
+               $title = "";
+               foreach ($links AS $link) {
+                       foreach($link->attributes AS $attributes) {
+                               if ($attributes->name == "href")
+                                       $href = $attributes->textContent;
+                               if ($attributes->name == "rel")
+                                       $rel = $attributes->textContent;
+                               if ($attributes->name == "type")
+                                       $type = $attributes->textContent;
+                               if ($attributes->name == "length")
+                                       $length = $attributes->textContent;
+                               if ($attributes->name == "title")
+                                       $title = $attributes->textContent;
+                       }
+                       if (($rel != "") AND ($href != ""))
+                               switch($rel) {
+                                       case "alternate":
+                                               $item["plink"] = $href;
+                                               break;
+                                       case "enclosure":
+                                               $enclosure = $href;
+                                               if(strlen($item["attach"]))
+                                                       $item["attach"] .= ",";
+
+                                               $item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]';
+                                               break;
+                               }
+               }
+       }
+
+       /**
+        * @brief Processes the entry elements which contain the items and comments
+        *
+        * @param array $header Array of the header elements that always stay the same
+        * @param object $xpath XPath object
+        * @param object $entry entry elements
+        * @param array $importer Record of the importer user mixed with contact of the content
+        */
+       private function process_entry($header, $xpath, $entry, $importer) {
+
+               logger("Processing entries");
+
+               $item = $header;
+
+               // Get the uri
+               $item["uri"] = $xpath->query("atom:id/text()", $entry)->item(0)->nodeValue;
+
+               // Fetch the owner
+               $owner = self::fetchauthor($xpath, $entry, $importer, "dfrn:owner", true);
+
+               $item["owner-name"] = $owner["name"];
+               $item["owner-link"] = $owner["link"];
+               $item["owner-avatar"] = $owner["avatar"];
+
+               // fetch the author
+               $author = self::fetchauthor($xpath, $entry, $importer, "atom:author", true);
+
+               $item["author-name"] = $author["name"];
+               $item["author-link"] = $author["link"];
+               $item["author-avatar"] = $author["avatar"];
+
+               $item["title"] = $xpath->query("atom:title/text()", $entry)->item(0)->nodeValue;
+
+               $item["created"] = $xpath->query("atom:published/text()", $entry)->item(0)->nodeValue;
+               $item["edited"] = $xpath->query("atom:updated/text()", $entry)->item(0)->nodeValue;
+
+               $item["body"] = $xpath->query("dfrn:env/text()", $entry)->item(0)->nodeValue;
+               $item["body"] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$item["body"]);
+               // make sure nobody is trying to sneak some html tags by us
+               $item["body"] = notags(base64url_decode($item["body"]));
+
+               $item["body"] = limit_body_size($item["body"]);
+
+               /// @todo Do we really need this check for HTML elements? (It was copied from the old function)
+               if((strpos($item['body'],'<') !== false) && (strpos($item['body'],'>') !== false)) {
+
+                       $item['body'] = reltoabs($item['body'],$base_url);
+
+                       $item['body'] = html2bb_video($item['body']);
+
+                       $item['body'] = oembed_html2bbcode($item['body']);
+
+                       $config = HTMLPurifier_Config::createDefault();
+                       $config->set('Cache.DefinitionImpl', null);
+
+                       // we shouldn't need a whitelist, because the bbcode converter
+                       // will strip out any unsupported tags.
+
+                       $purifier = new HTMLPurifier($config);
+                       $item['body'] = $purifier->purify($item['body']);
+
+                       $item['body'] = @html2bbcode($item['body']);
+               }
+
+               // We don't need the content element since "dfrn:env" is always present
+               //$item["body"] = $xpath->query("atom:content/text()", $entry)->item(0)->nodeValue;
+
+               $item["last-child"] = $xpath->query("dfrn:comment-allow/text()", $entry)->item(0)->nodeValue;
+               $item["location"] = $xpath->query("dfrn:location/text()", $entry)->item(0)->nodeValue;
+
+               $georsspoint = $xpath->query("georss:point", $entry);
+               if ($georsspoint)
+                       $item["coord"] = $georsspoint->item(0)->nodeValue;
+
+               $item["private"] = $xpath->query("dfrn:private/text()", $entry)->item(0)->nodeValue;
+
+               $item["extid"] = $xpath->query("dfrn:extid/text()", $entry)->item(0)->nodeValue;
+
+               if ($xpath->query("dfrn:extid/text()", $entry)->item(0)->nodeValue == "true")
+                       $item["bookmark"] = true;
+
+               $notice_info = $xpath->query("statusnet:notice_info", $entry);
+               if ($notice_info AND ($notice_info->length > 0)) {
+                       foreach($notice_info->item(0)->attributes AS $attributes) {
+                               if ($attributes->name == "source")
+                                       $item["app"] = strip_tags($attributes->textContent);
+                       }
+               }
+
+               $item["guid"] = $xpath->query("dfrn:diaspora_guid/text()", $entry)->item(0)->nodeValue;
+
+               // We store the data from "dfrn:diaspora_signature" in a different table, this is done in "item_store"
+               $dsprsig = unxmlify($xpath->query("dfrn:diaspora_signature/text()", $entry)->item(0)->nodeValue);
+               if ($dsprsig != "")
+                       $item["dsprsig"] = $dsprsig;
+
+               $item["verb"] = $xpath->query("activity:verb/text()", $entry)->item(0)->nodeValue;
+
+               if ($xpath->query("activity:object-type/text()", $entry)->item(0)->nodeValue != "")
+                       $item["object-type"] = $xpath->query("activity:object-type/text()", $entry)->item(0)->nodeValue;
+
+               $object = $xpath->query("activity:object", $entry)->item(0);
+               $item["object"] = self::transform_activity($xpath, $object, "object");
+
+               if (trim($item["object"]) != "") {
+                       $r = parse_xml_string($item["object"], false);
+                       if (isset($r->type))
+                               $item["object-type"] = $r->type;
+               }
+
+               $target = $xpath->query("activity:target", $entry)->item(0);
+               $item["target"] = self::transform_activity($xpath, $target, "target");
+
+               $categories = $xpath->query("atom:category", $entry);
+               if ($categories) {
+                       foreach ($categories AS $category) {
+                               foreach($category->attributes AS $attributes)
+                                       if ($attributes->name == "term") {
+                                               $term = $attributes->textContent;
+                                               if(strlen($item["tag"]))
+                                                       $item["tag"] .= ",";
+
+                                               $item["tag"] .= "#[url=".App::get_baseurl()."/search?tag=".$term."]".$term."[/url]";
+                                       }
+                       }
+               }
+
+               $enclosure = "";
+
+               $links = $xpath->query("atom:link", $entry);
+               if ($links)
+                       self::parse_links($links, $item);
+
+               // Is it a reply or a top level posting?
+               $item["parent-uri"] = $item["uri"];
+
+               $inreplyto = $xpath->query("thr:in-reply-to", $entry);
+               if (is_object($inreplyto->item(0)))
+                       foreach($inreplyto->item(0)->attributes AS $attributes)
+                               if ($attributes->name == "ref")
+                                       $item["parent-uri"] = $attributes->textContent;
+
+               // Get the type of the item (Top level post, reply or remote reply)
+               $entrytype = self::get_entry_type($importer, $item);
+
+               // Now assign the rest of the values that depend on the type of the message
+               if (in_array($entrytype, array(DFRN_REPLY, DFRN_REPLY_RC))) {
+                       if (!isset($item["object-type"]))
+                               $item["object-type"] = ACTIVITY_OBJ_COMMENT;
+
+                       if ($item["contact-id"] != $owner["contact-id"])
+                               $item["contact-id"] = $owner["contact-id"];
+
+                       if (($item["network"] != $owner["network"]) AND ($owner["network"] != ""))
+                               $item["network"] = $owner["network"];
+
+                       if ($item["contact-id"] != $author["contact-id"])
+                               $item["contact-id"] = $author["contact-id"];
+
+                       if (($item["network"] != $author["network"]) AND ($author["network"] != ""))
+                               $item["network"] = $author["network"];
+
+                       // This code was taken from the old DFRN code
+                       // When activated, forums don't work.
+                       // And: Why should we disallow commenting by followers?
+                       // the behaviour is now similar to the Diaspora part.
+                       //if($importer["rel"] == CONTACT_IS_FOLLOWER) {
+                       //      logger("Contact ".$importer["id"]." is only follower. Quitting", LOGGER_DEBUG);
+                       //      return;
+                       //}
+               }
+
+               if ($entrytype == DFRN_REPLY_RC) {
+                       $item["type"] = "remote-comment";
+                       $item["wall"] = 1;
+               } elseif ($entrytype == DFRN_TOP_LEVEL) {
+                       if (!isset($item["object-type"]))
+                               $item["object-type"] = ACTIVITY_OBJ_NOTE;
+
+                       // Is it an event?
+                       if ($item["object-type"] == ACTIVITY_OBJ_EVENT) {
+                               logger("Item ".$item["uri"]." seems to contain an event.", LOGGER_DEBUG);
+                               $ev = bbtoevent($item["body"]);
+                               if((x($ev, "desc") || x($ev, "summary")) && x($ev, "start")) {
+                                       logger("Event in item ".$item["uri"]." was found.", LOGGER_DEBUG);
+                                       $ev["cid"] = $importer["id"];
+                                       $ev["uid"] = $importer["uid"];
+                                       $ev["uri"] = $item["uri"];
+                                       $ev["edited"] = $item["edited"];
+                                       $ev['private'] = $item['private'];
+                                       $ev["guid"] = $item["guid"];
+
+                                       $r = q("SELECT `id` FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
+                                               dbesc($item["uri"]),
+                                               intval($importer["uid"])
+                                       );
+                                       if(count($r))
+                                               $ev["id"] = $r[0]["id"];
+
+                                       $event_id = event_store($ev);
+                                       logger("Event ".$event_id." was stored", LOGGER_DEBUG);
+                                       return;
+                               }
+                       }
+               }
+
+               $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
+                       dbesc($item["uri"]),
+                       intval($importer["importer_uid"])
+               );
+
+               if (!self::process_verbs($entrytype, $importer, $item, $is_like)) {
+                       logger("Exiting because 'process_verbs' told us so", LOGGER_DEBUG);
+                       return;
+               }
+
+               // Update content if 'updated' changes
+               if(count($r)) {
+                       if (self::update_content($r[0], $item, $importer, $entrytype))
+                               logger("Item ".$item["uri"]." was updated.", LOGGER_DEBUG);
+                       else
+                               logger("Item ".$item["uri"]." already existed.", LOGGER_DEBUG);
+                       return;
+               }
+
+               if (in_array($entrytype, array(DFRN_REPLY, DFRN_REPLY_RC))) {
+                       $posted_id = item_store($item);
+                       $parent = 0;
+
+                       if($posted_id) {
+
+                               logger("Reply from contact ".$item["contact-id"]." was stored with id ".$posted_id, LOGGER_DEBUG);
+
+                               $item["id"] = $posted_id;
+
+                               $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
+                                       intval($posted_id),
+                                       intval($importer["importer_uid"])
+                               );
+                               if(count($r)) {
+                                       $parent = $r[0]["parent"];
+                                       $parent_uri = $r[0]["parent-uri"];
+                               }
+
+                               if(!$is_like) {
+                                       $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
+                                               dbesc(datetime_convert()),
+                                               intval($importer["importer_uid"]),
+                                               intval($r[0]["parent"])
+                                       );
+
+                                       $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
+                                               dbesc(datetime_convert()),
+                                               intval($importer["importer_uid"]),
+                                               intval($posted_id)
+                                       );
+                               }
+
+                               if($posted_id AND $parent AND ($entrytype == DFRN_REPLY_RC)) {
+                                       logger("Notifying followers about comment ".$posted_id, LOGGER_DEBUG);
+                                       proc_run("php", "include/notifier.php", "comment-import", $posted_id);
+                               }
+
+                               return true;
+                       }
+               } else { // $entrytype == DFRN_TOP_LEVEL
+                       if(!link_compare($item["owner-link"],$importer["url"])) {
+                               // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
+                               // but otherwise there's a possible data mixup on the sender's system.
+                               // the tgroup delivery code called from item_store will correct it if it's a forum,
+                               // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
+                               logger('Correcting item owner.', LOGGER_DEBUG);
+                               $item["owner-name"]   = $importer["senderName"];
+                               $item["owner-link"]   = $importer["url"];
+                               $item["owner-avatar"] = $importer["thumb"];
+                       }
+
+                       if(($importer["rel"] == CONTACT_IS_FOLLOWER) && (!tgroup_check($importer["importer_uid"], $item))) {
+                               logger("Contact ".$importer["id"]." is only follower and tgroup check was negative.", LOGGER_DEBUG);
+                               return;
+                       }
+
+                       // This is my contact on another system, but it's really me.
+                       // Turn this into a wall post.
+                       $notify = item_is_remote_self($importer, $item);
+
+                       $posted_id = item_store($item, false, $notify);
+
+                       logger("Item was stored with id ".$posted_id, LOGGER_DEBUG);
+
+                       if(stristr($item["verb"],ACTIVITY_POKE))
+                               self::do_poke($item, $importer, $posted_id);
+               }
+       }
+
+       /**
+        * @brief Deletes items
+        *
+        * @param object $xpath XPath object
+        * @param object $deletion deletion elements
+        * @param array $importer Record of the importer user mixed with contact of the content
+        */
+       private function process_deletion($xpath, $deletion, $importer) {
+
+               logger("Processing deletions");
+
+               foreach($deletion->attributes AS $attributes) {
+                       if ($attributes->name == "ref")
+                               $uri = $attributes->textContent;
+                       if ($attributes->name == "when")
+                               $when = $attributes->textContent;
+               }
+               if ($when)
+                       $when = datetime_convert("UTC", "UTC", $when, "Y-m-d H:i:s");
+               else
+                       $when = datetime_convert("UTC", "UTC", "now", "Y-m-d H:i:s");
+
+               if (!$uri OR !$importer["id"])
+                       return false;
+
+               /// @todo Only select the used fields
+               $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
+                               WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
+                               dbesc($uri),
+                               intval($importer["uid"]),
+                               intval($importer["id"])
+                       );
+               if(!count($r)) {
+                       logger("Item with uri ".$uri." from contact ".$importer["id"]." for user ".$importer["uid"]." wasn't found.", LOGGER_DEBUG);
+                       return;
+               } else {
+
+                       $item = $r[0];
+
+                       $entrytype = self::get_entry_type($importer, $item);
+
+                       if(!$item["deleted"])
+                               logger('deleting item '.$item["id"].' uri='.$uri, LOGGER_DEBUG);
+                       else
+                               return;
+
+                       if($item["object-type"] === ACTIVITY_OBJ_EVENT) {
+                               logger("Deleting event ".$item["event-id"], LOGGER_DEBUG);
+                               event_delete($item["event-id"]);
+                       }
+
+                       if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) {
+                               $xo = parse_xml_string($item["object"],false);
+                               $xt = parse_xml_string($item["target"],false);
+                               if($xt->type === ACTIVITY_OBJ_NOTE) {
+                                       $i = q("SELECT `id`, `contact-id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
+                                               dbesc($xt->id),
+                                               intval($importer["importer_uid"])
+                                       );
+                                       if(count($i)) {
+
+                                               // For tags, the owner cannot remove the tag on the author's copy of the post.
+
+                                               $owner_remove = (($item["contact-id"] == $i[0]["contact-id"]) ? true: false);
+                                               $author_remove = (($item["origin"] && $item["self"]) ? true : false);
+                                               $author_copy = (($item["origin"]) ? true : false);
+
+                                               if($owner_remove && $author_copy)
+                                                       return;
+                                               if($author_remove || $owner_remove) {
+                                                       $tags = explode(',',$i[0]["tag"]);
+                                                       $newtags = array();
+                                                       if(count($tags)) {
+                                                               foreach($tags as $tag)
+                                                                       if(trim($tag) !== trim($xo->body))
+                                                                               $newtags[] = trim($tag);
+                                                       }
+                                                       q("UPDATE `item` SET `tag` = '%s' WHERE `id` = %d",
+                                                               dbesc(implode(',',$newtags)),
+                                                               intval($i[0]["id"])
+                                                       );
+                                                       create_tags_from_item($i[0]["id"]);
+                                               }
+                                       }
+                               }
+                       }
+
+                       if($entrytype == DFRN_TOP_LEVEL) {
+                               $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
+                                               `body` = '', `title` = ''
+                                       WHERE `parent-uri` = '%s' AND `uid` = %d",
+                                               dbesc($when),
+                                               dbesc(datetime_convert()),
+                                               dbesc($uri),
+                                               intval($importer["uid"])
+                                       );
+                               create_tags_from_itemuri($uri, $importer["uid"]);
+                               create_files_from_itemuri($uri, $importer["uid"]);
+                               update_thread_uri($uri, $importer["uid"]);
+                       } else {
+                               $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
+                                               `body` = '', `title` = ''
+                                       WHERE `uri` = '%s' AND `uid` = %d",
+                                               dbesc($when),
+                                               dbesc(datetime_convert()),
+                                               dbesc($uri),
+                                               intval($importer["uid"])
+                                       );
+                               create_tags_from_itemuri($uri, $importer["uid"]);
+                               create_files_from_itemuri($uri, $importer["uid"]);
+                               update_thread_uri($uri, $importer["importer_uid"]);
+                               if($item["last-child"]) {
+                                       // ensure that last-child is set in case the comment that had it just got wiped.
+                                       q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
+                                               dbesc(datetime_convert()),
+                                               dbesc($item["parent-uri"]),
+                                               intval($item["uid"])
+                                       );
+                                       // who is the last child now?
+                                       $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
+                                               ORDER BY `created` DESC LIMIT 1",
+                                                       dbesc($item["parent-uri"]),
+                                                       intval($importer["uid"])
+                                       );
+                                       if(count($r)) {
+                                               q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
+                                                       intval($r[0]["id"])
+                                               );
+                                       }
+                               }
+                               // if this is a relayed delete, propagate it to other recipients
+
+                               if($entrytype == DFRN_REPLY_RC) {
+                                       logger("Notifying followers about deletion of post ".$item["id"], LOGGER_DEBUG);
+                                       proc_run("php", "include/notifier.php","drop", $item["id"]);
+                               }
+                       }
+               }
+       }
+
+       /**
+        * @brief Imports a DFRN message
+        *
+        * @param text $xml The DFRN message
+        * @param array $importer Record of the importer user mixed with contact of the content
+        * @param bool $sort_by_date Is used when feeds are polled
+        */
+       public static function import($xml,$importer, $sort_by_date = false) {
+
+               if ($xml == "")
+                       return;
+
+               if($importer["readonly"]) {
+                       // We aren't receiving stuff from this person. But we will quietly ignore them
+                       // rather than a blatant "go away" message.
+                       logger('ignoring contact '.$importer["id"]);
+                       return;
+               }
+
+               $doc = new DOMDocument();
+               @$doc->loadXML($xml);
+
+               $xpath = new DomXPath($doc);
+               $xpath->registerNamespace("atom", NAMESPACE_ATOM1);
+               $xpath->registerNamespace("thr", NAMESPACE_THREAD);
+               $xpath->registerNamespace("at", NAMESPACE_TOMB);
+               $xpath->registerNamespace("media", NAMESPACE_MEDIA);
+               $xpath->registerNamespace("dfrn", NAMESPACE_DFRN);
+               $xpath->registerNamespace("activity", NAMESPACE_ACTIVITY);
+               $xpath->registerNamespace("georss", NAMESPACE_GEORSS);
+               $xpath->registerNamespace("poco", NAMESPACE_POCO);
+               $xpath->registerNamespace("ostatus", NAMESPACE_OSTATUS);
+               $xpath->registerNamespace("statusnet", NAMESPACE_STATUSNET);
+
+               $header = array();
+               $header["uid"] = $importer["uid"];
+               $header["network"] = NETWORK_DFRN;
+               $header["type"] = "remote";
+               $header["wall"] = 0;
+               $header["origin"] = 0;
+               $header["contact-id"] = $importer["id"];
+
+               // Update the contact table if the data has changed
+               // Only the "dfrn:owner" in the head section contains all data
+               self::fetchauthor($xpath, $doc->firstChild, $importer, "dfrn:owner", false);
+
+               logger("Import DFRN message for user ".$importer["uid"]." from contact ".$importer["id"], LOGGER_DEBUG);
+
+               // is it a public forum? Private forums aren't supported by now with this method
+               $forum = intval($xpath->evaluate("/atom:feed/dfrn:community/text()", $context)->item(0)->nodeValue);
+
+               if ($forum != $importer["forum"])
+                       q("UPDATE `contact` SET `forum` = %d WHERE `forum` != %d AND `id` = %d",
+                               intval($forum), intval($forum),
+                               intval($importer["id"])
+                       );
+
+               $mails = $xpath->query("/atom:feed/dfrn:mail");
+               foreach ($mails AS $mail)
+                       self::process_mail($xpath, $mail, $importer);
+
+               $suggestions = $xpath->query("/atom:feed/dfrn:suggest");
+               foreach ($suggestions AS $suggestion)
+                       self::process_suggestion($xpath, $suggestion, $importer);
+
+               $relocations = $xpath->query("/atom:feed/dfrn:relocate");
+               foreach ($relocations AS $relocation)
+                       self::process_relocation($xpath, $relocation, $importer);
+
+               $deletions = $xpath->query("/atom:feed/at:deleted-entry");
+               foreach ($deletions AS $deletion)
+                       self::process_deletion($xpath, $deletion, $importer);
+
+               if (!$sort_by_date) {
+                       $entries = $xpath->query("/atom:feed/atom:entry");
+                       foreach ($entries AS $entry)
+                               self::process_entry($header, $xpath, $entry, $importer);
+               } else {
+                       $newentries = array();
+                       $entries = $xpath->query("/atom:feed/atom:entry");
+                       foreach ($entries AS $entry) {
+                               $created = $xpath->query("atom:published/text()", $entry)->item(0)->nodeValue;
+                               $newentries[strtotime($created)] = $entry;
+                       }
+
+                       // Now sort after the publishing date
+                       ksort($newentries);
+
+                       foreach ($newentries AS $entry)
+                               self::process_entry($header, $xpath, $entry, $importer);
+               }
+               logger("Import done for user ".$importer["uid"]." from contact ".$importer["id"], LOGGER_DEBUG);
+       }
+}
+?>
index 75e4a7106bd064622c59401b60acf43d0ae9d2e2..93fe2a472feb47268375709fd4f771614c8e7634 100644 (file)
@@ -14,6 +14,7 @@ require_once('include/queue_fn.php');
 require_once('include/lock.php');
 require_once('include/threads.php');
 require_once('mod/share.php');
+require_once('include/enotify.php');
 
 function diaspora_dispatch_public($msg) {
 
@@ -728,7 +729,7 @@ function diaspora_request($importer,$xml) {
 
                require_once('include/Photo.php');
 
-               $photos = import_profile_photo($contact_record['photo'],$importer['uid'],$contact_record['id']);
+               update_contact_avatar($contact_record['photo'],$importer['uid'],$contact_record['id']);
 
                // technically they are sharing with us (CONTACT_IS_SHARING),
                // but if our page-type is PAGE_COMMUNITY or PAGE_SOAPBOX
@@ -739,26 +740,17 @@ function diaspora_request($importer,$xml) {
                else
                        $new_relation = CONTACT_IS_FOLLOWER;
 
-               $r = q("UPDATE `contact` SET
-                       `photo` = '%s',
-                       `thumb` = '%s',
-                       `micro` = '%s',
-                       `rel` = %d,
+               $r = q("UPDATE `contact` SET `rel` = %d,
                        `name-date` = '%s',
                        `uri-date` = '%s',
-                       `avatar-date` = '%s',
                        `blocked` = 0,
                        `pending` = 0,
                        `writable` = 1
                        WHERE `id` = %d
                        ",
-                       dbesc($photos[0]),
-                       dbesc($photos[1]),
-                       dbesc($photos[2]),
                        intval($new_relation),
                        dbesc(datetime_convert()),
                        dbesc(datetime_convert()),
-                       dbesc(datetime_convert()),
                        intval($contact_record['id'])
                );
 
@@ -826,6 +818,23 @@ function diaspora_plink($addr, $guid) {
        return 'https://'.substr($addr,strpos($addr,'@')+1).'/posts/'.$guid;
 }
 
+function diaspora_repair_signature($signature, $handle = "", $level = 1) {
+
+       if ($signature == "")
+               return($signature);
+
+       if (base64_encode(base64_decode(base64_decode($signature))) == base64_decode($signature)) {
+               $signature = base64_decode($signature);
+               logger("Repaired double encoded signature from Diaspora/Hubzilla handle ".$handle." - level ".$level, LOGGER_DEBUG);
+
+               // Do a recursive call to be able to fix even multiple levels
+               if ($level < 10)
+                       $signature = diaspora_repair_signature($signature, $handle, ++$level);
+       }
+
+       return($signature);
+}
+
 function diaspora_post($importer,$xml,$msg) {
 
        $a = get_app();
@@ -1565,62 +1574,22 @@ function diaspora_comment($importer,$xml,$msg) {
                //);
        //}
 
-       if(($parent_item['origin']) && (! $parent_author_signature)) {
+       // If we are the origin of the parent we store the original signature and notify our followers
+       if($parent_item['origin']) {
+               $author_signature_base64 = base64_encode($author_signature);
+               $author_signature_base64 = diaspora_repair_signature($author_signature_base64, $diaspora_handle);
+
                q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
                        intval($message_id),
                        dbesc($signed_data),
-                       dbesc(base64_encode($author_signature)),
+                       dbesc($author_signature_base64),
                        dbesc($diaspora_handle)
                );
 
-               // if the message isn't already being relayed, notify others
-               // the existence of parent_author_signature means the parent_author or owner
-               // is already relaying.
-
+               // notify others
                proc_run('php','include/notifier.php','comment-import',$message_id);
        }
 
-       $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0 ",
-               dbesc($parent_item['uri']),
-               intval($importer['uid'])
-       );
-
-       if(count($myconv)) {
-               $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
-
-               foreach($myconv as $conv) {
-
-                       // now if we find a match, it means we're in this conversation
-
-                       if(! link_compare($conv['author-link'],$importer_url))
-                               continue;
-
-                       require_once('include/enotify.php');
-
-                       $conv_parent = $conv['parent'];
-
-                       notification(array(
-                               'type'         => NOTIFY_COMMENT,
-                               'notify_flags' => $importer['notify-flags'],
-                               'language'     => $importer['language'],
-                               'to_name'      => $importer['username'],
-                               'to_email'     => $importer['email'],
-                               'uid'          => $importer['uid'],
-                               'item'         => $datarray,
-                               'link'             => $a->get_baseurl().'/display/'.urlencode($datarray['guid']),
-                               'source_name'  => $datarray['author-name'],
-                               'source_link'  => $datarray['author-link'],
-                               'source_photo' => $datarray['author-avatar'],
-                               'verb'         => ACTIVITY_POST,
-                               'otype'        => 'item',
-                               'parent'       => $conv_parent,
-                               'parent_uri'   => $parent_uri
-                       ));
-
-                       // only send one notification
-                       break;
-               }
-       }
        return;
 }
 
@@ -1775,7 +1744,6 @@ function diaspora_conversation($importer,$xml,$msg) {
                        intval($conversation['id'])
                );
 
-               require_once('include/enotify.php');
                notification(array(
                        'type' => NOTIFY_MAIL,
                        'notify_flags' => $importer['notify-flags'],
@@ -1973,11 +1941,15 @@ function diaspora_photo($importer,$xml,$msg,$attempt=1) {
                                           array($remote_photo_name, 'scaled_full_' . $remote_photo_name));
 
        if(strpos($parent_item['body'],$link_text) === false) {
+
+               $parent_item['body'] = $link_text . $parent_item['body'];
+
                $r = q("UPDATE `item` SET `body` = '%s', `visible` = 1 WHERE `id` = %d AND `uid` = %d",
-                       dbesc($link_text . $parent_item['body']),
+                       dbesc($parent_item['body']),
                        intval($parent_item['id']),
                        intval($parent_item['uid'])
                );
+               put_item_in_cache($parent_item, true);
                update_thread($parent_item['id']);
        }
 
@@ -2222,21 +2194,21 @@ EOT;
        //      );
        //}
 
-       if(! $parent_author_signature) {
+       // If we are the origin of the parent we store the original signature and notify our followers
+       if($parent_item['origin']) {
+               $author_signature_base64 = base64_encode($author_signature);
+               $author_signature_base64 = diaspora_repair_signature($author_signature_base64, $diaspora_handle);
+
                q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
                        intval($message_id),
                        dbesc($signed_data),
-                       dbesc(base64_encode($author_signature)),
+                       dbesc($author_signature_base64),
                        dbesc($diaspora_handle)
                );
-       }
-
-       // if the message isn't already being relayed, notify others
-       // the existence of parent_author_signature means the parent_author or owner
-       // is already relaying. The parent_item['origin'] indicates the message was created on our system
 
-       if(($parent_item['origin']) && (! $parent_author_signature))
+               // notify others
                proc_run('php','include/notifier.php','comment-import',$message_id);
+       }
 
        return;
 }
@@ -2332,8 +2304,7 @@ function diaspora_signed_retraction($importer,$xml,$msg) {
                        return;
                }
 
-       }
-       else {
+       } else {
 
                $sig_decode = base64_decode($sig);
 
@@ -2367,7 +2338,7 @@ function diaspora_signed_retraction($importer,$xml,$msg) {
                                        intval($r[0]['parent'])
                                );
                                if(count($p)) {
-                                       if(($p[0]['origin']) && (! $parent_author_signature)) {
+                                       if($p[0]['origin']) {
                                                q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
                                                        $r[0]['id'],
                                                        dbesc($signed_data),
@@ -2407,10 +2378,10 @@ function diaspora_profile($importer,$xml,$msg) {
        if(! $contact)
                return;
 
-       if($contact['blocked']) {
-               logger('diaspora_post: Ignoring this author.');
-               return 202;
-       }
+       //if($contact['blocked']) {
+       //      logger('diaspora_post: Ignoring this author.');
+       //      return 202;
+       //}
 
        $name = unxmlify($xml->first_name) . ((strlen($xml->last_name)) ? ' ' . unxmlify($xml->last_name) : '');
        $image_url = unxmlify($xml->image_url);
@@ -2418,6 +2389,8 @@ function diaspora_profile($importer,$xml,$msg) {
        $location = diaspora2bb(unxmlify($xml->location));
        $about = diaspora2bb(unxmlify($xml->bio));
        $gender = unxmlify($xml->gender);
+       $searchable = (unxmlify($xml->searchable) == "true");
+       $nsfw = (unxmlify($xml->nsfw) == "true");
        $tags = unxmlify($xml->tag_string);
 
        $tags = explode("#", $tags);
@@ -2432,6 +2405,8 @@ function diaspora_profile($importer,$xml,$msg) {
        $keywords = implode(", ", $keywords);
 
        $handle_parts = explode("@", $diaspora_handle);
+       $nick = $handle_parts[0];
+
        if($name === '') {
                $name = $handle_parts[0];
        }
@@ -2448,7 +2423,7 @@ function diaspora_profile($importer,$xml,$msg) {
 
        require_once('include/Photo.php');
 
-       $images = import_profile_photo($image_url,$importer['uid'],$contact['id']);
+       update_contact_avatar($image_url,$importer['uid'],$contact['id']);
 
        // Generic birthday. We don't know the timezone. The year is irrelevant.
 
@@ -2466,12 +2441,11 @@ function diaspora_profile($importer,$xml,$msg) {
        /// @TODO Update name on item['author-name'] if the name changed. See consume_feed()
        /// (Not doing this currently because D* protocol is scheduled for revision soon).
 
-       $r = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' , `bd` = '%s', `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s' WHERE `id` = %d AND `uid` = %d",
+       $r = q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `addr` = '%s', `name-date` = '%s', `bd` = '%s',
+                       `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s' WHERE `id` = %d AND `uid` = %d",
                dbesc($name),
-               dbesc(datetime_convert()),
-               dbesc($images[0]),
-               dbesc($images[1]),
-               dbesc($images[2]),
+               dbesc($nick),
+               dbesc($diaspora_handle),
                dbesc(datetime_convert()),
                dbesc($birthday),
                dbesc($location),
@@ -2482,26 +2456,17 @@ function diaspora_profile($importer,$xml,$msg) {
                intval($importer['uid'])
        );
 
-       if (unxmlify($xml->searchable) == "true") {
+       if ($searchable) {
                require_once('include/socgraph.php');
-               poco_check($contact['url'], $name, NETWORK_DIASPORA, $images[0], $about, $location, $gender, $keywords, "",
+               poco_check($contact['url'], $name, NETWORK_DIASPORA, $image_url, $about, $location, $gender, $keywords, "",
                        datetime_convert(), 2, $contact['id'], $importer['uid']);
        }
 
-       $profileurl = "";
-       $author = q("SELECT * FROM `unique_contacts` WHERE `url`='%s' LIMIT 1",
-                       dbesc(normalise_link($contact['url'])));
-
-       if (count($author) == 0) {
-               q("INSERT INTO `unique_contacts` (`url`, `name`, `avatar`, `location`, `about`) VALUES ('%s', '%s', '%s', '%s', '%s')",
-                       dbesc(normalise_link($contact['url'])), dbesc($name), dbesc($location), dbesc($about), dbesc($images[0]));
-
-               $author = q("SELECT id FROM unique_contacts WHERE url='%s' LIMIT 1",
-                       dbesc(normalise_link($contact['url'])));
-       } else if (normalise_link($contact['url']).$name.$location.$about != normalise_link($author[0]["url"]).$author[0]["name"].$author[0]["location"].$author[0]["about"]) {
-               q("UPDATE unique_contacts SET name = '%s', avatar = '%s', `location` = '%s', `about` = '%s' WHERE url = '%s'",
-               dbesc($name), dbesc($images[0]), dbesc($location), dbesc($about), dbesc(normalise_link($contact['url'])));
-       }
+       update_gcontact(array("url" => $contact['url'], "network" => NETWORK_DIASPORA, "generation" => 2,
+                               "photo" => $image_url, "name" => $name, "location" => $location,
+                               "about" => $about, "birthday" => $birthday, "gender" => $gender,
+                               "addr" => $diaspora_handle, "nick" => $nick, "keywords" => $keywords,
+                               "hide" => !$searchable, "nsfw" => $nsfw));
 
 /*     if($r) {
                if($oldphotos) {
@@ -2643,11 +2608,12 @@ function diaspora_send_status($item,$owner,$contact,$public_batch = false) {
        }
 
        logger('diaspora_send_status: '.$owner['username'].' -> '.$contact['name'].' base message: '.$msg, LOGGER_DATA);
+       logger('send guid '.$item['guid'], LOGGER_DEBUG);
 
        $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
        //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
 
-       $return_code = diaspora_transmit($owner,$contact,$slap,$public_batch);
+       $return_code = diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid']);
 
        logger('diaspora_send_status: guid: '.$item['guid'].' result '.$return_code, LOGGER_DEBUG);
 
@@ -2758,10 +2724,12 @@ function diaspora_send_images($item,$owner,$contact,$images,$public_batch = fals
 
 
                logger('diaspora_send_photo: base message: ' . $msg, LOGGER_DATA);
+               logger('send guid '.$r[0]['guid'], LOGGER_DEBUG);
+
                $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
                //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
 
-               diaspora_transmit($owner,$contact,$slap,$public_batch);
+               diaspora_transmit($owner,$contact,$slap,$public_batch,false,$r[0]['guid']);
        }
 
 }
@@ -2815,7 +2783,7 @@ function diaspora_send_followup($item,$owner,$contact,$public_batch = false) {
        // sign it
 
        if($like)
-               $signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $myaddr;
+               $signed_text =  $positive . ';' . $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $myaddr;
        else
                $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $myaddr;
 
@@ -2832,11 +2800,12 @@ function diaspora_send_followup($item,$owner,$contact,$public_batch = false) {
        ));
 
        logger('diaspora_followup: base message: ' . $msg, LOGGER_DATA);
+       logger('send guid '.$item['guid'], LOGGER_DEBUG);
 
        $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
        //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
 
-       return(diaspora_transmit($owner,$contact,$slap,$public_batch));
+       return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid']));
 }
 
 
@@ -2847,9 +2816,6 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) {
        $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
 //     $theiraddr = $contact['addr'];
 
-       $body = $item['body'];
-       $text = html_entity_decode(bb2diaspora($body));
-
        // Diaspora doesn't support threaded comments, but some
        // versions of Diaspora (i.e. Diaspora-pistos) support
        // likes on comments
@@ -2900,61 +2866,53 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) {
        // fetch the original signature if the relayable was created by a Diaspora
        // or DFRN user. Relayables for other networks are not supported.
 
-/*     $r = q("select * from sign where " . $sql_sign_id . " = %d limit 1",
+       $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE " . $sql_sign_id . " = %d LIMIT 1",
                intval($item['id'])
        );
-       if(count($r)) { 
+       if(count($r)) {
                $orig_sign = $r[0];
                $signed_text = $orig_sign['signed_text'];
                $authorsig = $orig_sign['signature'];
                $handle = $orig_sign['signer'];
+
+               // Split the signed text
+               $signed_parts = explode(";", $signed_text);
+
+               // Remove the parent guid
+               array_shift($signed_parts);
+
+               // Remove the comment guid
+               array_shift($signed_parts);
+
+               // Remove the handle
+               array_pop($signed_parts);
+
+               // Glue the parts together
+               $text = implode(";", $signed_parts);
        }
        else {
+               // This part is meant for cases where we don't have the signatur. (Which shouldn't happen with posts from Diaspora and Friendica)
+               // This means that the comment won't be accepted by newer Diaspora servers
 
-               // Author signature information (for likes, comments, and retractions of likes or comments,
-               // whether from Diaspora or Friendica) must be placed in the `sign` table before this 
-               // function is called
-               logger('diaspora_send_relay: original author signature not found, cannot send relayable');
-               return;
-       }*/
-
-       /* Since the author signature is only checked by the parent, not by the relay recipients,
-        * I think it may not be necessary for us to do so much work to preserve all the original
-        * signatures. The important thing that Diaspora DOES need is the original creator's handle.
-        * Let's just generate that and forget about all the original author signature stuff.
-        *
-        * Note: this might be more of an problem if we want to support likes on comments for older
-        * versions of Diaspora (diaspora-pistos), but since there are a number of problems with
-        * doing that, let's ignore it for now.
-        *
-        * Currently, only DFRN contacts are supported. StatusNet shouldn't be hard, but it hasn't
-        * been done yet
-        */
-
-       $handle = diaspora_handle_from_contact($item['contact-id']);
-       if(! $handle)
-               return;
+               $body = $item['body'];
+               $text = html_entity_decode(bb2diaspora($body));
 
+               $handle = diaspora_handle_from_contact($item['contact-id']);
+               if(! $handle)
+                       return;
 
-       if($relay_retract)
-               $sender_signed_text = $item['guid'] . ';' . $target_type;
-       elseif($like)
-               $sender_signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $handle;
-       else
-               $sender_signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $handle;
+               if($relay_retract)
+                       $signed_text = $item['guid'] . ';' . $target_type;
+               elseif($like)
+                       $signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $handle;
+               else
+                       $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $handle;
 
-       // Sign the relayable with the top-level owner's signature
-       //
-       // We'll use the $sender_signed_text that we just created, instead of the $signed_text
-       // stored in the database, because that provides the best chance that Diaspora will
-       // be able to reconstruct the signed text the same way we did. This is particularly a
-       // concern for the comment, whose signed text includes the text of the comment. The
-       // smallest change in the text of the comment, including removing whitespace, will
-       // make the signature verification fail. Since we translate from BB code to Diaspora's
-       // markup at the top of this function, which is AFTER we placed the original $signed_text
-       // in the database, it's hazardous to trust the original $signed_text.
+               $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'));
+       }
 
-       $parentauthorsig = base64_encode(rsa_sign($sender_signed_text,$owner['uprvkey'],'sha256'));
+       // Sign the relayable with the top-level owner's signature
+       $parentauthorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'));
 
        $msg = replace_macros($tpl,array(
                '$guid' => xmlify($item['guid']),
@@ -2968,12 +2926,12 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) {
        ));
 
        logger('diaspora_send_relay: base message: ' . $msg, LOGGER_DATA);
-
+       logger('send guid '.$item['guid'], LOGGER_DEBUG);
 
        $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
        //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
 
-       return(diaspora_transmit($owner,$contact,$slap,$public_batch));
+       return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid']));
 
 }
 
@@ -3005,10 +2963,12 @@ function diaspora_send_retraction($item,$owner,$contact,$public_batch = false) {
                '$signature' => xmlify(base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')))
        ));
 
+       logger('send guid '.$item['guid'], LOGGER_DEBUG);
+
        $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
        //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
 
-       return(diaspora_transmit($owner,$contact,$slap,$public_batch));
+       return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid']));
 }
 
 function diaspora_send_mail($item,$owner,$contact) {
@@ -3065,16 +3025,17 @@ function diaspora_send_mail($item,$owner,$contact) {
        }
 
        logger('diaspora_conversation: ' . print_r($xmsg,true), LOGGER_DATA);
+       logger('send guid '.$item['guid'], LOGGER_DEBUG);
 
        $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($xmsg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],false)));
        //$slap = 'xml=' . urlencode(diaspora_msg_build($xmsg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],false));
 
-       return(diaspora_transmit($owner,$contact,$slap,false));
+       return(diaspora_transmit($owner,$contact,$slap,false,false,$item['guid']));
 
 
 }
 
-function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false) {
+function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false,$guid = "") {
 
        $enabled = intval(get_config('system','diaspora_enabled'));
        if(! $enabled) {
@@ -3087,9 +3048,9 @@ function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false)
        if(! $dest_url) {
                logger('diaspora_transmit: no url for contact: ' . $contact['id'] . ' batch mode =' . $public_batch);
                return 0;
-       } 
+       }
 
-       logger('diaspora_transmit: ' . $logid . ' ' . $dest_url);
+       logger('diaspora_transmit: '.$logid.'-'.$guid.' '.$dest_url);
 
        if( (! $queue_run) && (was_recently_delayed($contact['id'])) ) {
                $return_code = 0;
@@ -3104,7 +3065,7 @@ function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false)
                }
        }
 
-       logger('diaspora_transmit: ' . $logid . ' returns: ' . $return_code);
+       logger('diaspora_transmit: '.$logid.'-'.$guid.' returns: '.$return_code);
 
        if((! $return_code) || (($return_code == 503) && (stristr($a->get_curl_headers(),'retry-after')))) {
                logger('diaspora_transmit: queue message');
index 6293317d3f4401e9ec71b0fe213f14ad669193e9..a8f670334b5d66a00d8fc5022d584472dfa85b80 100644 (file)
@@ -76,11 +76,18 @@ function discover_poco_run(&$argv, &$argc){
                update_suggestions();
        elseif (($mode == 2) AND get_config('system','poco_completion'))
                discover_users();
-       elseif (($mode == 1) AND ($search != "") and get_config('system','poco_local_search'))
+       elseif (($mode == 1) AND ($search != "") and get_config('system','poco_local_search')) {
                discover_directory($search);
-       elseif (($mode == 0) AND ($search == "") and (get_config('system','poco_discovery') > 0))
+               gs_search_user($search);
+       } elseif (($mode == 0) AND ($search == "") and (get_config('system','poco_discovery') > 0)) {
+               // Query Friendica and Hubzilla servers for their users
                poco_discover();
 
+               // Query GNU Social servers for their users ("statistics" addon has to be enabled on the GS server)
+               if (!get_config('system','ostatus_disabled'))
+                       gs_discover();
+       }
+
        logger('end '.$search);
 
        return;
@@ -128,7 +135,7 @@ function discover_users() {
                else
                        $server_url = poco_detect_server($user["url"]);
 
-               if (poco_check_server($server_url, $gcontacts[0]["network"])) {
+               if (($server_url == "") OR poco_check_server($server_url, $gcontacts[0]["network"])) {
                        logger('Check user '.$user["url"]);
                        poco_last_updated($user["url"], true);
 
@@ -191,6 +198,36 @@ function discover_directory($search) {
        Cache::set("dirsearch:".$search, time(), CACHE_DAY);
 }
 
+/**
+ * @brief Search for GNU Social user with gstools.org
+ *
+ * @param str $search User name
+ */
+function gs_search_user($search) {
+
+       $a = get_app();
+
+       $url = "http://gstools.org/api/users_search/".urlencode($search);
+
+       $result = z_fetch_url($url);
+       if (!$result["success"])
+               return false;
+
+       $contacts = json_decode($result["body"]);
+
+       if ($contacts->status == 'ERROR')
+               return false;
+
+       foreach($contacts->data AS $user) {
+               $contact = probe_url($user->site_address."/".$user->name);
+               if ($contact["network"] != NETWORK_PHANTOM) {
+                       $contact["about"] = $user->description;
+                       update_gcontact($contact);
+               }
+       }
+}
+
+
 if (array_search(__file__,get_included_files())===0){
   discover_poco_run($_SERVER["argv"],$_SERVER["argc"]);
   killme();
index e02c61383136f5350b01af64928883c5715e924c..54b59857c2cdb5f72872c7644cb082962a51ddbe 100644 (file)
@@ -633,4 +633,129 @@ function notification($params) {
 
 }
 
+/**
+ * @brief Checks for item related notifications and sends them
+ *
+ * @param int $itemid ID of the item for which the check should be done
+ * @param int $uid User ID
+ * @param str $defaulttype (Optional) Forces a notification with this type.
+ */
+function check_item_notification($itemid, $uid, $defaulttype = "") {
+
+       $notification_data = array("uid" => $uid, "profiles" => array());
+       call_hooks('check_item_notification', $notification_data);
+
+       $profiles = $notification_data["profiles"];
+
+       $user = q("SELECT `notify-flags`, `language`, `username`, `email` FROM `user` WHERE `uid` = %d", intval($uid));
+       if (!$user)
+               return false;
+
+       $owner = q("SELECT `id`, `url` FROM `contact` WHERE `self` AND `uid` = %d LIMIT 1", intval($uid));
+       if (!$owner)
+               return false;
+
+       $profiles[] = $owner[0]["url"];
+
+       $profiles2 = array();
+
+       foreach ($profiles AS $profile) {
+               $profiles2[] = normalise_link($profile);
+               $profiles2[] = str_replace("http://", "https://", normalise_link($profile));
+       }
+
+       $profiles = $profiles2;
+
+       $profile_list = "";
+
+       foreach ($profiles AS $profile) {
+               if ($profile_list != "")
+                       $profile_list .= "', '";
+
+               $profile_list .= dbesc($profile);
+       }
+
+       $profile_list = "'".$profile_list."'";
+
+       // Only act if it is a "real" post
+       // We need the additional check for the "local_profile" because of mixed situations on connector networks
+       $item = q("SELECT `id`, `mention`, `tag`,`parent`, `title`, `body`, `author-name`, `author-link`, `author-avatar`, `guid`,
+                       `parent-uri`, `uri`, `contact-id`
+                       FROM `item` WHERE `id` = %d AND `verb` IN ('%s', '') AND `type` != 'activity' AND
+                               NOT (`author-link` IN ($profile_list))  LIMIT 1",
+               intval($itemid), dbesc(ACTIVITY_POST));
+       if (!$item)
+               return false;
+
+       // Generate the notification array
+       $params = array();
+       $params["uid"] = $uid;
+       $params["notify_flags"] = $user[0]["notify-flags"];
+       $params["language"] = $user[0]["language"];
+       $params["to_name"] = $user[0]["username"];
+       $params["to_email"] = $user[0]["email"];
+       $params["item"] = $item[0];
+       $params["parent"] = $item[0]["parent"];
+       $params["link"] = App::get_baseurl().'/display/'.urlencode($item[0]["guid"]);
+       $params["otype"] = 'item';
+       $params["source_name"] = $item[0]["author-name"];
+       $params["source_link"] = $item[0]["author-link"];
+       $params["source_photo"] = $item[0]["author-avatar"];
+
+       if ($item[0]["parent-uri"] === $item[0]["uri"]) {
+                // Send a notification for every new post?
+                $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
+                        intval($item[0]['contact-id']),
+                        intval($uid)
+                );
+                $send_notification = count($r);
+
+                if (!$send_notification) {
+                        $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
+                                intval(TERM_OBJ_POST), intval($itemid), intval(TERM_MENTION), intval($uid));
+
+                        if (count($tags)) {
+                                foreach ($tags AS $tag) {
+                                        $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
+                                                normalise_link($tag["url"]), intval($uid));
+                                        if (count($r))
+                                                $send_notification = true;
+                                }
+                        }
+                }
+
+               if ($send_notification) {
+                       $params["type"] = NOTIFY_SHARE;
+                       $params["verb"] = ACTIVITY_TAG;
+               }
+       }
+
+       // Is the user mentioned in this post?
+       $tagged = false;
+
+       foreach ($profiles AS $profile) {
+               if (strpos($item[0]["tag"], "=".$profile."]") OR strpos($item[0]["body"], "=".$profile."]"))
+                       $tagged = true;
+       }
+
+       if ($item[0]["mention"] OR $tagged OR ($defaulttype == NOTIFY_TAGSELF)) {
+               $params["type"] = NOTIFY_TAGSELF;
+               $params["verb"] = ACTIVITY_TAG;
+       }
+
+       // Is it a post that the user had started or where he interacted?
+       $parent = q("SELECT `thread`.`iid` FROM `thread` INNER JOIN `item` ON `item`.`parent` = `thread`.`iid`
+                       WHERE `thread`.`iid` = %d AND `thread`.`uid` = %d AND NOT `thread`.`ignored` AND
+                               (`thread`.`mention` OR `item`.`author-link` IN ($profile_list))
+                       LIMIT 1",
+                       intval($item[0]["parent"]), intval($uid));
+
+       if ($parent AND !isset($params["type"])) {
+               $params["type"] = NOTIFY_COMMENT;
+               $params["verb"] = ACTIVITY_POST;
+       }
+
+       if (isset($params["type"]))
+               notification($params);
+}
 ?>
index c4111dc0b174869be3fff15d6b58599a10d06bcd..a9f054fc2ed2bf530e9c6c0d9fbadf7570313c6d 100644 (file)
@@ -61,7 +61,7 @@ function format_event_html($ev, $simple = false) {
                        . bbcode($ev['location'])
                        . '</span></p>' . "\r\n";
 
-               if (strpos($ev['location'], "[map")===False) {
+               if (strpos($ev['location'], "[map") !== False) {
                        $map = generate_named_map($ev['location']);
                        if ($map!==$ev['location']) $o.=$map;
                }
@@ -76,7 +76,6 @@ function format_event_html($ev, $simple = false) {
 function parse_event($h) {
 
        require_once('include/Scrape.php');
-       require_once('library/HTMLPurifier.auto.php');
        require_once('include/html2bbcode');
 
        $h = '<html><body>' . $h . '</body></html>';
index 30868042167ab6416c079d92d56edf6f88438f0f..873c594e8499ea93aeaa55727abeb09f34e81e28 100644 (file)
@@ -18,7 +18,6 @@ function expire_run(&$argv, &$argc){
 
        require_once('include/session.php');
        require_once('include/datetime.php');
-       require_once('library/simplepie/simplepie.inc');
        require_once('include/items.php');
        require_once('include/Contact.php');
 
index 2639604576cf5951f956fa3abfdad55d0e951b01..87a9b46d5ab71dbd5e995cfb65fff58d7d45d7c3 100644 (file)
@@ -1,23 +1,25 @@
 <?php
 
 /**
- * @file include/features.php * 
+ * @file include/features.php
  * @brief Features management
  */
 
 /**
  * @brief check if feature is enabled
  * 
- * return boolean
+ * @return boolean
  */
 function feature_enabled($uid,$feature) {
-       //return true;
 
-       $x = get_pconfig($uid,'feature',$feature);
+       $x = get_config('feature_lock',$feature);
        if($x === false) {
-               $x = get_config('feature',$feature);
-               if($x === false)
-                       $x = get_feature_default($feature);
+               $x = get_pconfig($uid,'feature',$feature);
+               if($x === false) {
+                       $x = get_config('feature',$feature);
+                       if($x === false)
+                               $x = get_feature_default($feature);
+               }
        }
        $arr = array('uid' => $uid, 'feature' => $feature, 'enabled' => $x);
        call_hooks('feature_enabled',$arr);
@@ -42,14 +44,17 @@ function get_feature_default($feature) {
 }
 
 /**
- * @ brief get a list of all available features
+ * @brief Get a list of all available features
+ * 
  * The array includes the setting group, the setting name,
  * explainations for the setting and if it's enabled or disabled
  * by default
  * 
+ * @param bool $filtered True removes any locked features
+ * 
  * @return array
  */
-function get_features() {
+function get_features($filtered = true) {
 
        $arr = array(
 
@@ -57,56 +62,78 @@ function get_features() {
                'general' => array(
                        t('General Features'),
                        //array('expire',         t('Content Expiration'),              t('Remove old posts/comments after a period of time')),
-                       array('multi_profiles', t('Multiple Profiles'),                 t('Ability to create multiple profiles'),false),
-                       array('photo_location', t('Photo Location'),                    t('Photo metadata is normally stripped. This extracts the location (if present) prior to stripping metadata and links it to a map.'),false),
+                       array('multi_profiles', t('Multiple Profiles'),                 t('Ability to create multiple profiles'), false, get_config('feature_lock','multi_profiles')),
+                       array('photo_location', t('Photo Location'),                    t('Photo metadata is normally stripped. This extracts the location (if present) prior to stripping metadata and links it to a map.'), false, get_config('feature_lock','photo_location')),
                ),
 
                // Post composition
                'composition' => array(
                        t('Post Composition Features'),
-                       array('richtext',       t('Richtext Editor'),                   t('Enable richtext editor'),false),
-                       array('preview',        t('Post Preview'),                      t('Allow previewing posts and comments before publishing them'),false),
-                       array('aclautomention', t('Auto-mention Forums'),               t('Add/remove mention when a fourm page is selected/deselected in ACL window.'),false),
+                       array('richtext',       t('Richtext Editor'),                   t('Enable richtext editor'), false, get_config('feature_lock','richtext')),
+                       array('preview',        t('Post Preview'),                      t('Allow previewing posts and comments before publishing them'), false, get_config('feature_lock','preview')),
+                       array('aclautomention', t('Auto-mention Forums'),               t('Add/remove mention when a fourm page is selected/deselected in ACL window.'), false, get_config('feature_lock','aclautomention')),
                ),
 
                // Network sidebar widgets
                'widgets' => array(
                        t('Network Sidebar Widgets'),
-                       array('archives',       t('Search by Date'),                    t('Ability to select posts by date ranges'),false),
-                       array('forumlist_widget', t('List Forums'),                     t('Enable widget to display the forums your are connected with'),true),
-                       array('groups',         t('Group Filter'),                      t('Enable widget to display Network posts only from selected group'),false),
-                       array('networks',       t('Network Filter'),                    t('Enable widget to display Network posts only from selected network'),false),
-                       array('savedsearch',    t('Saved Searches'),                    t('Save search terms for re-use'),false),
+                       array('archives',       t('Search by Date'),                    t('Ability to select posts by date ranges'), false, get_config('feature_lock','archives')),
+                       array('forumlist_widget', t('List Forums'),                     t('Enable widget to display the forums your are connected with'), true, get_config('feature_lock','forumlist_widget')),
+                       array('groups',         t('Group Filter'),                      t('Enable widget to display Network posts only from selected group'), false, get_config('feature_lock','groups')),
+                       array('networks',       t('Network Filter'),                    t('Enable widget to display Network posts only from selected network'), false, get_config('feature_lock','networks')),
+                       array('savedsearch',    t('Saved Searches'),                    t('Save search terms for re-use'), false, get_config('feature_lock','savedsearch')),
                ),
 
                // Network tabs
                'net_tabs' => array(
                        t('Network Tabs'),
-                       array('personal_tab',   t('Network Personal Tab'),              t('Enable tab to display only Network posts that you\'ve interacted on'),false),
-                       array('new_tab',        t('Network New Tab'),                   t('Enable tab to display only new Network posts (from the last 12 hours)'),false),
-                       array('link_tab',       t('Network Shared Links Tab'),          t('Enable tab to display only Network posts with links in them'),false),
+                       array('personal_tab',   t('Network Personal Tab'),              t('Enable tab to display only Network posts that you\'ve interacted on'), false, get_config('feature_lock','personal_tab')),
+                       array('new_tab',        t('Network New Tab'),                   t('Enable tab to display only new Network posts (from the last 12 hours)'), false, get_config('feature_lock','new_tab')),
+                       array('link_tab',       t('Network Shared Links Tab'),          t('Enable tab to display only Network posts with links in them'), false, get_config('feature_lock','link_tab')),
                ),
 
                // Item tools
                'tools' => array(
                        t('Post/Comment Tools'),
-                       array('multi_delete',   t('Multiple Deletion'),                 t('Select and delete multiple posts/comments at once'),false),
-                       array('edit_posts',     t('Edit Sent Posts'),                   t('Edit and correct posts and comments after sending'),false),
-                       array('commtag',        t('Tagging'),                           t('Ability to tag existing posts'),false),
-                       array('categories',     t('Post Categories'),                   t('Add categories to your posts'),false),
-                       array('filing',         t('Saved Folders'),                     t('Ability to file posts under folders'),false),
-                       array('dislike',        t('Dislike Posts'),                     t('Ability to dislike posts/comments')),
-                       array('star_posts',     t('Star Posts'),                        t('Ability to mark special posts with a star indicator'),false),
-                       array('ignore_posts',   t('Mute Post Notifications'),           t('Ability to mute notifications for a thread'),false),
+                       array('multi_delete',   t('Multiple Deletion'),                 t('Select and delete multiple posts/comments at once'), false, get_config('feature_lock','multi_delete')),
+                       array('edit_posts',     t('Edit Sent Posts'),                   t('Edit and correct posts and comments after sending'), false, get_config('feature_lock','edit_posts')),
+                       array('commtag',        t('Tagging'),                           t('Ability to tag existing posts'), false, get_config('feature_lock','commtag')),
+                       array('categories',     t('Post Categories'),                   t('Add categories to your posts'), false, get_config('feature_lock','categories')),
+                       array('filing',         t('Saved Folders'),                     t('Ability to file posts under folders'), false, get_config('feature_lock','filing')),
+                       array('dislike',        t('Dislike Posts'),                     t('Ability to dislike posts/comments'), false, get_config('feature_lock','dislike')),
+                       array('star_posts',     t('Star Posts'),                        t('Ability to mark special posts with a star indicator'), false, get_config('feature_lock','star_posts')),
+                       array('ignore_posts',   t('Mute Post Notifications'),           t('Ability to mute notifications for a thread'), false, get_config('feature_lock','ignore_posts')),
                ),
 
                // Advanced Profile Settings
                'advanced_profile' => array(
                        t('Advanced Profile Settings'),
-                       array('forumlist_profile', t('List Forums'),                    t('Show visitors public community forums at the Advanced Profile Page'),false),
+                       array('forumlist_profile', t('List Forums'),                    t('Show visitors public community forums at the Advanced Profile Page'), false, get_config('feature_lock','forumlist_profile')),
                ),
        );
 
+       // removed any locked features and remove the entire category if this makes it empty
+
+       if($filtered) {
+               foreach($arr as $k => $x) {
+                       $has_items = false;
+                       $kquantity = count($arr[$k]);
+                       for($y = 0; $y < $kquantity; $y ++) {
+                               if(is_array($arr[$k][$y])) {
+                                       if($arr[$k][$y][4] === false) {
+                                               $has_items = true;
+                                       }
+                                       else {
+                                               unset($arr[$k][$y]);
+                                       }
+                               }
+                       }
+                       if(! $has_items) {
+                               unset($arr[$k]);
+                       }
+               }
+       }
+
        call_hooks('get_features',$arr);
        return $arr;
 }
index 21c05c8f3ef139062963dfbd05446b96f492d9a4..22ff079b633949b941201d1578dd47e770b76670 100644 (file)
@@ -264,24 +264,8 @@ function new_contact($uid,$url,$interactive = false) {
 
        require_once("include/Photo.php");
 
-       $photos = import_profile_photo($ret['photo'],$uid,$contact_id);
-
-       $r = q("UPDATE `contact` SET `photo` = '%s',
-                       `thumb` = '%s',
-                       `micro` = '%s',
-                       `name-date` = '%s',
-                       `uri-date` = '%s',
-                       `avatar-date` = '%s'
-                       WHERE `id` = %d",
-                       dbesc($photos[0]),
-                       dbesc($photos[1]),
-                       dbesc($photos[2]),
-                       dbesc(datetime_convert()),
-                       dbesc(datetime_convert()),
-                       dbesc(datetime_convert()),
-                       intval($contact_id)
-               );
-
+       // Update the avatar
+       update_contact_avatar($ret['photo'],$uid,$contact_id);
 
        // pull feed and consume it, which should subscribe to the hub.
 
diff --git a/include/forums.php b/include/forums.php
deleted file mode 100644 (file)
index 995a29c..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-<?php
-
-/**
- * @file include/forums.php
- * @brief Functions related to forum functionality *
- */
-
-
-/**
- * @brief Function to list all forums a user is connected with
- *
- * @param int $uid of the profile owner
- * @param boolean $showhidden
- *     Show frorums which are not hidden
- * @param boolean $lastitem
- *     Sort by lastitem
- * @param boolean $showprivate
- *     Show private groups
- *
- * @returns array
- *     'url'   => forum url
- *     'name'  => forum name
- *     'id'    => number of the key from the array
- *     'micro' => contact photo in format micro
- */
-function get_forumlist($uid, $showhidden = true, $lastitem, $showprivate = false) {
-
-       $forumlist = array();
-
-       $order = (($showhidden) ? '' : ' AND NOT `hidden` ');
-       $order .= (($lastitem) ? ' ORDER BY `last-item` DESC ' : ' ORDER BY `name` ASC ');
-       $select = '`forum` ';
-       if ($showprivate) {
-               $select = '(`forum` OR `prv`)';
-       }
-
-       $contacts = q("SELECT `contact`.`id`, `contact`.`url`, `contact`.`name`, `contact`.`micro` FROM `contact`
-                       WHERE `network`= 'dfrn' AND $select AND `uid` = %d
-                       AND NOT `blocked` AND NOT `hidden` AND NOT `pending` AND NOT `archive`
-                       AND `success_update` > `failure_update`
-                       $order ",
-                       intval($uid)
-       );
-
-       foreach($contacts as $contact) {
-               $forumlist[] = array(
-                       'url'   => $contact['url'],
-                       'name'  => $contact['name'],
-                       'id'    => $contact['id'],
-                       'micro' => $contact['micro'],
-               );
-       }
-       return($forumlist);
-}
-
-
-/**
- * @brief Forumlist widget
- *
- * Sidebar widget to show subcribed friendica forums. If activated
- * in the settings, it appears at the notwork page sidebar
- *
- * @param int $uid
- * @param int $cid
- *     The contact id which is used to mark a forum as "selected"
- * @return string
- */
-function widget_forumlist($uid,$cid = 0) {
-
-       if(! intval(feature_enabled(local_user(),'forumlist_widget')))
-               return;
-
-       $o = '';
-
-       //sort by last updated item
-       $lastitem = true;
-
-       $contacts = get_forumlist($uid,true,$lastitem, true);
-       $total = count($contacts);
-       $visible_forums = 10;
-
-       if(count($contacts)) {
-
-               $id = 0;
-
-               foreach($contacts as $contact) {
-
-                       $selected = (($cid == $contact['id']) ? ' forum-selected' : '');
-
-                       $entry = array(
-                               'url' => z_root() . '/network?f=&cid=' . $contact['id'],
-                               'external_url' => z_root() . '/redir/' . $contact['id'],
-                               'name' => $contact['name'],
-                               'cid' => $contact['id'],
-                               'selected'      => $selected,
-                               'micro' => proxy_url($contact['micro'], false, PROXY_SIZE_MICRO),
-                               'id' => ++$id,
-                       );
-                       $entries[] = $entry;
-               }
-
-               $tpl = get_markup_template('widget_forumlist.tpl');
-
-               $o .= replace_macros($tpl,array(
-                       '$title'        => t('Forums'),
-                       '$forums'       => $entries,
-                       '$link_desc'    => t('External link to forum'),
-                       '$total'        => $total,
-                       '$visible_forums' => $visible_forums,
-                       '$showmore'     => t('show more'),
-               ));
-       }
-
-       return $o;
-}
-
-/**
- * @brief Format forumlist as contact block
- *
- * This function is used to show the forumlist in
- * the advanced profile.
- *
- * @param int $uid
- * @return string
- *
- */
-function forumlist_profile_advanced($uid) {
-
-       $profile = intval(feature_enabled($uid,'forumlist_profile'));
-       if(! $profile)
-               return;
-
-       $o = '';
-
-       // place holder in case somebody wants configurability
-       $show_total = 9999;
-
-       //don't sort by last updated item
-       $lastitem = false;
-
-       $contacts = get_forumlist($uid,false,$lastitem,false);
-
-       $total_shown = 0;
-
-       foreach($contacts as $contact) {
-               $forumlist .= micropro($contact,false,'forumlist-profile-advanced');
-               $total_shown ++;
-               if($total_shown == $show_total)
-                       break;
-       }
-
-       if(count($contacts) > 0)
-               $o .= $forumlist;
-               return $o;
-}
-
-/**
- * @brief count unread forum items
- *
- * Count unread items of connected forums and private groups
- *
- * @return array
- *     'id' => contact id
- *     'name' => contact/forum name
- *     'count' => counted unseen forum items
- *
- */
-
-function forums_count_unseen() {
-       $r = q("SELECT `contact`.`id`, `contact`.`name`, COUNT(*) AS `count` FROM `item`
-                       INNER JOIN `contact` ON `item`.`contact-id` = `contact`.`id`
-                       WHERE `item`.`uid` = %d AND `item`.`visible` AND NOT `item`.`deleted` AND `item`.`unseen`
-                       AND `contact`.`network`= 'dfrn' AND (`contact`.`forum` OR `contact`.`prv`)
-                       AND NOT `contact`.`blocked` AND NOT `contact`.`hidden`
-                       AND NOT `contact`.`pending` AND NOT `contact`.`archive`
-                       AND `contact`.`success_update` > `failure_update`
-                       GROUP BY `contact`.`id` ",
-               intval(local_user())
-       );
-
-       return $r;
-}
index 99dc12bf8d9a27c96d6a2b79bed01e9939ad2aa0..9ba2d2a744dcc88fea320253dec337d54345fefd 100644 (file)
@@ -60,6 +60,8 @@ class FriendicaSmartyEngine implements ITemplateEngine {
                        $template = $s;
                        $s = new FriendicaSmarty();
                }
+               
+               $r['$APP'] = get_app();
 
                // "middleware": inject variables into templates
                $arr = array(
index 84292f263affb3fb7d6daed93f566674f20f2e38..dfa9137d7de03d6677a20b29a7cdf699c51bb121 100644 (file)
@@ -33,7 +33,7 @@ function gprobe_run(&$argv, &$argc){
 
        $url = hex2bin($argv[1]);
 
-       $r = q("select * from gcontact where nurl = '%s' limit 1",
+       $r = q("SELECT `id`, `url`, `network` FROM `gcontact` WHERE `nurl` = '%s' ORDER BY `id` LIMIT 1",
                dbesc(normalise_link($url))
        );
 
@@ -58,21 +58,16 @@ function gprobe_run(&$argv, &$argc){
                if (is_null($result))
                        Cache::set("gprobe:".$urlparts["host"],serialize($arr));
 
-               if(count($arr) && x($arr,'network') && $arr['network'] === NETWORK_DFRN) {
-                       q("insert into `gcontact` (`name`,`url`,`nurl`,`photo`)
-                               values ( '%s', '%s', '%s', '%s') ",
-                               dbesc($arr['name']),
-                               dbesc($arr['url']),
-                               dbesc(normalise_link($arr['url'])),
-                               dbesc($arr['photo'])
-                       );
-               }
-               $r = q("select * from gcontact where nurl = '%s' limit 1",
+               if (!in_array($result["network"], array(NETWORK_FEED, NETWORK_PHANTOM)))
+                       update_gcontact($arr);
+
+               $r = q("SELECT `id`, `url`, `network` FROM `gcontact` WHERE `nurl` = '%s' ORDER BY `id` LIMIT 1",
                        dbesc(normalise_link($url))
                );
        }
        if(count($r))
-               poco_load(0,0,$r[0]['id'], str_replace('/profile/','/poco/',$r[0]['url']));
+               if ($r[0]["network"] == NETWORK_DFRN)
+                       poco_load(0,0,$r[0]['id'], str_replace('/profile/','/poco/',$r[0]['url']));
 
        logger("gprobe end for ".normalise_link($url), LOGGER_DEBUG);
        return;
index fd1c97dfde47d1af8c665b03eac51370428a7940..2b872f16a7ce95234bdae038d3546df1f1242147 100644 (file)
@@ -215,7 +215,7 @@ function mini_group_select($uid,$gid = 0) {
 
 /**
  * @brief Create group sidebar widget
- * 
+ *
  * @param string $every
  * @param string $each
  * @param string $editmode
@@ -234,7 +234,7 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro
                return '';
 
        $groups = array();
-       
+
        $groups[] = array(
                'text'  => t('Everybody'),
                'id' => 0,
@@ -255,7 +255,7 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro
        if(count($r)) {
                foreach($r as $rr) {
                        $selected = (($group_id == $rr['id']) ? ' group-selected' : '');
-                       
+
                        if ($editmode == "full") {
                                $groupedit = array(
                                        'href' => "group/".$rr['id'],
@@ -264,7 +264,7 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro
                        } else {
                                $groupedit = null;
                        }
-                       
+
                        $groups[] = array(
                                'id'            => $rr['id'],
                                'cid'           => $cid,
@@ -297,17 +297,26 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro
        return $o;
 }
 
-function expand_groups($a,$check_dead = false) {
+function expand_groups($a,$check_dead = false, $use_gcontact = false) {
        if(! (is_array($a) && count($a)))
                return array();
        $groups = implode(',', $a);
        $groups = dbesc($groups);
-       $r = q("SELECT `contact-id` FROM `group_member` WHERE `gid` IN ( $groups )");
+
+       if ($use_gcontact)
+               $r = q("SELECT `gcontact`.`id` AS `contact-id` FROM `group_member`
+                               INNER JOIN `contact` ON `contact`.`id` = `group_member`.`contact-id`
+                               INNER JOIN `gcontact` ON `gcontact`.`nurl` = `contact`.`nurl`
+                       WHERE `gid` IN ($groups)");
+       else
+               $r = q("SELECT `contact-id` FROM `group_member` WHERE `gid` IN ( $groups )");
+
+
        $ret = array();
        if(count($r))
                foreach($r as $rr)
                        $ret[] = $rr['contact-id'];
-       if($check_dead) {
+       if($check_dead AND !$use_gcontact) {
                require_once('include/acl_selectors.php');
                $ret = prune_deadguys($ret);
        }
@@ -353,17 +362,16 @@ function groups_containing($uid,$c) {
  */
 function groups_count_unseen() {
 
-       $r = q("SELECT `group`.`id`, `group`.`name`, COUNT(`item`.`id`) AS `count` FROM `group`, `group_member`, `item`
-                       WHERE `group`.`uid` = %d
-                       AND `item`.`uid` = %d
-                       AND `item`.`unseen` AND `item`.`visible`
-                       AND NOT `item`.`deleted`
-                       AND `item`.`contact-id` = `group_member`.`contact-id`
-                       AND `group_member`.`gid` = `group`.`id`
-                       GROUP BY `group`.`id` ",
+       $r = q("SELECT `group`.`id`, `group`.`name`,
+                       (SELECT COUNT(*) FROM `item`
+                               WHERE `uid` = %d AND `unseen` AND
+                                       `contact-id` IN (SELECT `contact-id` FROM `group_member`
+                                                               WHERE `group_member`.`gid` = `group`.`id` AND `group_member`.`uid` = %d)) AS `count`
+                       FROM `group` WHERE `group`.`uid` = %d;",
+               intval(local_user()),
                intval(local_user()),
                intval(local_user())
        );
 
        return $r;
-}
\ No newline at end of file
+}
index fb405b90f17b278ea035cf11f0cb4846f7504d65..ec66225d0ffd8261e82cbf87def67c723276186f 100644 (file)
@@ -3,7 +3,7 @@
  * @file include/identity.php
  */
 
-require_once('include/forums.php');
+require_once('include/ForumManager.php');
 require_once('include/bbcode.php');
 require_once("mod/proxy.php");
 
@@ -300,6 +300,7 @@ function profile_sidebar($profile, $block = 0) {
                $account_type = "";
 
        if((x($profile,'address') == 1)
+                       || (x($profile,'location') == 1)
                        || (x($profile,'locality') == 1)
                        || (x($profile,'region') == 1)
                        || (x($profile,'postal-code') == 1)
@@ -368,6 +369,8 @@ function profile_sidebar($profile, $block = 0) {
 
        if (isset($p["address"]))
                $p["address"] = bbcode($p["address"]);
+       else
+               $p["address"] = bbcode($p["location"]);
 
        if (isset($p["photo"]))
                $p["photo"] = proxy_url($p["photo"], false, PROXY_SIZE_SMALL);
@@ -652,7 +655,7 @@ function advanced_profile(&$a) {
        
                //show subcribed forum if it is enabled in the usersettings
                if (feature_enabled($uid,'forumlist_profile')) {
-                       $profile['forumlist'] = array( t('Forums:'), forumlist_profile_advanced($uid));
+                       $profile['forumlist'] = array( t('Forums:'), ForumManager::profile_advanced($uid));
                }
 
                if ($a->profile['uid'] == local_user())
index c9e13b1a1f3f084cfff8803032b848254103c743..1af6fe1b522686e7cbe8285a62b3644e5c364bf2 100644 (file)
@@ -14,314 +14,19 @@ require_once('include/socgraph.php');
 require_once('include/plaintext.php');
 require_once('include/ostatus.php');
 require_once('include/feed.php');
+require_once('include/Contact.php');
 require_once('mod/share.php');
+require_once('include/enotify.php');
+require_once('include/dfrn.php');
 
 require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
 
-
-function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0, $forpubsub = false) {
-
-
-       $sitefeed    = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
-       $public_feed = (($dfrn_id) ? false : true);
-       $starred     = false;   // not yet implemented, possible security issues
-       $converse    = false;
-
-       if($public_feed && $a->argc > 2) {
-               for($x = 2; $x < $a->argc; $x++) {
-                       if($a->argv[$x] == 'converse')
-                               $converse = true;
-                       if($a->argv[$x] == 'starred')
-                               $starred = true;
-                       if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
-                               $category = $a->argv[$x+1];
-               }
-       }
-
-
-
-       // default permissions - anonymous user
-
-       $sql_extra = " AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid`  = '' AND `item`.`deny_gid`  = '' ";
-
-       $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
-               FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
-               WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1",
-               dbesc($owner_nick)
-       );
-
-       if(! count($r))
-               killme();
-
-       $owner = $r[0];
-       $owner_id = $owner['user_uid'];
-       $owner_nick = $owner['nickname'];
-
-       $birthday = feed_birthday($owner_id,$owner['timezone']);
-
-       $sql_post_table = "";
-       $visibility = "";
-
-       if(! $public_feed) {
-
-               $sql_extra = '';
-               switch($direction) {
-                       case (-1):
-                               $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id));
-                               $my_id = $dfrn_id;
-                               break;
-                       case 0:
-                               $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
-                               $my_id = '1:' . $dfrn_id;
-                               break;
-                       case 1:
-                               $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id));
-                               $my_id = '0:' . $dfrn_id;
-                               break;
-                       default:
-                               return false;
-                               break; // NOTREACHED
-               }
-
-               $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1",
-                       intval($owner_id)
-               );
-
-               if(! count($r))
-                       killme();
-
-               $contact = $r[0];
-               require_once('include/security.php');
-               $groups = init_groups_visitor($contact['id']);
-
-               if(count($groups)) {
-                       for($x = 0; $x < count($groups); $x ++)
-                               $groups[$x] = '<' . intval($groups[$x]) . '>' ;
-                       $gs = implode('|', $groups);
-               }
-               else
-                       $gs = '<<>>' ; // Impossible to match
-
-               $sql_extra = sprintf("
-                       AND ( `allow_cid` = '' OR     `allow_cid` REGEXP '<%d>' )
-                       AND ( `deny_cid`  = '' OR NOT `deny_cid`  REGEXP '<%d>' )
-                       AND ( `allow_gid` = '' OR     `allow_gid` REGEXP '%s' )
-                       AND ( `deny_gid`  = '' OR NOT `deny_gid`  REGEXP '%s')
-               ",
-                       intval($contact['id']),
-                       intval($contact['id']),
-                       dbesc($gs),
-                       dbesc($gs)
-               );
-       }
-
-       if($public_feed)
-               $sort = 'DESC';
-       else
-               $sort = 'ASC';
-
-       // Include answers to status.net posts in pubsub feeds
-       if($forpubsub) {
-               $sql_post_table = "INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent`
-                               LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid`";
-               $visibility = sprintf("AND (`item`.`parent` = `item`.`id`) OR (`item`.`network` = '%s' AND ((`thread`.`network`='%s') OR (`thritem`.`network` = '%s')))",
-                                       dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS));
-               $date_field = "`received`";
-               $sql_order = "`item`.`received` DESC";
-       } else {
-               $date_field = "`changed`";
-               $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC";
-       }
-
-       if(! strlen($last_update))
-               $last_update = 'now -30 days';
-
-       if(isset($category)) {
-               $sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
-                               dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id));
-               //$sql_extra .= file_tag_file_query('item',$category,'category');
-       }
-
-       if($public_feed) {
-               if(! $converse)
-                       $sql_extra .= " AND `contact`.`self` = 1 ";
-       }
-
-       $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
-
-       //      AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' )
-       //      dbesc($check_date),
-
-       $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`,
-               `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`,
-               `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`,
-               `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
-               `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`,
-               `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer`
-               FROM `item` $sql_post_table
-               INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
-               AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
-               LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
-               WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0
-               AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s'
-               $sql_extra
-               ORDER BY $sql_order LIMIT 0, 300",
-               intval($owner_id),
-               dbesc($check_date),
-               dbesc($sort)
-       );
-
-       // Will check further below if this actually returned results.
-       // We will provide an empty feed if that is the case.
-
-       $items = $r;
-
-       $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl');
-
-       $atom = '';
-
-       $hubxml = feed_hublinks();
-
-       $salmon = feed_salmonlinks($owner_nick);
-
-       $alternatelink = $owner['url'];
-
-       if(isset($category))
-               $alternatelink .= "/category/".$category;
-
-       $atom .= replace_macros($feed_template, array(
-               '$version'      => xmlify(FRIENDICA_VERSION),
-               '$feed_id'      => xmlify($a->get_baseurl() . '/profile/' . $owner_nick),
-               '$feed_title'   => xmlify($owner['name']),
-               '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) ,
-               '$hub'          => $hubxml,
-               '$salmon'       => $salmon,
-               '$alternatelink' => xmlify($alternatelink),
-               '$name'         => xmlify($owner['name']),
-               '$profile_page' => xmlify($owner['url']),
-               '$photo'        => xmlify($owner['photo']),
-               '$thumb'        => xmlify($owner['thumb']),
-               '$picdate'      => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) ,
-               '$uridate'      => xmlify(datetime_convert('UTC','UTC',$owner['uri-date']    . '+00:00' , ATOM_TIME)) ,
-               '$namdate'      => xmlify(datetime_convert('UTC','UTC',$owner['name-date']   . '+00:00' , ATOM_TIME)) ,
-               '$birthday'     => ((strlen($birthday)) ? '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>' : ''),
-               '$community'    => (($owner['page-flags'] == PAGE_COMMUNITY) ? '<dfrn:community>1</dfrn:community>' : '')
-       ));
-
-       call_hooks('atom_feed', $atom);
-
-       if(! count($items)) {
-
-               call_hooks('atom_feed_end', $atom);
-
-               $atom .= '</feed>' . "\r\n";
-               return $atom;
-       }
-
-       foreach($items as $item) {
-
-               // prevent private email from leaking.
-               if($item['network'] === NETWORK_MAIL)
-                       continue;
-
-               // public feeds get html, our own nodes use bbcode
-
-               if($public_feed) {
-                       $type = 'html';
-                       // catch any email that's in a public conversation and make sure it doesn't leak
-                       if($item['private'])
-                               continue;
-               }
-               else {
-                       $type = 'text';
-               }
-
-               $atom .= atom_entry($item,$type,null,$owner,true);
-       }
-
-       call_hooks('atom_feed_end', $atom);
-
-       $atom .= '</feed>' . "\r\n";
-
-       return $atom;
-}
-
-
 function construct_verb($item) {
        if($item['verb'])
                return $item['verb'];
        return ACTIVITY_POST;
 }
 
-function construct_activity_object($item) {
-
-       if($item['object']) {
-               $o = '<as:object>' . "\r\n";
-               $r = parse_xml_string($item['object'],false);
-
-
-               if(! $r)
-                       return '';
-               if($r->type)
-                       $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
-               if($r->id)
-                       $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
-               if($r->title)
-                       $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
-               if($r->link) {
-                       if(substr($r->link,0,1) === '<') {
-                               // patch up some facebook "like" activity objects that got stored incorrectly
-                               // for a couple of months prior to 9-Jun-2011 and generated bad XML.
-                               // we can probably remove this hack here and in the following function in a few months time.
-                               if(strstr($r->link,'&') && (! strstr($r->link,'&amp;')))
-                                       $r->link = str_replace('&','&amp;', $r->link);
-                               $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
-                               $o .= $r->link;
-                       }
-                       else
-                               $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
-               }
-               if($r->content)
-                       $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
-               $o .= '</as:object>' . "\r\n";
-               return $o;
-       }
-
-       return '';
-}
-
-function construct_activity_target($item) {
-
-       if($item['target']) {
-               $o = '<as:target>' . "\r\n";
-               $r = parse_xml_string($item['target'],false);
-               if(! $r)
-                       return '';
-               if($r->type)
-                       $o .= '<as:object-type>' . xmlify($r->type) . '</as:object-type>' . "\r\n";
-               if($r->id)
-                       $o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
-               if($r->title)
-                       $o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
-               if($r->link) {
-                       if(substr($r->link,0,1) === '<') {
-                               if(strstr($r->link,'&') && (! strstr($r->link,'&amp;')))
-                                       $r->link = str_replace('&','&amp;', $r->link);
-                               $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
-                               $o .= $r->link;
-                       }
-                       else
-                               $o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
-               }
-               if($r->content)
-                       $o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
-               $o .= '</as:target>' . "\r\n";
-               return $o;
-       }
-
-       return '';
-}
-
 /* limit_body_size()
  *
  *             The purpose of this function is to apply system message length limits to
@@ -440,482 +145,6 @@ function title_is_body($title, $body) {
        return($title == $body);
 }
 
-
-
-function get_atom_elements($feed, $item, $contact = array()) {
-
-       require_once('library/HTMLPurifier.auto.php');
-       require_once('include/html2bbcode.php');
-
-       $best_photo = array();
-
-       $res = array();
-
-       $author = $item->get_author();
-       if($author) {
-               $res['author-name'] = unxmlify($author->get_name());
-               $res['author-link'] = unxmlify($author->get_link());
-       }
-       else {
-               $res['author-name'] = unxmlify($feed->get_title());
-               $res['author-link'] = unxmlify($feed->get_permalink());
-       }
-       $res['uri'] = unxmlify($item->get_id());
-       $res['title'] = unxmlify($item->get_title());
-       $res['body'] = unxmlify($item->get_content());
-       $res['plink'] = unxmlify($item->get_link(0));
-
-       if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
-               logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG);
-               $res['title'] = "";
-               $res['body'] = nl2br($res['body']);
-       }
-
-       // removing the content of the title if its identically to the body
-       // This helps with auto generated titles e.g. from tumblr
-       if (title_is_body($res["title"], $res["body"]))
-               $res['title'] = "";
-
-       if($res['plink'])
-               $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
-       else
-               $base_url = '';
-
-       // look for a photo. We should check media size and find the best one,
-       // but for now let's just find any author photo
-       // Additionally we look for an alternate author link. On OStatus this one is the one we want.
-
-       $authorlinks = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"]["http://www.w3.org/2005/Atom"]["link"];
-       if (is_array($authorlinks)) {
-               foreach ($authorlinks as $link) {
-                       $linkdata = array_shift($link["attribs"]);
-
-                       if ($linkdata["rel"] == "alternate")
-                               $res["author-link"] = $linkdata["href"];
-               };
-       }
-
-       $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
-
-       if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
-               $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
-               foreach($base as $link) {
-                       if($link['attribs']['']['rel'] === 'alternate')
-                               $res['author-link'] = unxmlify($link['attribs']['']['href']);
-
-                       if(!x($res, 'author-avatar') || !$res['author-avatar']) {
-                               if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
-                                       $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
-                       }
-               }
-       }
-
-       $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
-
-       if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
-               $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
-               if($base && count($base)) {
-                       foreach($base as $link) {
-                               if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
-                                       $res['author-link'] = unxmlify($link['attribs']['']['href']);
-                               if(!x($res, 'author-avatar') || !$res['author-avatar']) {
-                                       if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
-                                               $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
-                               }
-                       }
-               }
-       }
-
-       // No photo/profile-link on the item - look at the feed level
-
-       if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) {
-               $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
-               if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
-                       $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
-                       foreach($base as $link) {
-                               if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
-                                       $res['author-link'] = unxmlify($link['attribs']['']['href']);
-                               if(! $res['author-avatar']) {
-                                       if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
-                                               $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
-                               }
-                       }
-               }
-
-               $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
-
-               if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
-                       $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
-
-                       if($base && count($base)) {
-                               foreach($base as $link) {
-                                       if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link']))
-                                               $res['author-link'] = unxmlify($link['attribs']['']['href']);
-                                       if(! (x($res,'author-avatar'))) {
-                                               if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
-                                                       $res['author-avatar'] = unxmlify($link['attribs']['']['href']);
-                                       }
-                               }
-                       }
-               }
-       }
-
-       $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info');
-       if($apps && $apps[0]['attribs']['']['source']) {
-               $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
-               if($res['app'] === 'web')
-                       $res['app'] = 'OStatus';
-       }
-
-       // base64 encoded json structure representing Diaspora signature
-
-       $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature');
-       if($dsig) {
-               $res['dsprsig'] = unxmlify($dsig[0]['data']);
-       }
-
-       $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid');
-       if($dguid)
-               $res['guid'] = unxmlify($dguid[0]['data']);
-
-       $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark');
-       if($bm)
-               $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0);
-
-
-       /**
-        * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
-        */
-
-       $have_real_body = false;
-
-       $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
-       if($rawenv) {
-               $have_real_body = true;
-               $res['body'] = $rawenv[0]['data'];
-               $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
-               // make sure nobody is trying to sneak some html tags by us
-               $res['body'] = notags(base64url_decode($res['body']));
-       }
-
-
-       $res['body'] = limit_body_size($res['body']);
-
-       // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
-       // the content type. Our own network only emits text normally, though it might have been converted to
-       // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
-       // have to assume it is all html and needs to be purified.
-
-       // It doesn't matter all that much security wise - because before this content is used anywhere, we are
-       // going to escape any tags we find regardless, but this lets us import a limited subset of html from
-       // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
-       // html.
-
-       if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
-
-               $res['body'] = reltoabs($res['body'],$base_url);
-
-               $res['body'] = html2bb_video($res['body']);
-
-               $res['body'] = oembed_html2bbcode($res['body']);
-
-               $config = HTMLPurifier_Config::createDefault();
-               $config->set('Cache.DefinitionImpl', null);
-
-               // we shouldn't need a whitelist, because the bbcode converter
-               // will strip out any unsupported tags.
-
-               $purifier = new HTMLPurifier($config);
-               $res['body'] = $purifier->purify($res['body']);
-
-               $res['body'] = @html2bbcode($res['body']);
-
-
-       }
-       elseif(! $have_real_body) {
-
-               // it's not one of our messages and it has no tags
-               // so it's probably just text. We'll escape it just to be safe.
-
-               $res['body'] = escape_tags($res['body']);
-       }
-
-
-       // this tag is obsolete but we keep it for really old sites
-
-       $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow');
-       if($allow && $allow[0]['data'] == 1)
-               $res['last-child'] = 1;
-       else
-               $res['last-child'] = 0;
-
-       $private = $item->get_item_tags(NAMESPACE_DFRN,'private');
-       if($private && intval($private[0]['data']) > 0)
-               $res['private'] = intval($private[0]['data']);
-       else
-               $res['private'] = 0;
-
-       $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid');
-       if($extid && $extid[0]['data'])
-               $res['extid'] = $extid[0]['data'];
-
-       $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
-       if($rawlocation)
-               $res['location'] = unxmlify($rawlocation[0]['data']);
-
-
-       $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published');
-       if($rawcreated)
-               $res['created'] = unxmlify($rawcreated[0]['data']);
-
-
-       $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated');
-       if($rawedited)
-               $res['edited'] = unxmlify($rawedited[0]['data']);
-
-       if((x($res,'edited')) && (! (x($res,'created'))))
-               $res['created'] = $res['edited'];
-
-       if(! $res['created'])
-               $res['created'] = $item->get_date('c');
-
-       if(! $res['edited'])
-               $res['edited'] = $item->get_date('c');
-
-
-       // Disallow time travelling posts
-
-       $d1 = strtotime($res['created']);
-       $d2 = strtotime($res['edited']);
-       $d3 = strtotime('now');
-
-       if($d1 > $d3)
-               $res['created'] = datetime_convert();
-       if($d2 > $d3)
-               $res['edited'] = datetime_convert();
-
-       $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
-       if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
-               $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
-       elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
-               $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
-       if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
-               $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
-       elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
-               $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
-
-       if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
-               $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
-
-               foreach($base as $link) {
-                       if(!x($res, 'owner-avatar') || !$res['owner-avatar']) {
-                               if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
-                                       $res['owner-avatar'] = unxmlify($link['attribs']['']['href']);
-                       }
-               }
-       }
-
-       $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point');
-       if($rawgeo)
-               $res['coord'] = unxmlify($rawgeo[0]['data']);
-
-       if ($contact["network"] == NETWORK_FEED) {
-               $res['verb'] = ACTIVITY_POST;
-               $res['object-type'] = ACTIVITY_OBJ_NOTE;
-       }
-
-       $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
-
-       // select between supported verbs
-
-       if($rawverb) {
-               $res['verb'] = unxmlify($rawverb[0]['data']);
-       }
-
-       // translate OStatus unfollow to activity streams if it happened to get selected
-
-       if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
-               $res['verb'] = ACTIVITY_UNFOLLOW;
-
-       $cats = $item->get_categories();
-       if($cats) {
-               $tag_arr = array();
-               foreach($cats as $cat) {
-                       $term = $cat->get_term();
-                       if(! $term)
-                               $term = $cat->get_label();
-                       $scheme = $cat->get_scheme();
-                       if($scheme && $term && stristr($scheme,'X-DFRN:'))
-                               $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]';
-                       elseif($term)
-                               $tag_arr[] = notags(trim($term));
-               }
-               $res['tag'] =  implode(',', $tag_arr);
-       }
-
-       $attach = $item->get_enclosures();
-       if($attach) {
-               $att_arr = array();
-               foreach($attach as $att) {
-                       $len   = intval($att->get_length());
-                       $link  = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
-                       $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
-                       $type  = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
-                       if(strpos($type,';'))
-                               $type = substr($type,0,strpos($type,';'));
-                       if((! $link) || (strpos($link,'http') !== 0))
-                               continue;
-
-                       if(! $title)
-                               $title = ' ';
-                       if(! $type)
-                               $type = 'application/octet-stream';
-
-                       $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]';
-               }
-               $res['attach'] = implode(',', $att_arr);
-       }
-
-       $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
-
-       if($rawobj) {
-               $res['object'] = '<object>' . "\n";
-               $child = $rawobj[0]['child'];
-               if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
-                       $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
-                       $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
-               }
-               if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
-                       $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
-               if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
-                       $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
-               if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
-                       $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
-               if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
-                       $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
-                       if(! $body)
-                               $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
-                       // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
-                       $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
-                       if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
-
-                               $body = html2bb_video($body);
-
-                               $config = HTMLPurifier_Config::createDefault();
-                               $config->set('Cache.DefinitionImpl', null);
-
-                               $purifier = new HTMLPurifier($config);
-                               $body = $purifier->purify($body);
-                               $body = html2bbcode($body);
-                       }
-
-                       $res['object'] .= '<content>' . $body . '</content>' . "\n";
-               }
-
-               $res['object'] .= '</object>' . "\n";
-       }
-
-       $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
-
-       if($rawobj) {
-               $res['target'] = '<target>' . "\n";
-               $child = $rawobj[0]['child'];
-               if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
-                       $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n";
-               }
-               if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
-                       $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n";
-               if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
-                       $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n";
-               if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
-                       $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n";
-               if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
-                       $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
-                       if(! $body)
-                               $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
-                       // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
-                       $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n";
-                       if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
-
-                               $body = html2bb_video($body);
-
-                               $config = HTMLPurifier_Config::createDefault();
-                               $config->set('Cache.DefinitionImpl', null);
-
-                               $purifier = new HTMLPurifier($config);
-                               $body = $purifier->purify($body);
-                               $body = html2bbcode($body);
-                       }
-
-                       $res['target'] .= '<content>' . $body . '</content>' . "\n";
-               }
-
-               $res['target'] .= '</target>' . "\n";
-       }
-
-       // This is some experimental stuff. By now retweets are shown with "RT:"
-       // But: There is data so that the message could be shown similar to native retweets
-       // There is some better way to parse this array - but it didn't worked for me.
-       $child = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://activitystrea.ms/spec/1.0/"][object][0]["child"];
-       if (is_array($child)) {
-               logger('get_atom_elements: Looking for status.net repeated message');
-
-               $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"];
-               $orig_id = ostatus_convert_href($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"]);
-               $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10];
-               $uri = $author["uri"][0]["data"];
-               $name = $author["name"][0]["data"];
-               $avatar = @array_shift($author["link"][2]["attribs"]);
-               $avatar = $avatar["href"];
-
-               if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) {
-                       logger('get_atom_elements: fixing sender of repeated message. '.$orig_id, LOGGER_DEBUG);
-
-                       if (!intval(get_config('system','wall-to-wall_share'))) {
-                               $prefix = share_header($name, $uri, $avatar, "", "", $orig_link);
-
-                               $res["body"] = $prefix.html2bbcode($message)."[/share]";
-                       } else {
-                               $res["owner-name"] = $res["author-name"];
-                               $res["owner-link"] = $res["author-link"];
-                               $res["owner-avatar"] = $res["author-avatar"];
-
-                               $res["author-name"] = $name;
-                               $res["author-link"] = $uri;
-                               $res["author-avatar"] = $avatar;
-
-                               $res["body"] = html2bbcode($message);
-                       }
-               }
-       }
-
-       if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) {
-               $preview = "";
-
-               // Handle enclosures and treat them as preview picture
-               if (isset($attach))
-                       foreach ($attach AS $attachment)
-                               if ($attachment->type == "image/jpeg")
-                                       $preview = $attachment->link;
-
-               $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
-               $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']);
-               $res["title"] = "";
-               $res["object-type"] = ACTIVITY_OBJ_BOOKMARK;
-               unset($res["attach"]);
-       } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS))
-               $res["body"] = add_page_info_to_body($res["body"]);
-       elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) {
-               $res["body"] = add_page_info_to_body($res["body"]);
-       }
-
-       $arr = array('feed' => $feed, 'item' => $item, 'result' => $res);
-
-       call_hooks('parse_atom', $arr);
-
-       return $res;
-}
-
 function add_page_info_data($data) {
        call_hooks('page_info_data', $data);
 
@@ -955,8 +184,9 @@ function add_page_info_data($data) {
                $a = get_app();
                $hashtags = "\n";
                foreach ($data["keywords"] AS $keyword) {
-                       $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
-                                               array("","", "", "", "", ""), $keyword);
+                       /// @todo make a positive list of allowed characters
+                       $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'", "’", "`", "(", ")", "„", "“"),
+                                               array("","", "", "", "", "", "", "", "", "", "", ""), $keyword);
                        $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
                }
        }
@@ -967,12 +197,7 @@ function add_page_info_data($data) {
 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
        require_once("mod/parse_url.php");
 
-       $data = Cache::get("parse_url:".$url);
-       if (is_null($data)){
-               $data = parseurl_getsiteinfo($url, true);
-               Cache::set("parse_url:".$url,serialize($data), CACHE_DAY);
-       } else
-               $data = unserialize($data);
+       $data = parseurl_getsiteinfo_cached($url, true);
 
        if ($photo != "")
                $data["images"][0]["src"] = $photo;
@@ -1066,37 +291,6 @@ function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
        return $body;
 }
 
-function encode_rel_links($links) {
-       $o = '';
-       if(! ((is_array($links)) && (count($links))))
-               return $o;
-       foreach($links as $link) {
-               $o .= '<link ';
-               if($link['attribs']['']['rel'])
-                       $o .= 'rel="' . $link['attribs']['']['rel'] . '" ';
-               if($link['attribs']['']['type'])
-                       $o .= 'type="' . $link['attribs']['']['type'] . '" ';
-               if($link['attribs']['']['href'])
-                       $o .= 'href="' . $link['attribs']['']['href'] . '" ';
-               if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
-                       $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" ';
-               if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
-                       $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" ';
-               $o .= ' />' . "\n" ;
-       }
-       return xmlify($o);
-}
-
-function add_guid($item) {
-       $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"]));
-       if ($r)
-               return;
-
-       q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')",
-               dbesc($item["guid"]), dbesc($item["plink"]),
-               dbesc($item["uri"]), dbesc($item["network"]));
-}
-
 /**
  * Adds a "lang" specification in a "postopts" element of given $arr,
  * if possible and not already present.
@@ -1142,6 +336,30 @@ function item_add_language_opt(&$arr) {
        }
 }
 
+/**
+ * @brief Creates an unique guid out of a given uri
+ *
+ * @param string $uri uri of an item entry
+ * @return string unique guid
+ */
+function uri_to_guid($uri) {
+
+       // Our regular guid routine is using this kind of prefix as well
+       // We have to avoid that different routines could accidentally create the same value
+       $parsed = parse_url($uri);
+       $guid_prefix = hash("crc32", $parsed["host"]);
+
+       // Remove the scheme to make sure that "https" and "http" doesn't make a difference
+       unset($parsed["scheme"]);
+
+       $host_id = implode("/", $parsed);
+
+       // We could use any hash algorithm since it isn't a security issue
+       $host_hash = hash("ripemd128", $host_id);
+
+       return $guid_prefix.$host_hash;
+}
+
 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
 
        // If it is a posting where users should get notifications, then define it as wall posting
@@ -1217,42 +435,19 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa
                }
        }
 
-       // If there is no guid then take the same guid that was taken before for the same uri
-       if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) {
-               logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG);
-               $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1",
-                       dbesc(trim($arr['uri'])), dbesc(trim($arr['network'])));
-
-               if(count($r)) {
-                       $arr['guid'] = $r[0]["guid"];
-                       logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG);
-               }
-       }
-
-       // If there is no guid then take the same guid that was taken before for the same plink
-       if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) {
-               logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG);
-               $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1",
-                       dbesc(trim($arr['plink'])), dbesc(trim($arr['network'])));
-
-               if(count($r)) {
-                       $arr['guid'] = $r[0]["guid"];
-                       logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG);
-
-                       if ($r[0]["uri"] != $arr['uri'])
-                       logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG);
-               }
-       }
-
-       // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
-       // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
-       //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
-       //      $arr['body'] = strip_tags($arr['body']);
+       // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
+       // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
+       //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
+       //      $arr['body'] = strip_tags($arr['body']);
 
        item_add_language_opt($arr);
 
        if ($notify)
                $guid_prefix = "";
+       elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != ""))
+               $arr['guid'] = uri_to_guid($arr['plink']);
+       elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != ""))
+               $arr['guid'] = uri_to_guid($arr['uri']);
        else {
                $parsed = parse_url($arr["author-link"]);
                $guid_prefix = hash("crc32", $parsed["host"]);
@@ -1304,6 +499,16 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa
        $arr['inform']        = ((x($arr,'inform'))        ? trim($arr['inform'])                : '');
        $arr['file']          = ((x($arr,'file'))          ? trim($arr['file'])                  : '');
 
+
+       if (($arr['author-link'] == "") AND ($arr['owner-link'] == "")) {
+               $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5);
+               foreach ($trace AS $func)
+                       $function[] = $func["function"];
+
+               $function = implode(", ", $function);
+               logger("Both author-link and owner-link are empty. Called by: ".$function, LOGGER_DEBUG);
+       }
+
        if ($arr['plink'] == "") {
                $a = get_app();
                $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
@@ -1338,6 +543,38 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa
                logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
        }
 
+       // The contact-id should be set before "item_store" was called - but there seems to be some issues
+       if ($arr["contact-id"] == 0) {
+               // First we are looking for a suitable contact that matches with the author of the post
+               // This is done only for comments (See below explanation at "gcontact-id")
+               if($arr['parent-uri'] != $arr['uri'])
+                       $arr["contact-id"] = get_contact($arr['author-link'], $uid);
+
+               // If not present then maybe the owner was found
+               if ($arr["contact-id"] == 0)
+                       $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
+
+               // Still missing? Then use the "self" contact of the current user
+               if ($arr["contact-id"] == 0) {
+                       $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
+                       if ($r)
+                               $arr["contact-id"] = $r[0]["id"];
+               }
+               logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
+       }
+
+       if ($arr["gcontact-id"] == 0) {
+               // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
+               // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
+               // On comments the author is the better choice.
+               if($arr['parent-uri'] === $arr['uri'])
+                       $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
+                                                                "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
+               else
+                       $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
+                                                                "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
+       }
+
        if ($arr['guid'] != "") {
                // Checking if there is already an item with the same guid
                logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
@@ -1531,9 +768,6 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa
                return 0;
        } elseif(count($r)) {
 
-               // Store the guid and other relevant data
-               add_guid($arr);
-
                $current_post = $r[0]['id'];
                logger('item_store: created item ' . $current_post);
 
@@ -1609,6 +843,14 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa
                );
 
        if($dsprsig) {
+
+               // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
+               // We can check for this condition when we decode and encode the stuff again.
+               if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
+                       $dsprsig->signature = base64_decode($dsprsig->signature);
+                       logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
+               }
+
                q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
                        intval($current_post),
                        dbesc($dsprsig->signed_text),
@@ -1653,67 +895,15 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa
        create_files_from_item($current_post);
 
        // Only check for notifications on start posts
-       if ($arr['parent-uri'] === $arr['uri']) {
+       if ($arr['parent-uri'] === $arr['uri'])
                add_thread($current_post);
-               logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
-
-               // Send a notification for every new post?
-               $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1",
-                       intval($arr['contact-id']),
-                       intval($arr['uid'])
-               );
-               $send_notification = count($r);
-
-               if (!$send_notification) {
-                       $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d",
-                               intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid']));
-
-                       if (count($tags)) {
-                               foreach ($tags AS $tag) {
-                                       $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`",
-                                               normalise_link($tag["url"]), intval($arr['uid']));
-                                       if (count($r))
-                                               $send_notification = true;
-                               }
-                       }
-               }
-
-               if ($send_notification) {
-                       logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
-                       $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
-                               intval($arr['uid']));
-
-                       $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d",
-                               intval($current_post),
-                               intval($arr['uid'])
-                       );
-
-                       $a = get_app();
-
-                       require_once('include/enotify.php');
-                       notification(array(
-                               'type'         => NOTIFY_SHARE,
-                               'notify_flags' => $u[0]['notify-flags'],
-                               'language'     => $u[0]['language'],
-                               'to_name'      => $u[0]['username'],
-                               'to_email'     => $u[0]['email'],
-                               'uid'          => $u[0]['uid'],
-                               'item'         => $item[0],
-                               'link'         => $a->get_baseurl().'/display/'.urlencode($arr['guid']),
-                               'source_name'  => $item[0]['author-name'],
-                               'source_link'  => $item[0]['author-link'],
-                               'source_photo' => $item[0]['author-avatar'],
-                               'verb'         => ACTIVITY_TAG,
-                               'otype'        => 'item',
-                               'parent'       => $arr['parent']
-                       ));
-                       logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG);
-               }
-       } else {
+       else {
                update_thread($parent_id);
                add_shadow_entry($arr);
        }
 
+       check_item_notification($current_post, $uid);
+
        if ($notify)
                proc_run('php', "include/notifier.php", $notify_type, $current_post);
 
@@ -1909,37 +1099,6 @@ function tag_deliver($uid,$item_id) {
                return;
        }
 
-
-       // send a notification
-
-       // use a local photo if we have one
-
-       $r = q("select * from contact where uid = %d and nurl = '%s' limit 1",
-               intval($u[0]['uid']),
-               dbesc(normalise_link($item['author-link']))
-       );
-       $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']);
-
-
-       require_once('include/enotify.php');
-       notification(array(
-               'type'         => NOTIFY_TAGSELF,
-               'notify_flags' => $u[0]['notify-flags'],
-               'language'     => $u[0]['language'],
-               'to_name'      => $u[0]['username'],
-               'to_email'     => $u[0]['email'],
-               'uid'          => $u[0]['uid'],
-               'item'         => $item,
-               'link'         => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])),
-               'source_name'  => $item['author-name'],
-               'source_link'  => $item['author-link'],
-               'source_photo' => $photo,
-               'verb'         => ACTIVITY_TAG,
-               'otype'        => 'item',
-               'parent'       => $item['parent']
-       ));
-
-
        $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
 
        call_hooks('tagged', $arr);
@@ -2036,245 +1195,9 @@ function tgroup_check($uid,$item) {
        if((! $community_page) && (! $prvgroup))
                return false;
 
-
-
        return true;
-
-}
-
-
-
-
-
-
-function dfrn_deliver($owner,$contact,$atom, $dissolve = false) {
-
-       $a = get_app();
-
-       $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']);
-
-       if($contact['duplex'] && $contact['dfrn-id'])
-               $idtosend = '0:' . $orig_id;
-       if($contact['duplex'] && $contact['issued-id'])
-               $idtosend = '1:' . $orig_id;
-
-
-       $rino = get_config('system','rino_encrypt');
-       $rino = intval($rino);
-       // use RINO1 if mcrypt isn't installed and RINO2 was selected
-       if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
-
-       logger("Local rino version: ". $rino, LOGGER_DEBUG);
-
-       $ssl_val = intval(get_config('system','ssl_policy'));
-       $ssl_policy = '';
-
-       switch($ssl_val){
-               case SSL_POLICY_FULL:
-                       $ssl_policy = 'full';
-                       break;
-               case SSL_POLICY_SELFSIGN:
-                       $ssl_policy = 'self';
-                       break;
-               case SSL_POLICY_NONE:
-               default:
-                       $ssl_policy = 'none';
-                       break;
-       }
-
-       $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : '');
-
-       logger('dfrn_deliver: ' . $url);
-
-       $xml = fetch_url($url);
-
-       $curl_stat = $a->get_curl_code();
-       if(! $curl_stat)
-               return(-1); // timed out
-
-       logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
-
-       if(! $xml)
-               return 3;
-
-       if(strpos($xml,'<?xml') === false) {
-               logger('dfrn_deliver: no valid XML returned');
-               logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
-               return 3;
-       }
-
-       $res = parse_xml_string($xml);
-
-       if((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id)))
-               return (($res->status) ? $res->status : 3);
-
-       $postvars     = array();
-       $sent_dfrn_id = hex2bin((string) $res->dfrn_id);
-       $challenge    = hex2bin((string) $res->challenge);
-       $perm         = (($res->perm) ? $res->perm : null);
-       $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0);
-       $rino_remote_version = intval($res->rino);
-       $page         = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0);
-
-       logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG);
-
-       if($owner['page-flags'] == PAGE_PRVGROUP)
-               $page = 2;
-
-       $final_dfrn_id = '';
-
-       if($perm) {
-               if((($perm == 'rw') && (! intval($contact['writable'])))
-               || (($perm == 'r') && (intval($contact['writable'])))) {
-                       q("update contact set writable = %d where id = %d",
-                               intval(($perm == 'rw') ? 1 : 0),
-                               intval($contact['id'])
-                       );
-                       $contact['writable'] = (string) 1 - intval($contact['writable']);
-               }
-       }
-
-       if(($contact['duplex'] && strlen($contact['pubkey']))
-               || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
-               || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
-               openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']);
-               openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']);
-       }
-       else {
-               openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']);
-               openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']);
-       }
-
-       $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.'));
-
-       if(strpos($final_dfrn_id,':') == 1)
-               $final_dfrn_id = substr($final_dfrn_id,2);
-
-       if($final_dfrn_id != $orig_id) {
-               logger('dfrn_deliver: wrong dfrn_id.');
-               // did not decode properly - cannot trust this site
-               return 3;
-       }
-
-       $postvars['dfrn_id']      = $idtosend;
-       $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION;
-       if($dissolve)
-               $postvars['dissolve'] = '1';
-
-
-       if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
-               $postvars['data'] = $atom;
-               $postvars['perm'] = 'rw';
-       }
-       else {
-               $postvars['data'] = str_replace('<dfrn:comment-allow>1','<dfrn:comment-allow>0',$atom);
-               $postvars['perm'] = 'r';
-       }
-
-       $postvars['ssl_policy'] = $ssl_policy;
-
-       if($page)
-               $postvars['page'] = $page;
-
-
-       if($rino>0 && $rino_remote_version>0 && (! $dissolve)) {
-               logger('rino version: '. $rino_remote_version);
-
-               switch($rino_remote_version) {
-                       case 1:
-                               // Deprecated rino version!
-                               $key = substr(random_string(),0,16);
-                               $data = aes_encrypt($postvars['data'],$key);
-                               break;
-                       case 2:
-                               // RINO 2 based on php-encryption
-                               try {
-                                       $key = Crypto::createNewRandomKey();
-                               } catch (CryptoTestFailed $ex) {
-                                       logger('Cannot safely create a key');
-                                       return -1;
-                               } catch (CannotPerformOperation $ex) {
-                                       logger('Cannot safely create a key');
-                                       return -1;
-                               }
-                               try {
-                                       $data = Crypto::encrypt($postvars['data'], $key);
-                               } catch (CryptoTestFailed $ex) {
-                                       logger('Cannot safely perform encryption');
-                                       return -1;
-                               } catch (CannotPerformOperation $ex) {
-                                       logger('Cannot safely perform encryption');
-                                       return -1;
-                               }
-                               break;
-                       default:
-                               logger("rino: invalid requested verision '$rino_remote_version'");
-                               return -1;
-               }
-
-               $postvars['rino'] = $rino_remote_version;
-               $postvars['data'] = bin2hex($data);
-
-               #logger('rino: sent key = ' . $key, LOGGER_DEBUG);
-
-
-               if($dfrn_version >= 2.1) {
-                       if(($contact['duplex'] && strlen($contact['pubkey']))
-                               || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey']))
-                               || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) {
-
-                               openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
-                       }
-                       else {
-                               openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
-                       }
-               }
-               else {
-                       if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) {
-                               openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']);
-                       }
-                       else {
-                               openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']);
-                       }
-               }
-
-               logger('md5 rawkey ' . md5($postvars['key']));
-
-               $postvars['key'] = bin2hex($postvars['key']);
-       }
-
-
-       logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA);
-
-       $xml = post_url($contact['notify'],$postvars);
-
-       logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA);
-
-       $curl_stat = $a->get_curl_code();
-       if((! $curl_stat) || (! strlen($xml)))
-               return(-1); // timed out
-
-       if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after')))
-               return(-1);
-
-       if(strpos($xml,'<?xml') === false) {
-               logger('dfrn_deliver: phase 2: no valid XML returned');
-               logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
-               return 3;
-       }
-
-       if($contact['term-date'] != '0000-00-00 00:00:00') {
-               logger("dfrn_deliver: $url back from the dead - removing mark for death");
-               require_once('include/Contact.php');
-               unmark_for_death($contact);
-       }
-
-       $res = parse_xml_string($xml);
-
-       return $res->status;
 }
 
-
 /*
   This function returns true if $update has an edited timestamp newer
   than $existing, i.e. $update contains new data which should override
@@ -2342,1888 +1265,136 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0)
                return;
        }
 
-       require_once('library/simplepie/simplepie.inc');
-       require_once('include/contact_selectors.php');
-
-       if(! strlen($xml)) {
-               logger('consume_feed: empty input');
-               return;
-       }
-
-       $feed = new SimplePie();
-       $feed->set_raw_data($xml);
-       if($datedir)
-               $feed->enable_order_by_date(true);
-       else
-               $feed->enable_order_by_date(false);
-       $feed->init();
-
-       if($feed->error())
-               logger('consume_feed: Error parsing XML: ' . $feed->error());
-
-       $permalink = $feed->get_permalink();
-
-       // Check at the feed level for updated contact name and/or photo
-
-       $name_updated  = '';
-       $new_name = '';
-       $photo_timestamp = '';
-       $photo_url = '';
-       $birthday = '';
-       $contact_updated = '';
-
-       $hubs = $feed->get_links('hub');
-       logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA);
-
-       if(count($hubs))
-               $hub = implode(',', $hubs);
-
-       $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
-       if(! $rawtags)
-               $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
-       if($rawtags) {
-               $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
-               if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
-                       $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
-                       $new_name = $elems['name'][0]['data'];
-
-                       // Manually checking for changed contact names
-                       if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) {
-                               $name_updated = date("c");
-                               $photo_timestamp = date("c");
-                       }
-               }
-               if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
-                       if ($photo_timestamp == "")
-                               $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
-                       $photo_url = $elems['link'][0]['attribs']['']['href'];
-               }
-
-               if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) {
-                       $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']);
+       if ($contact['network'] === NETWORK_DFRN) {
+               logger("Consume DFRN messages", LOGGER_DEBUG);
+
+               $r = q("SELECT  `contact`.*, `contact`.`uid` AS `importer_uid`,
+                                        `contact`.`pubkey` AS `cpubkey`,
+                                        `contact`.`prvkey` AS `cprvkey`,
+                                        `contact`.`thumb` AS `thumb`,
+                                        `contact`.`url` as `url`,
+                                        `contact`.`name` as `senderName`,
+                                        `user`.*
+                        FROM `contact`
+                        LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
+                        WHERE `contact`.`id` = %d AND `user`.`uid` = %d",
+                       dbesc($contact["id"]), dbesc($importer["uid"])
+               );
+               if ($r) {
+                       logger("Now import the DFRN feed");
+                       dfrn::import($xml,$r[0], true);
+                       return;
                }
        }
+}
 
-       if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) {
-               logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']);
+function item_is_remote_self($contact, &$datarray) {
+       $a = get_app();
 
-               $contact_updated = $photo_timestamp;
+       if (!$contact['remote_self'])
+               return false;
 
-               require_once("include/Photo.php");
-               $photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']);
+       // Prevent the forwarding of posts that are forwarded
+       if ($datarray["extid"] == NETWORK_DFRN)
+               return false;
 
-               q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
-                       WHERE `uid` = %d AND `id` = %d AND NOT `self`",
-                       dbesc(datetime_convert()),
-                       dbesc($photos[0]),
-                       dbesc($photos[1]),
-                       dbesc($photos[2]),
-                       intval($contact['uid']),
-                       intval($contact['id'])
-               );
-       }
+       // Prevent to forward already forwarded posts
+       if ($datarray["app"] == $a->get_hostname())
+               return false;
 
-       if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) {
-               if ($name_updated > $contact_updated)
-                       $contact_updated = $name_updated;
+       // Only forward posts
+       if ($datarray["verb"] != ACTIVITY_POST)
+               return false;
 
-               $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
-                       intval($contact['uid']),
-                       intval($contact['id'])
-               );
+       if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
+               return false;
 
-               $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
-                       dbesc(notags(trim($new_name))),
-                       dbesc(datetime_convert()),
-                       intval($contact['uid']),
-                       intval($contact['id']),
-                       dbesc(notags(trim($new_name)))
-               );
+       $datarray2 = $datarray;
+       logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
+       if ($contact['remote_self'] == 2) {
+               $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
+                       intval($contact['uid']));
+               if (count($r)) {
+                       $datarray['contact-id'] = $r[0]["id"];
 
-               // do our best to update the name on content items
+                       $datarray['owner-name'] = $r[0]["name"];
+                       $datarray['owner-link'] = $r[0]["url"];
+                       $datarray['owner-avatar'] = $r[0]["thumb"];
 
-               if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
-                       q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
-                               dbesc(notags(trim($new_name))),
-                               dbesc($r[0]['name']),
-                               dbesc($r[0]['url']),
-                               intval($contact['uid']),
-                               dbesc(notags(trim($new_name)))
-                       );
+                       $datarray['author-name']   = $datarray['owner-name'];
+                       $datarray['author-link']   = $datarray['owner-link'];
+                       $datarray['author-avatar'] = $datarray['owner-avatar'];
                }
-       }
 
-       if ($contact_updated AND $new_name AND $photo_url)
-               poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']);
+               if ($contact['network'] != NETWORK_FEED) {
+                       $datarray["guid"] = get_guid(32);
+                       unset($datarray["plink"]);
+                       $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
+                       $datarray["parent-uri"] = $datarray["uri"];
+                       $datarray["extid"] = $contact['network'];
+                       $urlpart = parse_url($datarray2['author-link']);
+                       $datarray["app"] = $urlpart["host"];
+               } else
+                       $datarray['private'] = 0;
+       }
 
-       if(strlen($birthday)) {
-               if(substr($birthday,0,4) != $contact['bdyear']) {
-                       logger('consume_feed: updating birthday: ' . $birthday);
+       if ($contact['network'] != NETWORK_FEED) {
+               // Store the original post
+               $r = item_store($datarray2, false, false);
+               logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
+       } else
+               $datarray["app"] = "Feed";
 
-                       /**
-                        *
-                        * Add new birthday event for this person
-                        *
-                        * $bdtext is just a readable placeholder in case the event is shared
-                        * with others. We will replace it during presentation to our $importer
-                        * to contain a sparkle link and perhaps a photo.
-                        *
-                        */
+       return true;
+}
 
-                       $bdtext = sprintf( t('%s\'s birthday'), $contact['name']);
-                       $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ;
+function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
+       $url = notags(trim($datarray['author-link']));
+       $name = notags(trim($datarray['author-name']));
+       $photo = notags(trim($datarray['author-avatar']));
 
+       if (is_object($item)) {
+               $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
+               if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
+                       $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
+       } else
+               $nick = $item;
 
-                       $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
-                               VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ",
-                               intval($contact['uid']),
+       if(is_array($contact)) {
+               if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
+                       || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
+                       $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
+                               intval(CONTACT_IS_FRIEND),
                                intval($contact['id']),
-                               dbesc(datetime_convert()),
-                               dbesc(datetime_convert()),
-                               dbesc(datetime_convert('UTC','UTC', $birthday)),
-                               dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')),
-                               dbesc($bdtext),
-                               dbesc($bdtext2),
-                               dbesc('birthday')
+                               intval($importer['uid'])
                        );
+               }
+               // send email notification to owner?
+       } else {
 
+               // create contact record
 
-                       // update bdyear
+               $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
+                       `blocked`, `readonly`, `pending`, `writable`)
+                       VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
+                       intval($importer['uid']),
+                       dbesc(datetime_convert()),
+                       dbesc($url),
+                       dbesc(normalise_link($url)),
+                       dbesc($name),
+                       dbesc($nick),
+                       dbesc($photo),
+                       dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
+                       intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
+               );
+               $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
+                               intval($importer['uid']),
+                               dbesc($url)
+               );
+               if(count($r)) {
+                               $contact_record = $r[0];
 
-                       q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d",
-                               dbesc(substr($birthday,0,4)),
-                               intval($contact['uid']),
-                               intval($contact['id'])
-                       );
-
-                       // This function is called twice without reloading the contact
-                       // Make sure we only create one event. This is why &$contact
-                       // is a reference var in this function
-
-                       $contact['bdyear'] = substr($birthday,0,4);
-               }
-       }
-
-       $community_page = 0;
-       $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
-       if($rawtags) {
-               $community_page = intval($rawtags[0]['data']);
-       }
-       if(is_array($contact) && intval($contact['forum']) != $community_page) {
-               q("update contact set forum = %d where id = %d",
-                       intval($community_page),
-                       intval($contact['id'])
-               );
-               $contact['forum'] = (string) $community_page;
-       }
-
-
-       // process any deleted entries
-
-       $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
-       if(is_array($del_entries) && count($del_entries) && $pass != 2) {
-               foreach($del_entries as $dentry) {
-                       $deleted = false;
-                       if(isset($dentry['attribs']['']['ref'])) {
-                               $uri = $dentry['attribs']['']['ref'];
-                               $deleted = true;
-                               if(isset($dentry['attribs']['']['when'])) {
-                                       $when = $dentry['attribs']['']['when'];
-                                       $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
-                               }
-                               else
-                                       $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
-                       }
-                       if($deleted && is_array($contact)) {
-                               $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id`
-                                       WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
-                                       dbesc($uri),
-                                       intval($importer['uid']),
-                                       intval($contact['id'])
-                               );
-                               if(count($r)) {
-                                       $item = $r[0];
-
-                                       if(! $item['deleted'])
-                                               logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
-
-                                       if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
-                                               logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
-                                               event_delete($item['event-id']);
-                                       }
-
-                                       if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
-                                               $xo = parse_xml_string($item['object'],false);
-                                               $xt = parse_xml_string($item['target'],false);
-                                               if($xt->type === ACTIVITY_OBJ_NOTE) {
-                                                       $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
-                                                               dbesc($xt->id),
-                                                               intval($importer['importer_uid'])
-                                                       );
-                                                       if(count($i)) {
-
-                                                               // For tags, the owner cannot remove the tag on the author's copy of the post.
-
-                                                               $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
-                                                               $author_remove = (($item['origin'] && $item['self']) ? true : false);
-                                                               $author_copy = (($item['origin']) ? true : false);
-
-                                                               if($owner_remove && $author_copy)
-                                                                       continue;
-                                                               if($author_remove || $owner_remove) {
-                                                                       $tags = explode(',',$i[0]['tag']);
-                                                                       $newtags = array();
-                                                                       if(count($tags)) {
-                                                                               foreach($tags as $tag)
-                                                                                       if(trim($tag) !== trim($xo->body))
-                                                                                               $newtags[] = trim($tag);
-                                                                       }
-                                                                       q("update item set tag = '%s' where id = %d",
-                                                                               dbesc(implode(',',$newtags)),
-                                                                               intval($i[0]['id'])
-                                                                       );
-                                                                       create_tags_from_item($i[0]['id']);
-                                                               }
-                                                       }
-                                               }
-                                       }
-
-                                       if($item['uri'] == $item['parent-uri']) {
-                                               $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
-                                                       `body` = '', `title` = ''
-                                                       WHERE `parent-uri` = '%s' AND `uid` = %d",
-                                                       dbesc($when),
-                                                       dbesc(datetime_convert()),
-                                                       dbesc($item['uri']),
-                                                       intval($importer['uid'])
-                                               );
-                                               create_tags_from_itemuri($item['uri'], $importer['uid']);
-                                               create_files_from_itemuri($item['uri'], $importer['uid']);
-                                               update_thread_uri($item['uri'], $importer['uid']);
-                                       }
-                                       else {
-                                               $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
-                                                       `body` = '', `title` = ''
-                                                       WHERE `uri` = '%s' AND `uid` = %d",
-                                                       dbesc($when),
-                                                       dbesc(datetime_convert()),
-                                                       dbesc($uri),
-                                                       intval($importer['uid'])
-                                               );
-                                               create_tags_from_itemuri($uri, $importer['uid']);
-                                               create_files_from_itemuri($uri, $importer['uid']);
-                                               if($item['last-child']) {
-                                                       // ensure that last-child is set in case the comment that had it just got wiped.
-                                                       q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
-                                                               dbesc(datetime_convert()),
-                                                               dbesc($item['parent-uri']),
-                                                               intval($item['uid'])
-                                                       );
-                                                       // who is the last child now?
-                                                       $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d
-                                                               ORDER BY `created` DESC LIMIT 1",
-                                                                       dbesc($item['parent-uri']),
-                                                                       intval($importer['uid'])
-                                                       );
-                                                       if(count($r)) {
-                                                               q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
-                                                                       intval($r[0]['id'])
-                                                               );
-                                                       }
-                                               }
-                                       }
-                               }
-                       }
-               }
-       }
-
-       // Now process the feed
-
-       if($feed->get_item_quantity()) {
-
-               logger('consume_feed: feed item count = ' . $feed->get_item_quantity());
-
-       // in inverse date order
-               if ($datedir)
-                       $items = array_reverse($feed->get_items());
-               else
-                       $items = $feed->get_items();
-
-
-               foreach($items as $item) {
-
-                       $is_reply = false;
-                       $item_id = $item->get_id();
-                       $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
-                       if(isset($rawthread[0]['attribs']['']['ref'])) {
-                               $is_reply = true;
-                               $parent_uri = $rawthread[0]['attribs']['']['ref'];
-                       }
-
-                       if(($is_reply) && is_array($contact)) {
-
-                               if($pass == 1)
-                                       continue;
-
-                               // not allowed to post
-
-                               if($contact['rel'] == CONTACT_IS_FOLLOWER)
-                                       continue;
-
-
-                               // Have we seen it? If not, import it.
-
-                               $item_id  = $item->get_id();
-                               $datarray = get_atom_elements($feed, $item, $contact);
-
-                               if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
-                                       $datarray['author-name'] = $contact['name'];
-                               if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
-                                       $datarray['author-link'] = $contact['url'];
-                               if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
-                                       $datarray['author-avatar'] = $contact['thumb'];
-
-                               if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
-                                       logger('consume_feed: no author information! ' . print_r($datarray,true));
-                                       continue;
-                               }
-
-                               $force_parent = false;
-                               if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
-                                       if($contact['network'] === NETWORK_OSTATUS)
-                                               $force_parent = true;
-                                       if(strlen($datarray['title']))
-                                               unset($datarray['title']);
-                                       $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
-                                               dbesc(datetime_convert()),
-                                               dbesc($parent_uri),
-                                               intval($importer['uid'])
-                                       );
-                                       $datarray['last-child'] = 1;
-                                       update_thread_uri($parent_uri, $importer['uid']);
-                               }
-
-
-                               $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
-                                       dbesc($item_id),
-                                       intval($importer['uid'])
-                               );
-
-                               // Update content if 'updated' changes
-
-                               if(count($r)) {
-                                       if (edited_timestamp_is_newer($r[0], $datarray)) {
-
-                                               // do not accept (ignore) an earlier edit than one we currently have.
-                                               if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
-                                                       continue;
-
-                                               $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
-                                                       dbesc($datarray['title']),
-                                                       dbesc($datarray['body']),
-                                                       dbesc($datarray['tag']),
-                                                       dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
-                                                       dbesc(datetime_convert()),
-                                                       dbesc($item_id),
-                                                       intval($importer['uid'])
-                                               );
-                                               create_tags_from_itemuri($item_id, $importer['uid']);
-                                               update_thread_uri($item_id, $importer['uid']);
-                                       }
-
-                                       // update last-child if it changes
-
-                                       $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
-                                       if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
-                                               $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
-                                                       dbesc(datetime_convert()),
-                                                       dbesc($parent_uri),
-                                                       intval($importer['uid'])
-                                               );
-                                               $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s'  WHERE `uri` = '%s' AND `uid` = %d",
-                                                       intval($allow[0]['data']),
-                                                       dbesc(datetime_convert()),
-                                                       dbesc($item_id),
-                                                       intval($importer['uid'])
-                                               );
-                                               update_thread_uri($item_id, $importer['uid']);
-                                       }
-                                       continue;
-                               }
-
-
-                               if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
-                                       // one way feed - no remote comment ability
-                                       $datarray['last-child'] = 0;
-                               }
-                               $datarray['parent-uri'] = $parent_uri;
-                               $datarray['uid'] = $importer['uid'];
-                               $datarray['contact-id'] = $contact['id'];
-                               if(($datarray['verb'] === ACTIVITY_LIKE)
-                                       || ($datarray['verb'] === ACTIVITY_DISLIKE)
-                                       || ($datarray['verb'] === ACTIVITY_ATTEND)
-                                       || ($datarray['verb'] === ACTIVITY_ATTENDNO)
-                                       || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
-                                       $datarray['type'] = 'activity';
-                                       $datarray['gravity'] = GRAVITY_LIKE;
-                                       // only one like or dislike per person
-                                       // splitted into two queries for performance issues
-                                       $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
-                                               intval($datarray['uid']),
-                                               dbesc($datarray['author-link']),
-                                               dbesc($datarray['verb']),
-                                               dbesc($datarray['parent-uri'])
-                                       );
-                                       if($r && count($r))
-                                               continue;
-
-                                       $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
-                                               intval($datarray['uid']),
-                                               dbesc($datarray['author-link']),
-                                               dbesc($datarray['verb']),
-                                               dbesc($datarray['parent-uri'])
-                                       );
-                                       if($r && count($r))
-                                               continue;
-                               }
-
-                               if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
-                                       $xo = parse_xml_string($datarray['object'],false);
-                                       $xt = parse_xml_string($datarray['target'],false);
-
-                                       if($xt->type == ACTIVITY_OBJ_NOTE) {
-                                               $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
-                                                       dbesc($xt->id),
-                                                       intval($importer['importer_uid'])
-                                               );
-                                               if(! count($r))
-                                                       continue;
-
-                                               // extract tag, if not duplicate, add to parent item
-                                               if($xo->id && $xo->content) {
-                                                       $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
-                                                       if(! (stristr($r[0]['tag'],$newtag))) {
-                                                               q("UPDATE item SET tag = '%s' WHERE id = %d",
-                                                                       dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag),
-                                                                       intval($r[0]['id'])
-                                                               );
-                                                               create_tags_from_item($r[0]['id']);
-                                                       }
-                                               }
-                                       }
-                               }
-
-                               $r = item_store($datarray,$force_parent);
-                               continue;
-                       }
-
-                       else {
-
-                               // Head post of a conversation. Have we seen it? If not, import it.
-
-                               $item_id  = $item->get_id();
-
-                               $datarray = get_atom_elements($feed, $item, $contact);
-
-                               if(is_array($contact)) {
-                                       if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN))
-                                               $datarray['author-name'] = $contact['name'];
-                                       if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN))
-                                               $datarray['author-link'] = $contact['url'];
-                                       if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN))
-                                               $datarray['author-avatar'] = $contact['thumb'];
-                               }
-
-                               if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) {
-                                       logger('consume_feed: no author information! ' . print_r($datarray,true));
-                                       continue;
-                               }
-
-                               // special handling for events
-
-                               if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
-                                       $ev = bbtoevent($datarray['body']);
-                                       if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
-                                               $ev['uid'] = $importer['uid'];
-                                               $ev['uri'] = $item_id;
-                                               $ev['edited'] = $datarray['edited'];
-                                               $ev['private'] = $datarray['private'];
-                                               $ev['guid'] = $datarray['guid'];
-
-                                               if(is_array($contact))
-                                                       $ev['cid'] = $contact['id'];
-                                               $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
-                                                       dbesc($item_id),
-                                                       intval($importer['uid'])
-                                               );
-                                               if(count($r))
-                                                       $ev['id'] = $r[0]['id'];
-                                               $xyz = event_store($ev);
-                                               continue;
-                                       }
-                               }
-
-                               if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) {
-                                       if(strlen($datarray['title']))
-                                               unset($datarray['title']);
-                                       $datarray['last-child'] = 1;
-                               }
-
-
-                               $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
-                                       dbesc($item_id),
-                                       intval($importer['uid'])
-                               );
-
-                               // Update content if 'updated' changes
-
-                               if(count($r)) {
-                                       if (edited_timestamp_is_newer($r[0], $datarray)) {
-
-                                               // do not accept (ignore) an earlier edit than one we currently have.
-                                               if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
-                                                       continue;
-
-                                               $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
-                                                       dbesc($datarray['title']),
-                                                       dbesc($datarray['body']),
-                                                       dbesc($datarray['tag']),
-                                                       dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
-                                                       dbesc(datetime_convert()),
-                                                       dbesc($item_id),
-                                                       intval($importer['uid'])
-                                               );
-                                               create_tags_from_itemuri($item_id, $importer['uid']);
-                                               update_thread_uri($item_id, $importer['uid']);
-                                       }
-
-                                       // update last-child if it changes
-
-                                       $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
-                                       if($allow && $allow[0]['data'] != $r[0]['last-child']) {
-                                               $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
-                                                       intval($allow[0]['data']),
-                                                       dbesc(datetime_convert()),
-                                                       dbesc($item_id),
-                                                       intval($importer['uid'])
-                                               );
-                                               update_thread_uri($item_id, $importer['uid']);
-                                       }
-                                       continue;
-                               }
-
-                               if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) {
-                                       logger('consume-feed: New follower');
-                                       new_follower($importer,$contact,$datarray,$item);
-                                       return;
-                               }
-                               if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW))  {
-                                       lose_follower($importer,$contact,$datarray,$item);
-                                       return;
-                               }
-
-                               if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) {
-                                       logger('consume-feed: New friend request');
-                                       new_follower($importer,$contact,$datarray,$item,true);
-                                       return;
-                               }
-                               if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND))  {
-                                       lose_sharer($importer,$contact,$datarray,$item);
-                                       return;
-                               }
-
-
-                               if(! is_array($contact))
-                                       return;
-
-
-                               if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) {
-                                               // one way feed - no remote comment ability
-                                               $datarray['last-child'] = 0;
-                               }
-                               if($contact['network'] === NETWORK_FEED)
-                                       $datarray['private'] = 2;
-
-                               $datarray['parent-uri'] = $item_id;
-                               $datarray['uid'] = $importer['uid'];
-                               $datarray['contact-id'] = $contact['id'];
-
-                               if(! link_compare($datarray['owner-link'],$contact['url'])) {
-                                       // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
-                                       // but otherwise there's a possible data mixup on the sender's system.
-                                       // the tgroup delivery code called from item_store will correct it if it's a forum,
-                                       // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
-                                       logger('consume_feed: Correcting item owner.', LOGGER_DEBUG);
-                                       $datarray['owner-name']   = $contact['name'];
-                                       $datarray['owner-link']   = $contact['url'];
-                                       $datarray['owner-avatar'] = $contact['thumb'];
-                               }
-
-                               // We've allowed "followers" to reach this point so we can decide if they are
-                               // posting an @-tag delivery, which followers are allowed to do for certain
-                               // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it.
-
-                               if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray)))
-                                       continue;
-
-                               // This is my contact on another system, but it's really me.
-                               // Turn this into a wall post.
-                               $notify = item_is_remote_self($contact, $datarray);
-
-                               $r = item_store($datarray, false, $notify);
-                               logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG);
-                               continue;
-
-                       }
-               }
-       }
-}
-
-function item_is_remote_self($contact, &$datarray) {
-       $a = get_app();
-
-       if (!$contact['remote_self'])
-               return false;
-
-       // Prevent the forwarding of posts that are forwarded
-       if ($datarray["extid"] == NETWORK_DFRN)
-               return false;
-
-       // Prevent to forward already forwarded posts
-       if ($datarray["app"] == $a->get_hostname())
-               return false;
-
-       // Only forward posts
-       if ($datarray["verb"] != ACTIVITY_POST)
-               return false;
-
-       if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
-               return false;
-
-       $datarray2 = $datarray;
-       logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
-       if ($contact['remote_self'] == 2) {
-               $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
-                       intval($contact['uid']));
-               if (count($r)) {
-                       $datarray['contact-id'] = $r[0]["id"];
-
-                       $datarray['owner-name'] = $r[0]["name"];
-                       $datarray['owner-link'] = $r[0]["url"];
-                       $datarray['owner-avatar'] = $r[0]["thumb"];
-
-                       $datarray['author-name']   = $datarray['owner-name'];
-                       $datarray['author-link']   = $datarray['owner-link'];
-                       $datarray['author-avatar'] = $datarray['owner-avatar'];
-               }
-
-               if ($contact['network'] != NETWORK_FEED) {
-                       $datarray["guid"] = get_guid(32);
-                       unset($datarray["plink"]);
-                       $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
-                       $datarray["parent-uri"] = $datarray["uri"];
-                       $datarray["extid"] = $contact['network'];
-                       $urlpart = parse_url($datarray2['author-link']);
-                       $datarray["app"] = $urlpart["host"];
-               } else
-                       $datarray['private'] = 0;
-       }
-
-       if ($contact['network'] != NETWORK_FEED) {
-               // Store the original post
-               $r = item_store($datarray2, false, false);
-               logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
-       } else
-               $datarray["app"] = "Feed";
-
-       return true;
-}
-
-function local_delivery($importer,$data) {
-       $a = get_app();
-
-       logger(__function__, LOGGER_TRACE);
-
-       if($importer['readonly']) {
-               // We aren't receiving stuff from this person. But we will quietly ignore them
-               // rather than a blatant "go away" message.
-               logger('local_delivery: ignoring');
-               return 0;
-               //NOTREACHED
-       }
-
-       // Consume notification feed. This may differ from consuming a public feed in several ways
-       // - might contain email or friend suggestions
-       // - might contain remote followup to our message
-       //              - in which case we need to accept it and then notify other conversants
-       // - we may need to send various email notifications
-
-       $feed = new SimplePie();
-       $feed->set_raw_data($data);
-       $feed->enable_order_by_date(false);
-       $feed->init();
-
-
-       if($feed->error())
-               logger('local_delivery: Error parsing XML: ' . $feed->error());
-
-
-       // Check at the feed level for updated contact name and/or photo
-
-       $name_updated  = '';
-       $new_name = '';
-       $photo_timestamp = '';
-       $photo_url = '';
-       $contact_updated = '';
-
-
-       $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner');
-
-// Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags
-//     if(! $rawtags)
-//             $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
-
-       if($rawtags) {
-               $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
-               if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) {
-                       $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated'];
-                       $new_name = $elems['name'][0]['data'];
-
-                       // Manually checking for changed contact names
-                       if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) {
-                               $name_updated = date("c");
-                               $photo_timestamp = date("c");
-                       }
-               }
-               if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) {
-                       if ($photo_timestamp == "")
-                               $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']);
-                       $photo_url = $elems['link'][0]['attribs']['']['href'];
-               }
-       }
-
-       if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) {
-
-               $contact_updated = $photo_timestamp;
-
-               logger('local_delivery: Updating photo for ' . $importer['name']);
-               require_once("include/Photo.php");
-
-               $photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']);
-
-               q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'
-                       WHERE `uid` = %d AND `id` = %d AND NOT `self`",
-                       dbesc(datetime_convert()),
-                       dbesc($photos[0]),
-                       dbesc($photos[1]),
-                       dbesc($photos[2]),
-                       intval($importer['importer_uid']),
-                       intval($importer['id'])
-               );
-       }
-
-       if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) {
-               if ($name_updated > $contact_updated)
-                       $contact_updated = $name_updated;
-
-               $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1",
-                       intval($importer['importer_uid']),
-                       intval($importer['id'])
-               );
-
-               $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`",
-                       dbesc(notags(trim($new_name))),
-                       dbesc(datetime_convert()),
-                       intval($importer['importer_uid']),
-                       intval($importer['id']),
-                       dbesc(notags(trim($new_name)))
-               );
-
-               // do our best to update the name on content items
-
-               if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) {
-                       q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'",
-                               dbesc(notags(trim($new_name))),
-                               dbesc($r[0]['name']),
-                               dbesc($r[0]['url']),
-                               intval($importer['importer_uid']),
-                               dbesc(notags(trim($new_name)))
-                       );
-               }
-       }
-
-       if ($contact_updated AND $new_name AND $photo_url)
-               poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']);
-
-       // Currently unsupported - needs a lot of work
-       $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' );
-       if(isset($reloc[0]['child'][NAMESPACE_DFRN])) {
-               $base = $reloc[0]['child'][NAMESPACE_DFRN];
-               $newloc = array();
-               $newloc['uid'] = $importer['importer_uid'];
-               $newloc['cid'] = $importer['id'];
-               $newloc['name'] = notags(unxmlify($base['name'][0]['data']));
-               $newloc['photo'] = notags(unxmlify($base['photo'][0]['data']));
-               $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data']));
-               $newloc['micro'] = notags(unxmlify($base['micro'][0]['data']));
-               $newloc['url'] = notags(unxmlify($base['url'][0]['data']));
-               $newloc['request'] = notags(unxmlify($base['request'][0]['data']));
-               $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data']));
-               $newloc['notify'] = notags(unxmlify($base['notify'][0]['data']));
-               $newloc['poll'] = notags(unxmlify($base['poll'][0]['data']));
-               $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data']));
-               /** relocated user must have original key pair */
-               /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data']));
-               $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/
-
-               logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG);
-
-               // update contact
-               $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;",
-                       intval($importer['id']),
-                       intval($importer['importer_uid']));
-               if ($r === false)
-                       return 1;
-               $old = $r[0];
-
-               $x = q("UPDATE contact SET
-                                       name = '%s',
-                                       photo = '%s',
-                                       thumb = '%s',
-                                       micro = '%s',
-                                       url = '%s',
-                                       nurl = '%s',
-                                       request = '%s',
-                                       confirm = '%s',
-                                       notify = '%s',
-                                       poll = '%s',
-                                       `site-pubkey` = '%s'
-                       WHERE id=%d AND uid=%d;",
-                                       dbesc($newloc['name']),
-                                       dbesc($newloc['photo']),
-                                       dbesc($newloc['thumb']),
-                                       dbesc($newloc['micro']),
-                                       dbesc($newloc['url']),
-                                       dbesc(normalise_link($newloc['url'])),
-                                       dbesc($newloc['request']),
-                                       dbesc($newloc['confirm']),
-                                       dbesc($newloc['notify']),
-                                       dbesc($newloc['poll']),
-                                       dbesc($newloc['sitepubkey']),
-                                       intval($importer['id']),
-                                       intval($importer['importer_uid']));
-
-               if ($x === false)
-                       return 1;
-               // update items
-               $fields = array(
-                       'owner-link' => array($old['url'], $newloc['url']),
-                       'author-link' => array($old['url'], $newloc['url']),
-                       'owner-avatar' => array($old['photo'], $newloc['photo']),
-                       'author-avatar' => array($old['photo'], $newloc['photo']),
-                       );
-               foreach ($fields as $n=>$f){
-                       $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d",
-                                       $n, dbesc($f[1]),
-                                       $n, dbesc($f[0]),
-                                       intval($importer['importer_uid']));
-                               if ($x === false)
-                                       return 1;
-                       }
-
-               /// @TODO
-               /// merge with current record, current contents have priority
-               /// update record, set url-updated
-               /// update profile photos
-               /// schedule a scan?
-               return 0;
-       }
-
-
-       // handle friend suggestion notification
-
-       $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' );
-       if(isset($sugg[0]['child'][NAMESPACE_DFRN])) {
-               $base = $sugg[0]['child'][NAMESPACE_DFRN];
-               $fsugg = array();
-               $fsugg['uid'] = $importer['importer_uid'];
-               $fsugg['cid'] = $importer['id'];
-               $fsugg['name'] = notags(unxmlify($base['name'][0]['data']));
-               $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data']));
-               $fsugg['url'] = notags(unxmlify($base['url'][0]['data']));
-               $fsugg['request'] = notags(unxmlify($base['request'][0]['data']));
-               $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data']));
-
-               // Does our member already have a friend matching this description?
-
-               $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
-                       dbesc($fsugg['name']),
-                       dbesc(normalise_link($fsugg['url'])),
-                       intval($fsugg['uid'])
-               );
-               if(count($r))
-                       return 0;
-
-               // Do we already have an fcontact record for this person?
-
-               $fid = 0;
-               $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
-                       dbesc($fsugg['url']),
-                       dbesc($fsugg['name']),
-                       dbesc($fsugg['request'])
-               );
-               if(count($r)) {
-                       $fid = $r[0]['id'];
-
-                       // OK, we do. Do we already have an introduction for this person ?
-                       $r = q("select id from intro where uid = %d and fid = %d limit 1",
-                               intval($fsugg['uid']),
-                               intval($fid)
-                       );
-                       if(count($r))
-                               return 0;
-               }
-               if(! $fid)
-                       $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ",
-                       dbesc($fsugg['name']),
-                       dbesc($fsugg['url']),
-                       dbesc($fsugg['photo']),
-                       dbesc($fsugg['request'])
-               );
-               $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1",
-                       dbesc($fsugg['url']),
-                       dbesc($fsugg['name']),
-                       dbesc($fsugg['request'])
-               );
-               if(count($r)) {
-                       $fid = $r[0]['id'];
-               }
-               // database record did not get created. Quietly give up.
-               else
-                       return 0;
-
-
-               $hash = random_string();
-
-               $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )
-                       VALUES( %d, %d, %d, '%s', '%s', '%s', %d )",
-                       intval($fsugg['uid']),
-                       intval($fid),
-                       intval($fsugg['cid']),
-                       dbesc($fsugg['body']),
-                       dbesc($hash),
-                       dbesc(datetime_convert()),
-                       intval(0)
-               );
-
-               notification(array(
-                       'type'         => NOTIFY_SUGGEST,
-                       'notify_flags' => $importer['notify-flags'],
-                       'language'     => $importer['language'],
-                       'to_name'      => $importer['username'],
-                       'to_email'     => $importer['email'],
-                       'uid'          => $importer['importer_uid'],
-                       'item'         => $fsugg,
-                       'link'         => $a->get_baseurl() . '/notifications/intros',
-                       'source_name'  => $importer['name'],
-                       'source_link'  => $importer['url'],
-                       'source_photo' => $importer['photo'],
-                       'verb'         => ACTIVITY_REQ_FRIEND,
-                       'otype'        => 'intro'
-               ));
-
-               return 0;
-       }
-
-       $ismail = false;
-
-       $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' );
-       if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) {
-
-               logger('local_delivery: private message received');
-
-               $ismail = true;
-               $base = $rawmail[0]['child'][NAMESPACE_DFRN];
-
-               $msg = array();
-               $msg['uid'] = $importer['importer_uid'];
-               $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data']));
-               $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data']));
-               $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data']));
-               $msg['contact-id'] = $importer['id'];
-               $msg['title'] = notags(unxmlify($base['subject'][0]['data']));
-               $msg['body'] = escape_tags(unxmlify($base['content'][0]['data']));
-               $msg['seen'] = 0;
-               $msg['replied'] = 0;
-               $msg['uri'] = notags(unxmlify($base['id'][0]['data']));
-               $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data']));
-               $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data'])));
-
-               dbesc_array($msg);
-
-               $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg))
-                       . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" );
-
-               // send notifications.
-
-               require_once('include/enotify.php');
-
-               $notif_params = array(
-                       'type' => NOTIFY_MAIL,
-                       'notify_flags' => $importer['notify-flags'],
-                       'language' => $importer['language'],
-                       'to_name' => $importer['username'],
-                       'to_email' => $importer['email'],
-                       'uid' => $importer['importer_uid'],
-                       'item' => $msg,
-                       'source_name' => $msg['from-name'],
-                       'source_link' => $importer['url'],
-                       'source_photo' => $importer['thumb'],
-                       'verb' => ACTIVITY_POST,
-                       'otype' => 'mail'
-               );
-
-               notification($notif_params);
-               return 0;
-
-               // NOTREACHED
-       }
-
-       $community_page = 0;
-       $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community');
-       if($rawtags) {
-               $community_page = intval($rawtags[0]['data']);
-       }
-       if(intval($importer['forum']) != $community_page) {
-               q("update contact set forum = %d where id = %d",
-                       intval($community_page),
-                       intval($importer['id'])
-               );
-               $importer['forum'] = (string) $community_page;
-       }
-
-       logger('local_delivery: feed item count = ' . $feed->get_item_quantity());
-
-       // process any deleted entries
-
-       $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
-       if(is_array($del_entries) && count($del_entries)) {
-               foreach($del_entries as $dentry) {
-                       $deleted = false;
-                       if(isset($dentry['attribs']['']['ref'])) {
-                               $uri = $dentry['attribs']['']['ref'];
-                               $deleted = true;
-                               if(isset($dentry['attribs']['']['when'])) {
-                                       $when = $dentry['attribs']['']['when'];
-                                       $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
-                               }
-                               else
-                                       $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
-                       }
-                       if($deleted) {
-
-                               // check for relayed deletes to our conversation
-
-                               $is_reply = false;
-                               $r = q("select * from item where uri = '%s' and uid = %d limit 1",
-                                       dbesc($uri),
-                                       intval($importer['importer_uid'])
-                               );
-                               if(count($r)) {
-                                       $parent_uri = $r[0]['parent-uri'];
-                                       if($r[0]['id'] != $r[0]['parent'])
-                                               $is_reply = true;
-                               }
-
-                               if($is_reply) {
-                                       $community = false;
-
-                                       if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
-                                               $sql_extra = '';
-                                               $community = true;
-                                               logger('local_delivery: possible community delete');
-                                       }
-                                       else
-                                               $sql_extra = " and contact.self = 1 and item.wall = 1 ";
-
-                                       // was the top-level post for this reply written by somebody on this site?
-                                       // Specifically, the recipient?
-
-                                       $is_a_remote_delete = false;
-
-                                       // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
-                                       $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
-                                               `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
-                                               INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
-                                               WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
-                                               AND `item`.`uid` = %d
-                                               $sql_extra
-                                               LIMIT 1",
-                                               dbesc($parent_uri),
-                                               dbesc($parent_uri),
-                                               dbesc($parent_uri),
-                                               intval($importer['importer_uid'])
-                                       );
-                                       if($r && count($r))
-                                               $is_a_remote_delete = true;
-
-                                       // Does this have the characteristics of a community or private group comment?
-                                       // If it's a reply to a wall post on a community/prvgroup page it's a
-                                       // valid community comment. Also forum_mode makes it valid for sure.
-                                       // If neither, it's not.
-
-                                       if($is_a_remote_delete && $community) {
-                                               if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
-                                                       $is_a_remote_delete = false;
-                                                       logger('local_delivery: not a community delete');
-                                               }
-                                       }
-
-                                       if($is_a_remote_delete) {
-                                               logger('local_delivery: received remote delete');
-                                       }
-                               }
-
-                               $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`
-                                       WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1",
-                                       dbesc($uri),
-                                       intval($importer['importer_uid']),
-                                       intval($importer['id'])
-                               );
-
-                               if(count($r)) {
-                                       $item = $r[0];
-
-                                       if($item['deleted'])
-                                               continue;
-
-                                       logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG);
-
-                                       if($item['object-type'] === ACTIVITY_OBJ_EVENT) {
-                                               logger("Deleting event ".$item['event-id'], LOGGER_DEBUG);
-                                               event_delete($item['event-id']);
-                                       }
-
-                                       if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
-                                               $xo = parse_xml_string($item['object'],false);
-                                               $xt = parse_xml_string($item['target'],false);
-
-                                               if($xt->type === ACTIVITY_OBJ_NOTE) {
-                                                       $i = q("select * from `item` where uri = '%s' and uid = %d limit 1",
-                                                               dbesc($xt->id),
-                                                               intval($importer['importer_uid'])
-                                                       );
-                                                       if(count($i)) {
-
-                                                               // For tags, the owner cannot remove the tag on the author's copy of the post.
-
-                                                               $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false);
-                                                               $author_remove = (($item['origin'] && $item['self']) ? true : false);
-                                                               $author_copy = (($item['origin']) ? true : false);
-
-                                                               if($owner_remove && $author_copy)
-                                                                       continue;
-                                                               if($author_remove || $owner_remove) {
-                                                                       $tags = explode(',',$i[0]['tag']);
-                                                                       $newtags = array();
-                                                                       if(count($tags)) {
-                                                                               foreach($tags as $tag)
-                                                                                       if(trim($tag) !== trim($xo->body))
-                                                                                               $newtags[] = trim($tag);
-                                                                       }
-                                                                       q("update item set tag = '%s' where id = %d",
-                                                                               dbesc(implode(',',$newtags)),
-                                                                               intval($i[0]['id'])
-                                                                       );
-                                                                       create_tags_from_item($i[0]['id']);
-                                                               }
-                                                       }
-                                               }
-                                       }
-
-                                       if($item['uri'] == $item['parent-uri']) {
-                                               $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
-                                                       `body` = '', `title` = ''
-                                                       WHERE `parent-uri` = '%s' AND `uid` = %d",
-                                                       dbesc($when),
-                                                       dbesc(datetime_convert()),
-                                                       dbesc($item['uri']),
-                                                       intval($importer['importer_uid'])
-                                               );
-                                               create_tags_from_itemuri($item['uri'], $importer['importer_uid']);
-                                               create_files_from_itemuri($item['uri'], $importer['importer_uid']);
-                                               update_thread_uri($item['uri'], $importer['importer_uid']);
-                                       }
-                                       else {
-                                               $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',
-                                                       `body` = '', `title` = ''
-                                                       WHERE `uri` = '%s' AND `uid` = %d",
-                                                       dbesc($when),
-                                                       dbesc(datetime_convert()),
-                                                       dbesc($uri),
-                                                       intval($importer['importer_uid'])
-                                               );
-                                               create_tags_from_itemuri($uri, $importer['importer_uid']);
-                                               create_files_from_itemuri($uri, $importer['importer_uid']);
-                                               update_thread_uri($uri, $importer['importer_uid']);
-                                               if($item['last-child']) {
-                                                       // ensure that last-child is set in case the comment that had it just got wiped.
-                                                       q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
-                                                               dbesc(datetime_convert()),
-                                                               dbesc($item['parent-uri']),
-                                                               intval($item['uid'])
-                                                       );
-                                                       // who is the last child now?
-                                                       $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d
-                                                               ORDER BY `created` DESC LIMIT 1",
-                                                                       dbesc($item['parent-uri']),
-                                                                       intval($importer['importer_uid'])
-                                                       );
-                                                       if(count($r)) {
-                                                               q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
-                                                                       intval($r[0]['id'])
-                                                               );
-                                                       }
-                                               }
-                                               // if this is a relayed delete, propagate it to other recipients
-
-                                               if($is_a_remote_delete)
-                                                       proc_run('php',"include/notifier.php","drop",$item['id']);
-                                       }
-                               }
-                       }
-               }
-       }
-
-
-       foreach($feed->get_items() as $item) {
-
-               $is_reply = false;
-               $item_id = $item->get_id();
-               $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
-               if(isset($rawthread[0]['attribs']['']['ref'])) {
-                       $is_reply = true;
-                       $parent_uri = $rawthread[0]['attribs']['']['ref'];
-               }
-
-               if($is_reply) {
-                       $community = false;
-
-                       if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) {
-                               $sql_extra = '';
-                               $community = true;
-                               logger('local_delivery: possible community reply');
-                       }
-                       else
-                               $sql_extra = " and contact.self = 1 and item.wall = 1 ";
-
-                       // was the top-level post for this reply written by somebody on this site?
-                       // Specifically, the recipient?
-
-                       $is_a_remote_comment = false;
-                       $top_uri = $parent_uri;
-
-                       $r = q("select `item`.`parent-uri` from `item`
-                               WHERE `item`.`uri` = '%s'
-                               LIMIT 1",
-                               dbesc($parent_uri)
-                       );
-                       if($r && count($r)) {
-                               $top_uri = $r[0]['parent-uri'];
-
-                               // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used?
-                               $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,
-                                       `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`
-                                       INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
-                                       WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')
-                                       AND `item`.`uid` = %d
-                                       $sql_extra
-                                       LIMIT 1",
-                                       dbesc($top_uri),
-                                       dbesc($top_uri),
-                                       dbesc($top_uri),
-                                       intval($importer['importer_uid'])
-                               );
-                               if($r && count($r))
-                                       $is_a_remote_comment = true;
-                       }
-
-                       // Does this have the characteristics of a community or private group comment?
-                       // If it's a reply to a wall post on a community/prvgroup page it's a
-                       // valid community comment. Also forum_mode makes it valid for sure.
-                       // If neither, it's not.
-
-                       if($is_a_remote_comment && $community) {
-                               if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) {
-                                       $is_a_remote_comment = false;
-                                       logger('local_delivery: not a community reply');
-                               }
-                       }
-
-                       if($is_a_remote_comment) {
-                               logger('local_delivery: received remote comment');
-                               $is_like = false;
-                               // remote reply to our post. Import and then notify everybody else.
-
-                               $datarray = get_atom_elements($feed, $item);
-
-                               $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body`  FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
-                                       dbesc($item_id),
-                                       intval($importer['importer_uid'])
-                               );
-
-                               // Update content if 'updated' changes
-
-                               if(count($r)) {
-                                       $iid = $r[0]['id'];
-                                       if (edited_timestamp_is_newer($r[0], $datarray)) {
-
-                                               // do not accept (ignore) an earlier edit than one we currently have.
-                                               if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
-                                                       continue;
-
-                                               logger('received updated comment' , LOGGER_DEBUG);
-                                               $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
-                                                       dbesc($datarray['title']),
-                                                       dbesc($datarray['body']),
-                                                       dbesc($datarray['tag']),
-                                                       dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
-                                                       dbesc(datetime_convert()),
-                                                       dbesc($item_id),
-                                                       intval($importer['importer_uid'])
-                                               );
-                                               create_tags_from_itemuri($item_id, $importer['importer_uid']);
-
-                                               proc_run('php',"include/notifier.php","comment-import",$iid);
-
-                                       }
-
-                                       continue;
-                               }
-
-
-
-                               $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1",
-                                       intval($importer['importer_uid'])
-                               );
-
-
-                               $datarray['type'] = 'remote-comment';
-                               $datarray['wall'] = 1;
-                               $datarray['parent-uri'] = $parent_uri;
-                               $datarray['uid'] = $importer['importer_uid'];
-                               $datarray['owner-name'] = $own[0]['name'];
-                               $datarray['owner-link'] = $own[0]['url'];
-                               $datarray['owner-avatar'] = $own[0]['thumb'];
-                               $datarray['contact-id'] = $importer['id'];
-
-                               if(($datarray['verb'] === ACTIVITY_LIKE)
-                                       || ($datarray['verb'] === ACTIVITY_DISLIKE)
-                                       || ($datarray['verb'] === ACTIVITY_ATTEND)
-                                       || ($datarray['verb'] === ACTIVITY_ATTENDNO)
-                                       || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
-                                       $is_like = true;
-                                       $datarray['type'] = 'activity';
-                                       $datarray['gravity'] = GRAVITY_LIKE;
-                                       $datarray['last-child'] = 0;
-                                       // only one like or dislike per person
-                                       // splitted into two queries for performance issues
-                                       $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
-                                               intval($datarray['uid']),
-                                               dbesc($datarray['author-link']),
-                                               dbesc($datarray['verb']),
-                                               dbesc($datarray['parent-uri'])
-                                       );
-                                       if($r && count($r))
-                                               continue;
-
-                                       $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
-                                               intval($datarray['uid']),
-                                               dbesc($datarray['author-link']),
-                                               dbesc($datarray['verb']),
-                                               dbesc($datarray['parent-uri'])
-
-                                       );
-                                       if($r && count($r))
-                                               continue;
-                               }
-
-                               if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
-
-                                       $xo = parse_xml_string($datarray['object'],false);
-                                       $xt = parse_xml_string($datarray['target'],false);
-
-                                       if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) {
-
-                                               // fetch the parent item
-
-                                               $tagp = q("select * from item where uri = '%s' and uid = %d limit 1",
-                                                       dbesc($xt->id),
-                                                       intval($importer['importer_uid'])
-                                               );
-                                               if(! count($tagp))
-                                                       continue;
-
-                                               // extract tag, if not duplicate, and this user allows tags, add to parent item
-
-                                               if($xo->id && $xo->content) {
-                                                       $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
-                                                       if(! (stristr($tagp[0]['tag'],$newtag))) {
-                                                               $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1",
-                                                                       intval($importer['importer_uid'])
-                                                               );
-                                                               if(count($i) && ! intval($i[0]['blocktags'])) {
-                                                                       q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d",
-                                                                               dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag),
-                                                                               intval($tagp[0]['id']),
-                                                                               dbesc(datetime_convert()),
-                                                                               dbesc(datetime_convert())
-                                                                       );
-                                                                       create_tags_from_item($tagp[0]['id']);
-                                                               }
-                                                       }
-                                               }
-                                       }
-                               }
-
-
-                               $posted_id = item_store($datarray);
-                               $parent = 0;
-
-                               if($posted_id) {
-
-                                       $datarray["id"] = $posted_id;
-
-                                       $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
-                                               intval($posted_id),
-                                               intval($importer['importer_uid'])
-                                       );
-                                       if(count($r)) {
-                                               $parent = $r[0]['parent'];
-                                               $parent_uri = $r[0]['parent-uri'];
-                                       }
-
-                                       if(! $is_like) {
-                                               $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d",
-                                                       dbesc(datetime_convert()),
-                                                       intval($importer['importer_uid']),
-                                                       intval($r[0]['parent'])
-                                               );
-
-                                               $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d",
-                                                       dbesc(datetime_convert()),
-                                                       intval($importer['importer_uid']),
-                                                       intval($posted_id)
-                                               );
-                                       }
-
-                                       if($posted_id && $parent) {
-
-                                               proc_run('php',"include/notifier.php","comment-import","$posted_id");
-
-                                               if((! $is_like) && (! $importer['self'])) {
-
-                                                       require_once('include/enotify.php');
-
-                                                       notification(array(
-                                                               'type'         => NOTIFY_COMMENT,
-                                                               'notify_flags' => $importer['notify-flags'],
-                                                               'language'     => $importer['language'],
-                                                               'to_name'      => $importer['username'],
-                                                               'to_email'     => $importer['email'],
-                                                               'uid'          => $importer['importer_uid'],
-                                                               'item'         => $datarray,
-                                                               'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
-                                                               'source_name'  => stripslashes($datarray['author-name']),
-                                                               'source_link'  => $datarray['author-link'],
-                                                               'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
-                                                                       ? $importer['thumb'] : $datarray['author-avatar']),
-                                                               'verb'         => ACTIVITY_POST,
-                                                               'otype'        => 'item',
-                                                               'parent'       => $parent,
-                                                               'parent_uri'   => $parent_uri,
-                                                       ));
-
-                                               }
-                                       }
-
-                                       return 0;
-                                       // NOTREACHED
-                               }
-                       }
-                       else {
-
-                               // regular comment that is part of this total conversation. Have we seen it? If not, import it.
-
-                               $item_id  = $item->get_id();
-                               $datarray = get_atom_elements($feed,$item);
-
-                               if($importer['rel'] == CONTACT_IS_FOLLOWER)
-                                       continue;
-
-                               $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
-                                       dbesc($item_id),
-                                       intval($importer['importer_uid'])
-                               );
-
-                               // Update content if 'updated' changes
-
-                               if(count($r)) {
-                                       if (edited_timestamp_is_newer($r[0], $datarray)) {
-
-                                               // do not accept (ignore) an earlier edit than one we currently have.
-                                               if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
-                                                       continue;
-
-                                               $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
-                                                       dbesc($datarray['title']),
-                                                       dbesc($datarray['body']),
-                                                       dbesc($datarray['tag']),
-                                                       dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
-                                                       dbesc(datetime_convert()),
-                                                       dbesc($item_id),
-                                                       intval($importer['importer_uid'])
-                                               );
-                                               create_tags_from_itemuri($item_id, $importer['importer_uid']);
-                                       }
-
-                                       // update last-child if it changes
-
-                                       $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
-                                       if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) {
-                                               $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d",
-                                                       dbesc(datetime_convert()),
-                                                       dbesc($parent_uri),
-                                                       intval($importer['importer_uid'])
-                                               );
-                                               $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s'  WHERE `uri` = '%s' AND `uid` = %d",
-                                                       intval($allow[0]['data']),
-                                                       dbesc(datetime_convert()),
-                                                       dbesc($item_id),
-                                                       intval($importer['importer_uid'])
-                                               );
-                                       }
-                                       continue;
-                               }
-
-                               $datarray['parent-uri'] = $parent_uri;
-                               $datarray['uid'] = $importer['importer_uid'];
-                               $datarray['contact-id'] = $importer['id'];
-                               if(($datarray['verb'] === ACTIVITY_LIKE)
-                                       || ($datarray['verb'] === ACTIVITY_DISLIKE)
-                                       || ($datarray['verb'] === ACTIVITY_ATTEND)
-                                       || ($datarray['verb'] === ACTIVITY_ATTENDNO)
-                                       || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) {
-                                       $datarray['type'] = 'activity';
-                                       $datarray['gravity'] = GRAVITY_LIKE;
-                                       // only one like or dislike per person
-                                       // splitted into two queries for performance issues
-                                       $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
-                                               intval($datarray['uid']),
-                                               dbesc($datarray['author-link']),
-                                               dbesc($datarray['verb']),
-                                               dbesc($datarray['parent-uri'])
-                                       );
-                                       if($r && count($r))
-                                               continue;
-
-                                       $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
-                                               intval($datarray['uid']),
-                                               dbesc($datarray['author-link']),
-                                               dbesc($datarray['verb']),
-                                               dbesc($datarray['parent-uri'])
-                                       );
-                                       if($r && count($r))
-                                               continue;
-
-                               }
-
-                               if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) {
-
-                                       $xo = parse_xml_string($datarray['object'],false);
-                                       $xt = parse_xml_string($datarray['target'],false);
-
-                                       if($xt->type == ACTIVITY_OBJ_NOTE) {
-                                               $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1",
-                                                       dbesc($xt->id),
-                                                       intval($importer['importer_uid'])
-                                               );
-                                               if(! count($r))
-                                                       continue;
-
-                                               // extract tag, if not duplicate, add to parent item
-                                               if($xo->content) {
-                                                       if(! (stristr($r[0]['tag'],trim($xo->content)))) {
-                                                               q("UPDATE item SET tag = '%s' WHERE id = %d",
-                                                                       dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'),
-                                                                       intval($r[0]['id'])
-                                                               );
-                                                               create_tags_from_item($r[0]['id']);
-                                                       }
-                                               }
-                                       }
-                               }
-
-                               $posted_id = item_store($datarray);
-
-                               // find out if our user is involved in this conversation and wants to be notified.
-
-                               if(!x($datarray['type']) || $datarray['type'] != 'activity') {
-
-                                       $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
-                                               dbesc($top_uri),
-                                               intval($importer['importer_uid'])
-                                       );
-
-                                       if(count($myconv)) {
-                                               $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
-
-                                               // first make sure this isn't our own post coming back to us from a wall-to-wall event
-                                               if(! link_compare($datarray['author-link'],$importer_url)) {
-
-
-                                                       foreach($myconv as $conv) {
-
-                                                               // now if we find a match, it means we're in this conversation
-
-                                                               if(! link_compare($conv['author-link'],$importer_url))
-                                                                       continue;
-
-                                                               require_once('include/enotify.php');
-
-                                                               $conv_parent = $conv['parent'];
-
-                                                               notification(array(
-                                                                       'type'         => NOTIFY_COMMENT,
-                                                                       'notify_flags' => $importer['notify-flags'],
-                                                                       'language'     => $importer['language'],
-                                                                       'to_name'      => $importer['username'],
-                                                                       'to_email'     => $importer['email'],
-                                                                       'uid'          => $importer['importer_uid'],
-                                                                       'item'         => $datarray,
-                                                                       'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
-                                                                       'source_name'  => stripslashes($datarray['author-name']),
-                                                                       'source_link'  => $datarray['author-link'],
-                                                                       'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
-                                                                               ? $importer['thumb'] : $datarray['author-avatar']),
-                                                                       'verb'         => ACTIVITY_POST,
-                                                                       'otype'        => 'item',
-                                                                       'parent'       => $conv_parent,
-                                                                       'parent_uri'   => $parent_uri
-
-                                                               ));
-
-                                                               // only send one notification
-                                                               break;
-                                                       }
-                                               }
-                                       }
-                               }
-                               continue;
-                       }
-               }
-
-               else {
-
-                       // Head post of a conversation. Have we seen it? If not, import it.
-
-
-                       $item_id  = $item->get_id();
-                       $datarray = get_atom_elements($feed,$item);
-
-                       if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) {
-                               $ev = bbtoevent($datarray['body']);
-                               if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) {
-                                       $ev['cid'] = $importer['id'];
-                                       $ev['uid'] = $importer['uid'];
-                                       $ev['uri'] = $item_id;
-                                       $ev['edited'] = $datarray['edited'];
-                                       $ev['private'] = $datarray['private'];
-                                       $ev['guid'] = $datarray['guid'];
-
-                                       $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
-                                               dbesc($item_id),
-                                               intval($importer['uid'])
-                                       );
-                                       if(count($r))
-                                               $ev['id'] = $r[0]['id'];
-                                       $xyz = event_store($ev);
-                                       continue;
-                               }
-                       }
-
-                       $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
-                               dbesc($item_id),
-                               intval($importer['importer_uid'])
-                       );
-
-                       // Update content if 'updated' changes
-
-                       if(count($r)) {
-                               if (edited_timestamp_is_newer($r[0], $datarray)) {
-
-                                       // do not accept (ignore) an earlier edit than one we currently have.
-                                       if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
-                                               continue;
-
-                                       $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
-                                               dbesc($datarray['title']),
-                                               dbesc($datarray['body']),
-                                               dbesc($datarray['tag']),
-                                               dbesc(datetime_convert('UTC','UTC',$datarray['edited'])),
-                                               dbesc(datetime_convert()),
-                                               dbesc($item_id),
-                                               intval($importer['importer_uid'])
-                                       );
-                                       create_tags_from_itemuri($item_id, $importer['importer_uid']);
-                                       update_thread_uri($item_id, $importer['importer_uid']);
-                               }
-
-                               // update last-child if it changes
-
-                               $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow');
-                               if($allow && $allow[0]['data'] != $r[0]['last-child']) {
-                                       $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d",
-                                               intval($allow[0]['data']),
-                                               dbesc(datetime_convert()),
-                                               dbesc($item_id),
-                                               intval($importer['importer_uid'])
-                                       );
-                               }
-                               continue;
-                       }
-
-                       $datarray['parent-uri'] = $item_id;
-                       $datarray['uid'] = $importer['importer_uid'];
-                       $datarray['contact-id'] = $importer['id'];
-
-
-                       if(! link_compare($datarray['owner-link'],$importer['url'])) {
-                               // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery,
-                               // but otherwise there's a possible data mixup on the sender's system.
-                               // the tgroup delivery code called from item_store will correct it if it's a forum,
-                               // but we're going to unconditionally correct it here so that the post will always be owned by our contact.
-                               logger('local_delivery: Correcting item owner.', LOGGER_DEBUG);
-                               $datarray['owner-name']   = $importer['senderName'];
-                               $datarray['owner-link']   = $importer['url'];
-                               $datarray['owner-avatar'] = $importer['thumb'];
-                       }
-
-                       if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray)))
-                               continue;
-
-                       // This is my contact on another system, but it's really me.
-                       // Turn this into a wall post.
-                       $notify = item_is_remote_self($importer, $datarray);
-
-                       $posted_id = item_store($datarray, false, $notify);
-
-                       if(stristr($datarray['verb'],ACTIVITY_POKE)) {
-                               $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1));
-                               if(! $verb)
-                                       continue;
-                               $xo = parse_xml_string($datarray['object'],false);
-
-                               if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) {
-
-                                       // somebody was poked/prodded. Was it me?
-
-                                       $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false);
-
-                               foreach($links->link as $l) {
-                               $atts = $l->attributes();
-                               switch($atts['rel']) {
-                                       case "alternate":
-                                                               $Blink = $atts['href'];
-                                                               break;
-                                                       default:
-                                                               break;
-                                   }
-                               }
-                                       if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) {
-
-                                               // send a notification
-                                               require_once('include/enotify.php');
-
-                                               notification(array(
-                                                       'type'         => NOTIFY_POKE,
-                                                       'notify_flags' => $importer['notify-flags'],
-                                                       'language'     => $importer['language'],
-                                                       'to_name'      => $importer['username'],
-                                                       'to_email'     => $importer['email'],
-                                                       'uid'          => $importer['importer_uid'],
-                                                       'item'         => $datarray,
-                                                       'link'             => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)),
-                                                       'source_name'  => stripslashes($datarray['author-name']),
-                                                       'source_link'  => $datarray['author-link'],
-                                                       'source_photo' => ((link_compare($datarray['author-link'],$importer['url']))
-                                                               ? $importer['thumb'] : $datarray['author-avatar']),
-                                                       'verb'         => $datarray['verb'],
-                                                       'otype'        => 'person',
-                                                       'activity'     => $verb,
-                                                       'parent'       => $datarray['parent']
-                                               ));
-                                       }
-                               }
-                       }
-
-                       continue;
-               }
-       }
-
-       return 0;
-       // NOTREACHED
-
-}
-
-
-function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
-       $url = notags(trim($datarray['author-link']));
-       $name = notags(trim($datarray['author-name']));
-       $photo = notags(trim($datarray['author-avatar']));
-
-       if (is_object($item)) {
-               $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
-               if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
-                       $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
-       } else
-               $nick = $item;
-
-       if(is_array($contact)) {
-               if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
-                       || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
-                       $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
-                               intval(CONTACT_IS_FRIEND),
-                               intval($contact['id']),
-                               intval($importer['uid'])
-                       );
-               }
-               // send email notification to owner?
-       } else {
-
-               // create contact record
-
-               $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
-                       `blocked`, `readonly`, `pending`, `writable`)
-                       VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
-                       intval($importer['uid']),
-                       dbesc(datetime_convert()),
-                       dbesc($url),
-                       dbesc(normalise_link($url)),
-                       dbesc($name),
-                       dbesc($nick),
-                       dbesc($photo),
-                       dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
-                       intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
-               );
-               $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
-                               intval($importer['uid']),
-                               dbesc($url)
-               );
-               if(count($r)) {
-                               $contact_record = $r[0];
-
-                               $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
+                               $photos = import_profile_photo($photo,$importer["uid"],$contact_record["id"]);
 
                                q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `id` = %d",
                                        dbesc($photos[0]),
@@ -4287,7 +1458,7 @@ function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
        }
 }
 
-function lose_follower($importer,$contact,$datarray,$item) {
+function lose_follower($importer,$contact,$datarray = array(),$item = "") {
 
        if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
                q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
@@ -4300,7 +1471,7 @@ function lose_follower($importer,$contact,$datarray,$item) {
        }
 }
 
-function lose_sharer($importer,$contact,$datarray,$item) {
+function lose_sharer($importer,$contact,$datarray = array(),$item = "") {
 
        if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
                q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
@@ -4313,7 +1484,6 @@ function lose_sharer($importer,$contact,$datarray,$item) {
        }
 }
 
-
 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
 
        $a = get_app();
@@ -4356,189 +1526,6 @@ function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
 
 }
 
-
-function atom_author($tag,$name,$uri,$h,$w,$photo,$geo) {
-       $o = '';
-       if(! $tag)
-               return $o;
-       $name = xmlify($name);
-       $uri = xmlify($uri);
-       $h = intval($h);
-       $w = intval($w);
-       $photo = xmlify($photo);
-
-
-       $o .= "<$tag>\r\n";
-       $o .= "\t<name>$name</name>\r\n";
-       $o .= "\t<uri>$uri</uri>\r\n";
-       $o .= "\t".'<link rel="photo"  type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
-       $o .= "\t".'<link rel="avatar" type="image/jpeg" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
-
-       if ($tag == "author") {
-
-               if($geo)
-                       $o .= '<georss:point>'.xmlify($geo).'</georss:point>'."\r\n";
-
-               $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
-                               `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`,
-                               `profile`.`homepage`,`contact`.`nick` FROM `profile`
-                               INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid`
-                               INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
-                               WHERE `profile`.`is-default` AND `contact`.`self` AND
-                                       NOT `user`.`hidewall` AND `contact`.`nurl`='%s'",
-                       dbesc(normalise_link($uri)));
-               if ($r) {
-                       $location = '';
-                       if($r[0]['locality'])
-                               $location .= $r[0]['locality'];
-                       if($r[0]['region']) {
-                               if($location)
-                                       $location .= ', ';
-                               $location .= $r[0]['region'];
-                       }
-                       if($r[0]['country-name']) {
-                               if($location)
-                                       $location .= ', ';
-                               $location .= $r[0]['country-name'];
-                       }
-
-                       $o .= "\t<poco:preferredUsername>".xmlify($r[0]["nick"])."</poco:preferredUsername>\r\n";
-                       $o .= "\t<poco:displayName>".xmlify($r[0]["name"])."</poco:displayName>\r\n";
-                       $o .= "\t<poco:note>".xmlify(bbcode($r[0]["about"]))."</poco:note>\r\n";
-                       $o .= "\t<poco:address>\r\n";
-                       $o .= "\t\t<poco:formatted>".xmlify($location)."</poco:formatted>\r\n";
-                       $o .= "\t</poco:address>\r\n";
-                       $o .= "\t<poco:urls>\r\n";
-                       $o .= "\t<poco:type>homepage</poco:type>\r\n";
-                       $o .= "\t\t<poco:value>".xmlify($r[0]["homepage"])."</poco:value>\r\n";
-                       $o .= "\t\t<poco:primary>true</poco:primary>\r\n";
-                       $o .= "\t</poco:urls>\r\n";
-               }
-       }
-
-       call_hooks('atom_author', $o);
-
-       $o .= "</$tag>\r\n";
-       return $o;
-}
-
-function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) {
-
-       $a = get_app();
-
-       if(! $item['parent'])
-               return;
-
-       if($item['deleted'])
-               return '<at:deleted-entry ref="' . xmlify($item['uri']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
-
-
-       if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
-               $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
-       else
-               $body = $item['body'];
-
-
-       $o = "\r\n\r\n<entry>\r\n";
-
-       if(is_array($author))
-               $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb'], $item['coord']);
-       else
-               $o .= atom_author('author',(($item['author-name']) ? $item['author-name'] : $item['name']),(($item['author-link']) ? $item['author-link'] : $item['url']),80,80,(($item['author-avatar']) ? $item['author-avatar'] : $item['thumb']), $item['coord']);
-       if(strlen($item['owner-name']))
-               $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar'], $item['coord']);
-
-       if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
-               $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"]));
-               $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
-               $o .= '<thr:in-reply-to ref="'.xmlify($parent_item).'" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$parent[0]['guid']).'" />'."\r\n";
-       }
-
-       $htmlbody = $body;
-
-       if ($item['title'] != "")
-               $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody;
-
-       $htmlbody = bbcode($htmlbody, false, false, 7);
-
-       $o .= '<id>' . xmlify($item['uri']) . '</id>' . "\r\n";
-       $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
-       $o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
-       $o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
-       $o .= '<dfrn:env>' . base64url_encode($body, true) . '</dfrn:env>' . "\r\n";
-       $o .= '<content type="' . $type . '" >' . xmlify((($type === 'html') ? $htmlbody : $body)) . '</content>' . "\r\n";
-       $o .= '<link rel="alternate" type="text/html" href="'.xmlify($a->get_baseurl().'/display/'.$item['guid']).'" />'."\r\n";
-
-       $o .= '<status_net notice_id="'.$item['id'].'"></status_net>'."\r\n";
-
-       if($comment)
-               $o .= '<dfrn:comment-allow>' . intval($item['last-child']) . '</dfrn:comment-allow>' . "\r\n";
-
-       if($item['location']) {
-               $o .= '<dfrn:location>' . xmlify($item['location']) . '</dfrn:location>' . "\r\n";
-               $o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
-       }
-
-       if($item['coord'])
-               $o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
-
-       if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
-               $o .= '<dfrn:private>' . (($item['private']) ? $item['private'] : 1) . '</dfrn:private>' . "\r\n";
-
-       if($item['extid'])
-               $o .= '<dfrn:extid>' . xmlify($item['extid']) . '</dfrn:extid>' . "\r\n";
-       if($item['bookmark'])
-               $o .= '<dfrn:bookmark>true</dfrn:bookmark>' . "\r\n";
-
-       if($item['app'])
-               $o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
-
-       if($item['guid'])
-               $o .= '<dfrn:diaspora_guid>' . $item['guid'] . '</dfrn:diaspora_guid>' . "\r\n";
-
-       if($item['signed_text']) {
-               $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer'])));
-               $o .= '<dfrn:diaspora_signature>' . xmlify($sign) . '</dfrn:diaspora_signature>' . "\r\n";
-       }
-
-       $verb = construct_verb($item);
-       $o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
-       $actobj = construct_activity_object($item);
-       if(strlen($actobj))
-               $o .= $actobj;
-       $actarg = construct_activity_target($item);
-       if(strlen($actarg))
-               $o .= $actarg;
-
-       $tags = item_getfeedtags($item);
-       if(count($tags)) {
-               foreach($tags as $t)
-                       if (($type != 'html') OR ($t[0] != "@"))
-                               $o .= '<category scheme="X-DFRN:' . xmlify($t[0]) . ':' . xmlify($t[1]) . '" term="' . xmlify($t[2]) . '" />' . "\r\n";
-       }
-
-       /// @TODO
-       /// To support these elements, the API needs to be enhanced
-       /// $o .= '<link rel="ostatus:conversation" href="'.xmlify($a->get_baseurl().'/display/'.$owner['nickname'].'/'.$item['parent']).'"/>'."\r\n";
-       /// $o .= "\t".'<link rel="self" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
-       /// $o .= "\t".'<link rel="edit" type="application/atom+xml" href="'.xmlify($a->get_baseurl().'/api/statuses/show/'.$item['id'].'.atom').'"/>'."\r\n";
-
-       // Deactivated since it was meant only for OStatus
-       //$o .= item_get_attachment($item);
-
-       $o .= item_getfeedattach($item);
-
-       $mentioned = get_mentions($item);
-       if($mentioned)
-               $o .= $mentioned;
-
-       call_hooks('atom_entry', $o);
-
-       $o .= '</entry>' . "\r\n";
-
-       return $o;
-}
-
 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
 
        if(get_config('system','disable_embedded'))
@@ -4642,7 +1629,6 @@ function fix_private_photos($s, $uid, $item = null, $cid = 0) {
        return($new_body);
 }
 
-
 function has_permissions($obj) {
        if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
                return true;
@@ -4703,50 +1689,6 @@ function item_getfeedtags($item) {
        return $ret;
 }
 
-function item_get_attachment($item) {
-       $o = "";
-       $siteinfo = get_attached_data($item["body"]);
-
-       switch($siteinfo["type"]) {
-               case 'link':
-                       $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
-                       break;
-               case 'photo':
-                       $imgdata = get_photo_info($siteinfo["image"]);
-                       $o = '<link rel="enclosure" href="'.xmlify($siteinfo["image"]).'" type="'.$imgdata["mime"].'" length="'.$imgdata["size"].'"/>'."\r\n";
-                       break;
-               case 'video':
-                       $o = '<link rel="enclosure" href="'.xmlify($siteinfo["url"]).'" type="text/html; charset=UTF-8" length="" title="'.xmlify($siteinfo["title"]).'"/>'."\r\n";
-                       break;
-               default:
-                       break;
-       }
-
-       return $o;
-}
-
-function item_getfeedattach($item) {
-       $ret = '';
-       $arr = explode('[/attach],',$item['attach']);
-       if(count($arr)) {
-               foreach($arr as $r) {
-                       $matches = false;
-                       $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches);
-                       if($cnt) {
-                               $ret .= '<link rel="enclosure" href="' . xmlify($matches[1]) . '" type="' . xmlify($matches[3]) . '" ';
-                               if(intval($matches[2]))
-                                       $ret .= 'length="' . intval($matches[2]) . '" ';
-                               if($matches[4] !== ' ')
-                                       $ret .= 'title="' . xmlify(trim($matches[4])) . '" ';
-                               $ret .= ' />' . "\r\n";
-                       }
-               }
-       }
-       return $ret;
-}
-
-
-
 function item_expire($uid, $days, $network = "", $force = false) {
 
        if((! $uid) || ($days < 1))
index 6aef0cb5b8b3b4421e8ac25c764def3a0ac1d764..646e0727be4ec63f09773ed6fddea299ed635db0 100644 (file)
@@ -279,6 +279,9 @@ function store_diaspora_like_retract_sig($activity, $item, $like_item, $contact)
                $contact_baseurl = substr($contact['url'], $contact_baseurl_start, $contact_baseurl_length);
                $diaspora_handle = $contact['nick'] . '@' . $contact_baseurl;
 
+               // This code could never had worked (the return values form the queries were used in a wrong way.
+               // Additionally it is needlessly complicated. Either the contact is owner or not. And we have this data already.
+/*
                // Get contact's private key if he's a user of the local Friendica server
                $r = q("SELECT `contact`.`uid` FROM `contact` WHERE `url` = '%s' AND `self` = 1 LIMIT 1",
                        dbesc($contact['url'])
@@ -289,9 +292,15 @@ function store_diaspora_like_retract_sig($activity, $item, $like_item, $contact)
                        $r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1",
                                intval($contact_uid)
                        );
+*/
+               // Is the contact the owner? Then fetch the private key
+               if ($contact['self'] AND ($contact['uid'] > 0)) {
+                       $r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1",
+                               intval($contact['uid'])
+                       );
 
-                       if( $r)
-                               $authorsig = base64_encode(rsa_sign($signed_text,$r['prvkey'],'sha256'));
+                       if($r)
+                               $authorsig = base64_encode(rsa_sign($signed_text,$r[0]['prvkey'],'sha256'));
                }
 
                if(! isset($authorsig))
@@ -329,6 +338,10 @@ function store_diaspora_like_sig($activity, $post_type, $contact, $post_id) {
                $contact_baseurl = substr($contact['url'], $contact_baseurl_start, $contact_baseurl_length);
                $diaspora_handle = $contact['nick'] . '@' . $contact_baseurl;
 
+
+               // This code could never had worked (the return values form the queries were used in a wrong way.
+               // Additionally it is needlessly complicated. Either the contact is owner or not. And we have this data already.
+/*
                // Get contact's private key if he's a user of the local Friendica server
                $r = q("SELECT `contact`.`uid` FROM `contact` WHERE `url` = '%s' AND `self` = 1 LIMIT 1",
                        dbesc($contact['url'])
@@ -343,6 +356,17 @@ function store_diaspora_like_sig($activity, $post_type, $contact, $post_id) {
                        if( $r)
                                $contact_uprvkey = $r['prvkey'];
                }
+*/
+
+               // Is the contact the owner? Then fetch the private key
+               if ($contact['self'] AND ($contact['uid'] > 0)) {
+                       $r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1",
+                               intval($contact['uid'])
+                       );
+
+                       if($r)
+                               $contact_uprvkey = $r[0]['prvkey'];
+               }
 
                $r = q("SELECT guid, parent FROM `item` WHERE id = %d LIMIT 1",
                        intval($post_id)
@@ -353,7 +377,7 @@ function store_diaspora_like_sig($activity, $post_type, $contact, $post_id) {
                                intval($r[0]['parent'])
                        );
                        if( $p) {
-                               $signed_text = $r[0]['guid'] . ';Post;' . $p[0]['guid'] . ';true;' . $diaspora_handle;
+                               $signed_text = 'true;'.$r[0]['guid'].';Post;'.$p[0]['guid'].';'.$diaspora_handle;
 
                                if(isset($contact_uprvkey))
                                        $authorsig = base64_encode(rsa_sign($signed_text,$contact_uprvkey,'sha256'));
index ac5191b259c876306ebd8ae898b94a52cdbc0540..c6379e407bcc79aae4a3ed852c3409d9225a1f09 100644 (file)
@@ -42,6 +42,7 @@ if(!function_exists('z_fetch_url')){
  * @return array an assoziative array with:
  *  * \e int \b return_code => HTTP return code or 0 if timeout or failure
  *  * \e boolean \b success => boolean true (if HTTP 2xx result) or false
+ *  * \e string \b redirect_url => in case of redirect, content was finally retrieved from this URL
  *  * \e string \b header => HTTP headers
  *  * \e string \b body => fetched content
  */
@@ -116,6 +117,9 @@ function z_fetch_url($url,$binary = false, &$redirects = 0, $opts=array()) {
        // if it throws any errors.
 
        $s = @curl_exec($ch);
+       if (curl_errno($ch) !== CURLE_OK) {
+               logger('fetch_url error fetching '.$url.': '.curl_error($ch), LOGGER_NORMAL);
+       }
 
        $base = $s;
        $curl_info = @curl_getinfo($ch);
@@ -133,6 +137,10 @@ function z_fetch_url($url,$binary = false, &$redirects = 0, $opts=array()) {
                $base = substr($base,strlen($chunk));
        }
 
+       $a->set_curl_code($http_code);
+       $a->set_curl_content_type($curl_info['content_type']);
+       $a->set_curl_headers($header);
+
        if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) {
                $new_location_info = @parse_url($curl_info["redirect_url"]);
                $old_location_info = @parse_url($curl_info["url"]);
@@ -160,13 +168,13 @@ function z_fetch_url($url,$binary = false, &$redirects = 0, $opts=array()) {
        $a->set_curl_content_type($curl_info['content_type']);
 
        $body = substr($s,strlen($header));
-       $a->set_curl_headers($header);
 
 
 
        $rc = intval($http_code);
        $ret['return_code'] = $rc;
        $ret['success'] = (($rc >= 200 && $rc <= 299) ? true : false);
+       $ret['redirect_url'] = $url;
        if(! $ret['success']) {
                $ret['error'] = curl_error($ch);
                $ret['debug'] = $curl_info;
@@ -1246,6 +1254,9 @@ function original_url($url, $depth=1, $fetchbody = false) {
 
        $a->save_timestamp($stamp1, "network");
 
+       if ($http_code == 0)
+               return($url);
+
        if ((($curl_info['http_code'] == "301") OR ($curl_info['http_code'] == "302"))
                AND (($curl_info['redirect_url'] != "") OR ($curl_info['location'] != ""))) {
                if ($curl_info['redirect_url'] != "")
index 92e48c355404a0f4bc1fb97451ea097d47f3f8b8..6c42f19c6aec66c22d0f2522b15f13a8382b7ea8 100644 (file)
@@ -535,7 +535,7 @@ function notifier_run(&$argv, &$argc){
 
        if($public_message) {
 
-               if (!$followup)
+               if (!$followup AND $top_level)
                        $r0 = diaspora_fetch_relay();
                else
                        $r0 = array();
index c848a4580c9dafc407c649fc2a36e0e2130eb399..b1770f68903408034053997dff8a0495081d89aa 100755 (executable)
@@ -13,7 +13,13 @@ function oembed_fetch_url($embedurl, $no_rich_type = false){
 
        $a = get_app();
 
-       $txt = Cache::get($a->videowidth . $embedurl);
+       $r = q("SELECT * FROM `oembed` WHERE `url` = '%s'",
+               dbesc(normalise_link($embedurl)));
+
+       if ($r)
+               $txt = $r[0]["content"];
+       else
+               $txt = Cache::get($a->videowidth . $embedurl);
 
        // These media files should now be caught in bbcode.php
        // left here as a fallback in case this is called from another source
@@ -66,8 +72,14 @@ function oembed_fetch_url($embedurl, $no_rich_type = false){
 
                if ($txt[0]!="{")
                        $txt='{"type":"error"}';
-               else    //save in cache
+               else {  //save in cache
+                       $j = json_decode($txt);
+                       if ($j->type != "error")
+                               q("INSERT INTO `oembed` (`url`, `content`, `created`) VALUES ('%s', '%s', '%s')",
+                                       dbesc(normalise_link($embedurl)), dbesc($txt), dbesc(datetime_convert()));
+
                        Cache::set($a->videowidth . $embedurl,$txt, CACHE_DAY);
+               }
        }
 
        $j = json_decode($txt);
@@ -85,7 +97,7 @@ function oembed_fetch_url($embedurl, $no_rich_type = false){
        // If fetching information doesn't work, then improve via internal functions
        if (($j->type == "error") OR ($no_rich_type AND ($j->type == "rich"))) {
                require_once("mod/parse_url.php");
-               $data = parseurl_getsiteinfo($embedurl, true, false);
+               $data = parseurl_getsiteinfo_cached($embedurl, true, false);
                $j->type = $data["type"];
 
                if ($j->type == "photo") {
index 516f1dfd4d7308096480f14ce108afb5ba570ec5..6fb191f73da534dff3230ac9692848a9dd897d68 100644 (file)
@@ -27,7 +27,6 @@ function onepoll_run(&$argv, &$argc){
 
        require_once('include/session.php');
        require_once('include/datetime.php');
-       require_once('library/simplepie/simplepie.inc');
        require_once('include/items.php');
        require_once('include/Contact.php');
        require_once('include/email.php');
@@ -335,7 +334,9 @@ function onepoll_run(&$argv, &$argc){
                if($contact['rel'] == CONTACT_IS_FOLLOWER || $contact['blocked'] || $contact['readonly'])
                        return;
 
-               $xml = fetch_url($contact['poll']);
+               $cookiejar = tempnam(get_temppath(), 'cookiejar-onepoll-');
+               $xml = fetch_url($contact['poll'], false, $redirects, 0, Null, $cookiejar);
+               unlink($cookiejar);
        }
        elseif($contact['network'] === NETWORK_MAIL || $contact['network'] === NETWORK_MAIL2) {
 
index 1087596e2f23aed16322d0826ab66d8ca5fd7170..00022f8c6cdd3ddfccf500d73e936008ab51814d 100644 (file)
@@ -17,15 +17,6 @@ define('OSTATUS_DEFAULT_POLL_INTERVAL', 30); // given in minutes
 define('OSTATUS_DEFAULT_POLL_TIMEFRAME', 1440); // given in minutes
 define('OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS', 14400); // given in minutes
 
-define("NS_ATOM", "http://www.w3.org/2005/Atom");
-define("NS_THR", "http://purl.org/syndication/thread/1.0");
-define("NS_GEORSS", "http://www.georss.org/georss");
-define("NS_ACTIVITY", "http://activitystrea.ms/spec/1.0/");
-define("NS_MEDIA", "http://purl.org/syndication/atommedia");
-define("NS_POCO", "http://portablecontacts.net/spec/1.0");
-define("NS_OSTATUS", "http://ostatus.org/schema/1.0");
-define("NS_STATUSNET", "http://status.net/schema/api/1/");
-
 function ostatus_check_follow_friends() {
        $r = q("SELECT `uid`,`v` FROM `pconfig` WHERE `cat`='system' AND `k`='ostatus_legacy_contact' AND `v` != ''");
 
@@ -127,47 +118,57 @@ function ostatus_fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch)
        $author["owner-link"] = $author["author-link"];
        $author["owner-avatar"] = $author["author-avatar"];
 
-       if ($r AND !$onlyfetch) {
+       // Only update the contacts if it is an OStatus contact
+       if ($r AND !$onlyfetch AND ($contact["network"] == NETWORK_OSTATUS)) {
                // Update contact data
-               $update_contact = ($r[0]['name-date'] < datetime_convert('','','now -12 hours'));
-               if ($update_contact) {
-                       logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG);
 
-                       $value = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue;
-                       if ($value != "")
-                               $contact["name"] = $value;
+               $value = $xpath->query("atom:link[@rel='salmon']", $context)->item(0)->nodeValue;
+               if ($value != "")
+                       $contact["notify"] = $value;
+
+               $value = $xpath->evaluate('atom:author/uri/text()', $context)->item(0)->nodeValue;
+               if ($value != "")
+                       $contact["alias"] = $value;
+
+               $value = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue;
+               if ($value != "")
+                       $contact["name"] = $value;
+
+               $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue;
+               if ($value != "")
+                       $contact["nick"] = $value;
 
-                       $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue;
-                       if ($value != "")
-                               $contact["nick"] = $value;
+               $value = $xpath->evaluate('atom:author/poco:note/text()', $context)->item(0)->nodeValue;
+               if ($value != "")
+                       $contact["about"] = html2bbcode($value);
 
-                       $value = $xpath->evaluate('atom:author/poco:note/text()', $context)->item(0)->nodeValue;
-                       if ($value != "")
-                               $contact["about"] = html2bbcode($value);
+               $value = $xpath->evaluate('atom:author/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue;
+               if ($value != "")
+                       $contact["location"] = $value;
+
+               if (($contact["name"] != $r[0]["name"]) OR ($contact["nick"] != $r[0]["nick"]) OR ($contact["about"] != $r[0]["about"]) OR ($contact["location"] != $r[0]["location"])) {
 
-                       $value = $xpath->evaluate('atom:author/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue;
-                       if ($value != "")
-                               $contact["location"] = $value;
+                       logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG);
 
-                       q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d AND `network` = '%s'",
+                       q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d",
                                dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]),
-                               dbesc(datetime_convert()), intval($contact["id"]), dbesc(NETWORK_OSTATUS));
+                               dbesc(datetime_convert()), intval($contact["id"]));
 
                        poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"],
-                                       "", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]);
+                                               "", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]);
                }
 
-               $update_photo = ($r[0]['avatar-date'] < datetime_convert('','','now -12 hours'));
-
-               if ($update_photo AND isset($author["author-avatar"])) {
+               if (isset($author["author-avatar"]) AND ($author["author-avatar"] != $r[0]['avatar'])) {
                        logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG);
 
-                       $photos = import_profile_photo($author["author-avatar"], $importer["uid"], $contact["id"]);
-
-                       q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' WHERE `id` = %d AND `network` = '%s'",
-                               dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]),
-                               dbesc(datetime_convert()), intval($contact["id"]), dbesc(NETWORK_OSTATUS));
+                       update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]);
                }
+
+
+               /// @todo Add the "addr" field
+               $contact["generation"] = 2;
+               $contact["photo"] = $author["author-avatar"];
+               update_gcontact($contact);
        }
 
        return($author);
@@ -183,14 +184,14 @@ function ostatus_salmon_author($xml, $importer) {
        @$doc->loadXML($xml);
 
        $xpath = new DomXPath($doc);
-       $xpath->registerNamespace('atom', "http://www.w3.org/2005/Atom");
-       $xpath->registerNamespace('thr', "http://purl.org/syndication/thread/1.0");
-       $xpath->registerNamespace('georss', "http://www.georss.org/georss");
-       $xpath->registerNamespace('activity', "http://activitystrea.ms/spec/1.0/");
-       $xpath->registerNamespace('media', "http://purl.org/syndication/atommedia");
-       $xpath->registerNamespace('poco', "http://portablecontacts.net/spec/1.0");
-       $xpath->registerNamespace('ostatus', "http://ostatus.org/schema/1.0");
-       $xpath->registerNamespace('statusnet', "http://status.net/schema/api/1/");
+       $xpath->registerNamespace('atom', NAMESPACE_ATOM1);
+       $xpath->registerNamespace('thr', NAMESPACE_THREAD);
+       $xpath->registerNamespace('georss', NAMESPACE_GEORSS);
+       $xpath->registerNamespace('activity', NAMESPACE_ACTIVITY);
+       $xpath->registerNamespace('media', NAMESPACE_MEDIA);
+       $xpath->registerNamespace('poco', NAMESPACE_POCO);
+       $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS);
+       $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET);
 
        $entries = $xpath->query('/atom:entry');
 
@@ -214,14 +215,14 @@ function ostatus_import($xml,$importer,&$contact, &$hub) {
        @$doc->loadXML($xml);
 
        $xpath = new DomXPath($doc);
-       $xpath->registerNamespace('atom', "http://www.w3.org/2005/Atom");
-       $xpath->registerNamespace('thr', "http://purl.org/syndication/thread/1.0");
-       $xpath->registerNamespace('georss', "http://www.georss.org/georss");
-       $xpath->registerNamespace('activity', "http://activitystrea.ms/spec/1.0/");
-       $xpath->registerNamespace('media', "http://purl.org/syndication/atommedia");
-       $xpath->registerNamespace('poco', "http://portablecontacts.net/spec/1.0");
-       $xpath->registerNamespace('ostatus', "http://ostatus.org/schema/1.0");
-       $xpath->registerNamespace('statusnet', "http://status.net/schema/api/1/");
+       $xpath->registerNamespace('atom', NAMESPACE_ATOM1);
+       $xpath->registerNamespace('thr', NAMESPACE_THREAD);
+       $xpath->registerNamespace('georss', NAMESPACE_GEORSS);
+       $xpath->registerNamespace('activity', NAMESPACE_ACTIVITY);
+       $xpath->registerNamespace('media', NAMESPACE_MEDIA);
+       $xpath->registerNamespace('poco', NAMESPACE_POCO);
+       $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS);
+       $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET);
 
        $gub = "";
        $hub_attributes = $xpath->query("/atom:feed/atom:link[@rel='hub']")->item(0)->attributes;
@@ -546,29 +547,6 @@ function ostatus_import($xml,$importer,&$contact, &$hub) {
                }
 
                logger("Item was stored with id ".$item_id, LOGGER_DEBUG);
-               $item["id"] = $item_id;
-
-               if ($mention) {
-                       $u = q("SELECT `notify-flags`, `language`, `username`, `email` FROM user WHERE uid = %d LIMIT 1", intval($item['uid']));
-                       $r = q("SELECT `parent` FROM `item` WHERE `id` = %d", intval($item_id));
-
-                       notification(array(
-                               'type'         => NOTIFY_TAGSELF,
-                               'notify_flags' => $u[0]["notify-flags"],
-                               'language'     => $u[0]["language"],
-                               'to_name'      => $u[0]["username"],
-                               'to_email'     => $u[0]["email"],
-                               'uid'          => $item["uid"],
-                               'item'         => $item,
-                               'link'         => $a->get_baseurl().'/display/'.urlencode(get_item_guid($item_id)),
-                               'source_name'  => $item["author-name"],
-                               'source_link'  => $item["author-link"],
-                               'source_photo' => $item["author-avatar"],
-                               'verb'         => ACTIVITY_TAG,
-                               'otype'        => 'item',
-                               'parent'       => $r[0]["parent"]
-                       ));
-               }
        }
 }
 
@@ -1013,28 +991,6 @@ function ostatus_completion($conversation_url, $uid, $item = array()) {
                // Add the conversation entry (but don't fetch the whole conversation)
                ostatus_store_conversation($newitem, $conversation_url);
 
-               if ($mention) {
-                       $u = q("SELECT `notify-flags`, `language`, `username`, `email` FROM user WHERE uid = %d LIMIT 1", intval($uid));
-                       $r = q("SELECT `parent` FROM `item` WHERE `id` = %d", intval($newitem));
-
-                       notification(array(
-                               'type'         => NOTIFY_TAGSELF,
-                               'notify_flags' => $u[0]["notify-flags"],
-                               'language'     => $u[0]["language"],
-                               'to_name'      => $u[0]["username"],
-                               'to_email'     => $u[0]["email"],
-                               'uid'          => $uid,
-                               'item'         => $arr,
-                               'link'         => $a->get_baseurl().'/display/'.urlencode(get_item_guid($newitem)),
-                               'source_name'  => $arr["author-name"],
-                               'source_link'  => $arr["author-link"],
-                               'source_photo' => $arr["author-avatar"],
-                               'verb'         => ACTIVITY_TAG,
-                               'otype'        => 'item',
-                               'parent'       => $r[0]["parent"]
-                       ));
-               }
-
                // If the newly created item is the top item then change the parent settings of the thread
                // This shouldn't happen anymore. This is supposed to be absolote.
                if ($arr["uri"] == $first_id) {
@@ -1109,7 +1065,7 @@ function get_reshared_guid($item) {
        return $guid;
 }
 
-function xml_add_element($doc, $parent, $element, $value = "", $attributes = array()) {
+function xml_create_element($doc, $element, $value = "", $attributes = array()) {
        $element = $doc->createElement($element, xmlify($value));
 
        foreach ($attributes AS $key => $value) {
@@ -1117,7 +1073,11 @@ function xml_add_element($doc, $parent, $element, $value = "", $attributes = arr
                $attribute->value = xmlify($value);
                $element->appendChild($attribute);
        }
+       return $element;
+}
 
+function xml_add_element($doc, $parent, $element, $value = "", $attributes = array()) {
+       $element = xml_create_element($doc, $element, $value, $attributes);
        $parent->appendChild($element);
 }
 
@@ -1151,16 +1111,16 @@ function ostatus_format_picture_post($body) {
 function ostatus_add_header($doc, $owner) {
        $a = get_app();
 
-       $root = $doc->createElementNS(NS_ATOM, 'feed');
+       $root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed');
        $doc->appendChild($root);
 
-       $root->setAttribute("xmlns:thr", NS_THR);
-       $root->setAttribute("xmlns:georss", NS_GEORSS);
-       $root->setAttribute("xmlns:activity", NS_ACTIVITY);
-       $root->setAttribute("xmlns:media", NS_MEDIA);
-       $root->setAttribute("xmlns:poco", NS_POCO);
-       $root->setAttribute("xmlns:ostatus", NS_OSTATUS);
-       $root->setAttribute("xmlns:statusnet", NS_STATUSNET);
+       $root->setAttribute("xmlns:thr", NAMESPACE_THREAD);
+       $root->setAttribute("xmlns:georss", NAMESPACE_GEORSS);
+       $root->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY);
+       $root->setAttribute("xmlns:media", NAMESPACE_MEDIA);
+       $root->setAttribute("xmlns:poco", NAMESPACE_POCO);
+       $root->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS);
+       $root->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET);
 
        $attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION);
        xml_add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes);
@@ -1352,6 +1312,10 @@ function ostatus_add_author($doc, $owner) {
 function ostatus_entry($doc, $item, $owner, $toplevel = false, $repeat = false) {
        $a = get_app();
 
+       if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) {
+               logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG);
+       }
+
        $is_repeat = false;
 
 /*     if (!$repeat) {
@@ -1374,15 +1338,15 @@ function ostatus_entry($doc, $item, $owner, $toplevel = false, $repeat = false)
                $entry = $doc->createElement("activity:object");
                $title = sprintf("New note by %s", $owner["nick"]);
        } else {
-               $entry = $doc->createElementNS(NS_ATOM, "entry");
+               $entry = $doc->createElementNS(NAMESPACE_ATOM1, "entry");
 
-               $entry->setAttribute("xmlns:thr", NS_THR);
-               $entry->setAttribute("xmlns:georss", NS_GEORSS);
-               $entry->setAttribute("xmlns:activity", NS_ACTIVITY);
-               $entry->setAttribute("xmlns:media", NS_MEDIA);
-               $entry->setAttribute("xmlns:poco", NS_POCO);
-               $entry->setAttribute("xmlns:ostatus", NS_OSTATUS);
-               $entry->setAttribute("xmlns:statusnet", NS_STATUSNET);
+               $entry->setAttribute("xmlns:thr", NAMESPACE_THREAD);
+               $entry->setAttribute("xmlns:georss", NAMESPACE_GEORSS);
+               $entry->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY);
+               $entry->setAttribute("xmlns:media", NAMESPACE_MEDIA);
+               $entry->setAttribute("xmlns:poco", NAMESPACE_POCO);
+               $entry->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS);
+               $entry->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET);
 
                $author = ostatus_add_author($doc, $owner);
                $entry->appendChild($author);
@@ -1447,9 +1411,12 @@ function ostatus_entry($doc, $item, $owner, $toplevel = false, $repeat = false)
                $repeated_owner["about"] = "";
                $repeated_owner["uid"] = 0;
 
-               $r =q("SELECT * FROM `unique_contacts` WHERE `url` = '%s'", normalise_link($repeated_item["author-link"]));
+               // Fetch the missing data from the global contacts
+               $r =q("SELECT * FROM `gcontact` WHERE `nurl` = '%s'", normalise_link($repeated_item["author-link"]));
                if ($r) {
-                       $repeated_owner["nick"] = $r[0]["nick"];
+                       if ($r[0]["nick"] != "")
+                               $repeated_owner["nick"] = $r[0]["nick"];
+
                        $repeated_owner["location"] = $r[0]["location"];
                        $repeated_owner["about"] = $r[0]["about"];
                }
index cb56850d4f3cec833eacab9953527df603c3ac74..05431bee2d27d2b6ca601d9f0a93d02e873c9590 100644 (file)
@@ -90,6 +90,14 @@ function get_attached_data($body) {
                                $post["text"] = $body;
                        }
                }
+
+               if (preg_match_all("(\[url\]([$URLSearchString]*)\[\/url\])ism", $body, $links,  PREG_SET_ORDER)) {
+                       if (count($links) == 1) {
+                               $post["type"] = "text";
+                               $post["url"] = $links[0][1];
+                               $post["text"] = $body;
+                       }
+               }
                if (!isset($post["type"])) {
                        $post["type"] = "text";
                        $post["text"] = trim($body);
@@ -129,9 +137,17 @@ function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2) {
        require_once("include/html2plain.php");
        require_once("include/network.php");
 
+       // Remove the hash tags
+       $URLSearchString = "^\[\]";
+       $body = preg_replace("/([#@])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '$1$3', $b["body"]);
+
+       // Add an URL element if the text contains a raw link
+       $body = preg_replace("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", '$1[url]$2[/url]', $body);
+
        // At first look at data that is attached via "type-..." stuff
        // This will hopefully replaced with a dedicated bbcode later
-       $post = get_attached_data($b["body"]);
+       //$post = get_attached_data($b["body"]);
+       $post = get_attached_data($body);
 
        if (($b["title"] != "") AND ($post["text"] != ""))
                $post["text"] = trim($b["title"]."\n\n".$post["text"]);
@@ -146,6 +162,8 @@ function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2) {
        if ($includedlinks) {
                if ($post["type"] == "link")
                        $link = $post["url"];
+               elseif ($post["type"] == "text")
+                       $link = $post["url"];
                elseif ($post["type"] == "video")
                        $link = $post["url"];
                elseif ($post["type"] == "photo")
@@ -161,8 +179,22 @@ function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2) {
                // But: if the link is beyond the limit, then it has to be added.
                if (($link != "") AND strstr($msg, $link)) {
                        $pos = strpos($msg, $link);
-                       if (($limit == 0) OR ($pos < $limit))
+
+                       // Will the text be shortened in the link?
+                       // Or is the link the last item in the post?
+                       if (($limit > 0) AND ($pos < $limit) AND (($pos + 23 > $limit) OR ($pos + strlen($link) == strlen($msg))))
+                               $msg = trim(str_replace($link, "", $msg));
+                       elseif (($limit == 0) OR ($pos < $limit)) {
+                               // The limit has to be increased since it will be shortened - but not now
+                               // Only do it with Twitter (htmlmode = 8)
+                               if (($limit > 0) AND (strlen($link) > 23) AND ($htmlmode == 8))
+                                       $limit = $limit - 23 + strlen($link);
+
                                $link = "";
+
+                               if ($post["type"] == "text")
+                                       unset($post["url"]);
+                       }
                }
        }
 
@@ -178,7 +210,9 @@ function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2) {
 
                if (iconv_strlen($msg, "UTF-8") > $limit) {
 
-                       if (!isset($post["url"])) {
+                       if (($post["type"] == "text") AND isset($post["url"]))
+                               $post["url"] = $b["plink"];
+                       elseif (!isset($post["url"])) {
                                $limit = $limit - 23;
                                $post["url"] = $b["plink"];
                        } elseif (strpos($b["body"], "[share") !== false)
index c681bfb3898448300578643ba2ad7ff14938bbb6..90e94ede9e05af23ba0bd56791569bbc6e0e1125 100644 (file)
@@ -26,6 +26,9 @@ function poller_run(&$argv, &$argc){
                unset($db_host, $db_user, $db_pass, $db_data);
        };
 
+       if (poller_max_connections_reached())
+               return;
+
        $load = current_load();
        if($load) {
                $maxsysload = intval(get_config('system','maxloadavg'));
@@ -39,8 +42,10 @@ function poller_run(&$argv, &$argc){
        }
 
        // Checking the number of workers
-       if (poller_too_much_workers(1))
+       if (poller_too_much_workers(1)) {
+               poller_kill_stale_workers();
                return;
+       }
 
        if(($argc <= 1) OR ($argv[1] != "no_cron")) {
                // Run the cron job that calls all other jobs
@@ -50,16 +55,7 @@ function poller_run(&$argv, &$argc){
                proc_run("php","include/cronhooks.php");
 
                // Cleaning dead processes
-               $r = q("SELECT DISTINCT(`pid`) FROM `workerqueue` WHERE `executed` != '0000-00-00 00:00:00'");
-               foreach($r AS $pid)
-                       if (!posix_kill($pid["pid"], 0))
-                               q("UPDATE `workerqueue` SET `executed` = '0000-00-00 00:00:00', `pid` = 0 WHERE `pid` = %d",
-                                       intval($pid["pid"]));
-                       else {
-                               /// @TODO Kill long running processes
-                               /// But: Update processes (like the database update) mustn't be killed
-                       }
-
+               poller_kill_stale_workers();
        } else
                // Sleep four seconds before checking for running processes again to avoid having too many workers
                sleep(4);
@@ -72,6 +68,10 @@ function poller_run(&$argv, &$argc){
 
        while ($r = q("SELECT * FROM `workerqueue` WHERE `executed` = '0000-00-00 00:00:00' ORDER BY `created` LIMIT 1")) {
 
+               // Constantly check the number of available database connections to let the frontend be accessible at any time
+               if (poller_max_connections_reached())
+                       return;
+
                // Count active workers and compare them with a maximum value that depends on the load
                if (poller_too_much_workers(3))
                        return;
@@ -124,6 +124,107 @@ function poller_run(&$argv, &$argc){
 
 }
 
+/**
+ * @brief Checks if the number of database connections has reached a critical limit.
+ *
+ * @return bool Are more than 3/4 of the maximum connections used?
+ */
+function poller_max_connections_reached() {
+
+       // Fetch the max value from the config. This is needed when the system cannot detect the correct value by itself.
+       $max = get_config("system", "max_connections");
+
+       if ($max == 0) {
+               // the maximum number of possible user connections can be a system variable
+               $r = q("SHOW VARIABLES WHERE `variable_name` = 'max_user_connections'");
+               if ($r)
+                       $max = $r[0]["Value"];
+
+               // Or it can be granted. This overrides the system variable
+               $r = q("SHOW GRANTS");
+               if ($r)
+                       foreach ($r AS $grants) {
+                               $grant = array_pop($grants);
+                               if (stristr($grant, "GRANT USAGE ON"))
+                                       if (preg_match("/WITH MAX_USER_CONNECTIONS (\d*)/", $grant, $match))
+                                               $max = $match[1];
+                       }
+       }
+
+       // If $max is set we will use the processlist to determine the current number of connections
+       // The processlist only shows entries of the current user
+       if ($max != 0) {
+               $r = q("SHOW PROCESSLIST");
+               if (!$r)
+                       return false;
+
+               $used = count($r);
+
+               logger("Connection usage (user values): ".$used."/".$max, LOGGER_DEBUG);
+
+               $level = $used / $max;
+
+               if ($level >= (3/4)) {
+                       logger("Maximum level (3/4) of user connections reached: ".$used."/".$max);
+                       return true;
+               }
+       }
+
+       // We will now check for the system values.
+       // This limit could be reached although the user limits are fine.
+       $r = q("SHOW VARIABLES WHERE `variable_name` = 'max_connections'");
+       if (!$r)
+               return false;
+
+       $max = intval($r[0]["Value"]);
+       if ($max == 0)
+               return false;
+
+       $r = q("SHOW STATUS WHERE `variable_name` = 'Threads_connected'");
+       if (!$r)
+               return false;
+
+       $used = intval($r[0]["Value"]);
+       if ($used == 0)
+               return false;
+
+       logger("Connection usage (system values): ".$used."/".$max, LOGGER_DEBUG);
+
+       $level = $used / $max;
+
+       if ($level < (3/4))
+               return false;
+
+       logger("Maximum level (3/4) of system connections reached: ".$used."/".$max);
+       return true;
+}
+
+/**
+ * @brief fix the queue entry if the worker process died
+ *
+ */
+function poller_kill_stale_workers() {
+       $r = q("SELECT `pid`, `executed` FROM `workerqueue` WHERE `executed` != '0000-00-00 00:00:00'");
+       foreach($r AS $pid)
+               if (!posix_kill($pid["pid"], 0))
+                       q("UPDATE `workerqueue` SET `executed` = '0000-00-00 00:00:00', `pid` = 0 WHERE `pid` = %d",
+                               intval($pid["pid"]));
+               else {
+                       // Kill long running processes
+                       $duration = (time() - strtotime($pid["executed"])) / 60;
+                       if ($duration > 180) {
+                               logger("Worker process ".$pid["pid"]." took more than 3 hours. It will be killed now.");
+                               posix_kill($pid["pid"], SIGTERM);
+
+                               // Question: If a process is stale: Should we remove it or should we reschedule it?
+                               // By now we rescheduling it. It's maybe not the wisest decision?
+                               q("UPDATE `workerqueue` SET `executed` = '0000-00-00 00:00:00', `pid` = 0 WHERE `pid` = %d",
+                                       intval($pid["pid"]));
+                       } else
+                               logger("Worker process ".$pid["pid"]." now runs for ".round($duration)." minutes. That's okay.", LOGGER_DEBUG);
+               }
+}
+
 function poller_too_much_workers($stage) {
 
        $queues = get_config("system", "worker_queues");
index 0fcf3617fbeb0d80c5923b4485c589ab0e27cefa..7cc72cc86604d801d08bdbbc88532b1055393b2f 100644 (file)
@@ -3,6 +3,7 @@
 require_once('include/datetime.php');
 require_once('include/diaspora.php');
 require_once('include/queue_fn.php');
+require_once('include/Contact.php');
 
 function profile_change() {
 
@@ -53,19 +54,7 @@ function profile_change() {
                $about = xmlify($profile['about']);
                require_once('include/bbcode.php');
                $about = xmlify(strip_tags(bbcode($about)));
-               $location = '';
-               if($profile['locality'])
-                       $location .= $profile['locality'];
-               if($profile['region']) {
-                       if($location)
-                               $location .= ', ';
-                       $location .= $profile['region'];
-               }
-               if($profile['country-name']) {
-                       if($location)
-                               $location .= ', ';
-                       $location .= $profile['country-name'];
-               }
+               $location = formatted_location($profile);
                $location = xmlify($location);
                $tags = '';
                if($profile['pub_keywords']) {
index cb5fe28ad9bc5e1fee4ab55fdcdb43f0b2c27ed4..1525ca3abf561cd363c9cad5d9d3158b49f75b32 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 require_once("boot.php");
 require_once('include/queue_fn.php');
+require_once('include/dfrn.php');
 
 function queue_run(&$argv, &$argc){
        global $a, $db;
@@ -52,6 +53,8 @@ function queue_run(&$argv, &$argc){
                $queue_id = 0;
 
        $deadguys = array();
+       $deadservers = array();
+       $serverlist = array();
 
        logger('queue: start');
 
@@ -95,7 +98,7 @@ function queue_run(&$argv, &$argc){
                // For the first 12 hours we'll try to deliver every 15 minutes
                // After that, we'll only attempt delivery once per hour.
 
-               $r = q("SELECT `id` FROM `queue` WHERE (( `created` > UTC_TIMESTAMP() - INTERVAL 12 HOUR && `last` < UTC_TIMESTAMP() - INTERVAL 15 MINUTE ) OR ( `last` < UTC_TIMESTAMP() - INTERVAL 1 HOUR ))");
+               $r = q("SELECT `id` FROM `queue` WHERE ((`created` > UTC_TIMESTAMP() - INTERVAL 12 HOUR && `last` < UTC_TIMESTAMP() - INTERVAL 15 MINUTE) OR (`last` < UTC_TIMESTAMP() - INTERVAL 1 HOUR)) ORDER BY `cid`, `created`");
        }
        if(! $r){
                return;
@@ -116,7 +119,7 @@ function queue_run(&$argv, &$argc){
                // so check again if this entry still needs processing
 
                if($queue_id) {
-                       $qi = q("select * from queue where `id` = %d limit 1",
+                       $qi = q("SELECT * FROM `queue` WHERE `id` = %d LIMIT 1",
                                intval($queue_id)
                        );
                }
@@ -142,8 +145,18 @@ function queue_run(&$argv, &$argc){
                        continue;
                }
 
-               if (!poco_reachable($c[0]['url'])) {
-                       logger('queue: skipping probably dead url: ' . $c[0]['url']);
+               $server = poco_detect_server($c[0]['url']);
+
+               if (($server != "") AND !in_array($server, $serverlist)) {
+                       logger("Check server ".$server." (".$c[0]["network"].")");
+                       if (!poco_check_server($server, $c[0]["network"], true))
+                               $deadservers[] = $server;
+
+                       $serverlist[] = $server;
+               }
+
+               if (($server != "") AND in_array($server, $deadservers)) {
+                       logger('queue: skipping known dead server: '.$server);
                        update_queue_time($q_item['id']);
                        continue;
                }
@@ -166,37 +179,39 @@ function queue_run(&$argv, &$argc){
 
                switch($contact['network']) {
                        case NETWORK_DFRN:
-                               logger('queue: dfrndelivery: item ' . $q_item['id'] . ' for ' . $contact['name']);
-                               $deliver_status = dfrn_deliver($owner,$contact,$data);
+                               logger('queue: dfrndelivery: item '.$q_item['id'].' for '.$contact['name'].' <'.$contact['url'].'>');
+                               $deliver_status = dfrn::deliver($owner,$contact,$data);
 
                                if($deliver_status == (-1)) {
                                        update_queue_time($q_item['id']);
                                        $deadguys[] = $contact['notify'];
-                               }
-                               else {
+                               } else
                                        remove_queue_item($q_item['id']);
-                               }
+
                                break;
                        case NETWORK_OSTATUS:
                                if($contact['notify']) {
-                                       logger('queue: slapdelivery: item ' . $q_item['id'] . ' for ' . $contact['name']);
+                                       logger('queue: slapdelivery: item '.$q_item['id'].' for '.$contact['name'].' <'.$contact['url'].'>');
                                        $deliver_status = slapper($owner,$contact['notify'],$data);
 
-                                       if($deliver_status == (-1))
+                                       if($deliver_status == (-1)) {
                                                update_queue_time($q_item['id']);
-                                       else
+                                               $deadguys[] = $contact['notify'];
+                                       } else
                                                remove_queue_item($q_item['id']);
                                }
                                break;
                        case NETWORK_DIASPORA:
                                if($contact['notify']) {
-                                       logger('queue: diaspora_delivery: item ' . $q_item['id'] . ' for ' . $contact['name']);
+                                       logger('queue: diaspora_delivery: item '.$q_item['id'].' for '.$contact['name'].' <'.$contact['url'].'>');
                                        $deliver_status = diaspora_transmit($owner,$contact,$data,$public,true);
 
-                                       if($deliver_status == (-1))
+                                       if($deliver_status == (-1)) {
                                                update_queue_time($q_item['id']);
-                                       else
+                                               $deadguys[] = $contact['notify'];
+                                       } else
                                                remove_queue_item($q_item['id']);
+
                                }
                                break;
 
@@ -212,6 +227,7 @@ function queue_run(&$argv, &$argc){
                                break;
 
                }
+               logger('Deliver status '.$deliver_status.' for item '.$q_item['id'].' to '.$contact['name'].' <'.$contact['url'].'>');
        }
 
        return;
diff --git a/include/remoteupdate.php b/include/remoteupdate.php
deleted file mode 100644 (file)
index 9effc9b..0000000
+++ /dev/null
@@ -1,261 +0,0 @@
-<?php
-/* update friendica */
-define('APIBASE', 'http://github.com/api/v2/');
-define('F9KREPO', 'friendica/friendica');
-
-$up_totalfiles = 0;
-$up_countfiles = 0;
-$up_lastp = -1;
-
-function checkUpdate(){
-       $r = fetch_url( APIBASE."json/repos/show/".F9KREPO."/tags" );
-       $tags = json_decode($r);
-       
-       $tag = 0.0;
-       foreach ($tags->tags as $i=>$v){
-               $i = (float)$i;
-               if ($i>$tag) $tag=$i;
-       }
-       
-       if ($tag==0.0) return false;
-       $f = fetch_url("https://raw.github.com/".F9KREPO."/".$tag."/boot.php","r");
-       preg_match("|'FRIENDICA_VERSION', *'([^']*)'|", $f, $m);
-       $version =  $m[1];
-       
-       $lv = explode(".", FRIENDICA_VERSION);
-       $rv = explode(".",$version);
-       foreach($lv as $i=>$v){
-               if ((int)$lv[$i] < (int)$rv[$i]) {
-                       return array($tag, $version, "https://github.com/friendica/friendica/zipball/".$tag);
-                       break;
-               }
-       }
-       return false;
-}
-function canWeWrite(){
-       $bd = dirname(dirname(__file__));
-       return is_writable( $bd."/boot.php" );
-}
-
-function out($txt){ echo "§".$txt."§"; ob_end_flush(); flush();}
-
-function up_count($path){
-    $file_count = 0;
-    $dir_handle = opendir($path);
-    if (!$dir_handle) return -1;
-    while ($file = readdir($dir_handle)) {
-        if ($file == '.' || $file == '..') continue;
-               $file_count++;
-        
-        if (is_dir($path . $file)){      
-            $file_count += up_count($path . $file . DIRECTORY_SEPARATOR);
-        }
-        
-    }
-    closedir($dir_handle);
-    return $file_count;
-}
-
-
-
-function up_unzip($file, $folder="/tmp"){
-       $folder.="/";
-       $zip = zip_open($file);
-       if ($zip) {
-         while ($zip_entry = zip_read($zip)) {
-               $zip_entry_name = zip_entry_name($zip_entry);
-               if (substr($zip_entry_name,strlen($zip_entry_name)-1,1)=="/"){
-                       mkdir($folder.$zip_entry_name,0777, true);
-               } else {
-                       $fp = fopen($folder.$zip_entry_name, "w");
-                       if (zip_entry_open($zip, $zip_entry, "r")) {
-                         $buf = zip_entry_read($zip_entry, zip_entry_filesize($zip_entry));
-                         fwrite($fp,"$buf");
-                         zip_entry_close($zip_entry);
-                         fclose($fp);
-                       }
-               }
-         }
-         zip_close($zip);
-       }       
-}
-
-/**
- * Walk recoursively in a folder and call a callback function on every
- * dir entry.
- * args:
- *     $dir            string          base dir to walk
- *     $callback       function        callback function
- *  $sort              int                     0: ascending, 1: descending
- *     $cb_argv        any                     extra value passed to callback
- * 
- * callback signature:
- * function name($fn, $dir [, $argv])
- *     $fn             string          full dir entry name
- *     $dir    string          start dir path
- *  $argv      any                     user value to callback
- * 
- */
-function up_walktree($dir, $callback=Null, $sort=0, $cb_argv=Null ,  $startdir=Null){
-       if (is_null($callback)) return;
-       if (is_null($startdir)) $startdir = $dir;
-       $res = scandir($dir, $sort);
-       foreach($res as $i=>$v){
-               if ($v!="." && $v!=".."){
-                       $fn = $dir."/".$v;
-                       if ($sort==0) $callback($fn, $startdir, $cb_argv);      
-                       if (is_dir($fn)) up_walktree($fn, $callback, $sort, $cb_argv, $startdir);
-                       if ($sort==1) $callback($fn, $startdir, $cb_argv);      
-               }
-       }
-       
-}
-
-function up_copy($fn, $dir){
-       global $up_countfiles, $up_totalfiles, $up_lastp;
-       $up_countfiles++; $prc=(int)(((float)$up_countfiles/(float)$up_totalfiles)*100);
-       
-       if (strpos($fn, ".gitignore")>-1 || strpos($fn, ".htaccess")>-1) return;  
-       $ddest = dirname(dirname(__file__));
-       $fd = str_replace($dir, $ddest, $fn);
-       
-       if (is_dir($fn) && !is_dir($fd)) {
-               $re=mkdir($fd,0777,true);
-       }
-       if (!is_dir($fn)){
-               $re=copy($fn, $fd);
-       }
-       
-       if ($re===false) { 
-               out("ERROR. Abort."); 
-               killme();
-       }
-       out("copy@Copy@$prc%");
-}
-
-function up_ftp($fn, $dir, $argv){
-       global $up_countfiles, $up_totalfiles, $up_lastp;
-       $up_countfiles++; $prc=(int)(((float)$up_countfiles/(float)$up_totalfiles)*100);
-
-       if (strpos($fn, ".gitignore")>-1 || strpos($fn, ".htaccess")>-1) return;
-       
-       list($ddest, $conn_id) = $argv;
-       $l = strlen($ddest)-1;
-       if (substr($ddest,$l,1)=="/") $ddest = substr($ddest,0,$l);
-       $fd = str_replace($dir, $ddest, $fn);
-       
-       if (is_dir($fn)){
-               if (ftp_nlist($conn_id, $fd)===false) { 
-                       $ret = ftp_mkdir($conn_id, $fd);
-               } else {
-                       $ret=true;
-               }
-       } else {
-               $ret = ftp_put($conn_id, $fd, $fn, FTP_BINARY); 
-       }
-       if (!$ret) { 
-               out("ERROR. Abort."); 
-               killme();
-       } 
-       out("copy@Copy@$prc%");
-}
-
-function up_rm($fn, $dir){
-       if (is_dir($fn)){
-               rmdir($fn);
-       } else {
-               unlink($fn);
-       }
-}
-
-function up_dlfile($url, $file) {
-       $in = fopen ($url, "r");
-       $out = fopen ($file, "w");
-       
-       $fs = filesize($url);
-       
-
-       if (!$in || !$out) return false;
-       
-       $s=0; $count=0;
-       while (!feof ($in)) {
-               $line = fgets ($in, 1024);
-               fwrite( $out, $line);
-               
-               $count++; $s += strlen($line);
-               if ($count==50){
-                       $count=0;
-                       $sp=$s/1024.0; $ex="Kb";
-                       if ($sp>1024) { $sp=$sp/1024; $ex="Mb"; }
-                       if ($sp>1024) { $sp=$sp/1024; $ex="Gb"; }
-                       $sp = ((int)($sp*100))/100;
-                       out("dwl@Download@".$sp.$ex);
-               }
-       }
-       fclose($in);
-       return true;
-}
-
-function doUpdate($remotefile, $ftpdata=false){
-       global $up_totalfiles;
-       
-       
-       $localtmpfile = tempnam("/tmp", "fk");
-       out("dwl@Download@starting...");
-       $rt= up_dlfile($remotefile, $localtmpfile);
-       if ($rt==false || filesize($localtmpfile)==0){
-               out("dwl@Download@ERROR.");
-               unlink($localtmpfile);
-               return;
-       }
-       out("dwl@Download@Ok.");
-       
-       out("unzip@Unzip@");
-       $tmpdirname = $localfile."ex";
-       mkdir($tmpdirname);
-       up_unzip($localtmpfile, $tmpdirname);
-       $basedir = glob($tmpdirname."/*"); $basedir=$basedir[0];
-       out ("unzip@Unzip@Ok.");
-       
-       $up_totalfiles = up_count($basedir."/");
-       
-       if (canWeWrite()){
-               out("copy@Copy@");
-               up_walktree($basedir, 'up_copy');
-       }
-       if ($ftpdata!==false && is_array($ftpdata) && $ftpdata['ftphost']!="" ){
-               out("ftpcon@Connect to FTP@");
-               $conn_id = ftp_connect($ftpdata['ftphost']);
-               $login_result = ftp_login($conn_id, $ftpdata['ftpuser'], $ftpdata['ftppwd']);
-               
-               if ((!$conn_id) || (!$login_result)) { 
-                       out("ftpcon@Connect to FTP@FAILED");
-                       up_clean($tmpdirname, $localtmpfile); 
-                       return;
-               } else {
-                       out("ftpcon@Connect to FTP@Ok.");
-               }
-               out("copy@Copy@");
-               up_walktree($basedir, 'up_ftp', 0, array( $ftpdata['ftppath'], $conn_id));
-               ftp_close($conn_id);
-       }
-
-       up_clean($tmpdirname, $localtmpfile); 
-       
-}
-
-function up_clean($tmpdirname, $localtmpfile){
-       out("clean@Clean up@");
-       unlink($localtmpfile);
-       up_walktree($tmpdirname, 'up_rm', 1);
-       rmdir($tmpdirname);
-       out("clean@Clean up@Ok.");
-}
index 89b09515ed0c73776a683c15064de44263950ba0..3b8e9140f8eb476b36d30f553eedb81679d03dba 100644 (file)
@@ -139,15 +139,16 @@ function poco_load($cid,$uid = 0,$zcid = 0,$url = null) {
                poco_check($profile_url, $name, $network, $profile_photo, $about, $location, $gender, $keywords, $connect_url, $updated, $generation, $cid, $uid, $zcid);
 
                // Update the Friendica contacts. Diaspora is doing it via a message. (See include/diaspora.php)
-               if (($location != "") OR ($about != "") OR ($keywords != "") OR ($gender != ""))
-                       q("UPDATE `contact` SET `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s'
-                               WHERE `nurl` = '%s' AND NOT `self` AND `network` = '%s'",
-                               dbesc($location),
-                               dbesc($about),
-                               dbesc($keywords),
-                               dbesc($gender),
-                               dbesc(normalise_link($profile_url)),
-                               dbesc(NETWORK_DFRN));
+               // Deactivated because we now update Friendica contacts in dfrn.php
+               //if (($location != "") OR ($about != "") OR ($keywords != "") OR ($gender != ""))
+               //      q("UPDATE `contact` SET `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s'
+               //              WHERE `nurl` = '%s' AND NOT `self` AND `network` = '%s'",
+               //              dbesc($location),
+               //              dbesc($about),
+               //              dbesc($keywords),
+               //              dbesc($gender),
+               //              dbesc(normalise_link($profile_url)),
+               //              dbesc(NETWORK_DFRN));
        }
        logger("poco_load: loaded $total entries",LOGGER_DEBUG);
 
@@ -227,6 +228,8 @@ function poco_check($profile_url, $name, $network, $profile_photo, $about, $loca
                $server_url = $x[0]["server_url"];
                $nick = $x[0]["nick"];
                $addr = $x[0]["addr"];
+               $alias =  $x[0]["alias"];
+               $notify =  $x[0]["notify"];
        } else {
                $created = "0000-00-00 00:00:00";
                $server_url = "";
@@ -234,6 +237,8 @@ function poco_check($profile_url, $name, $network, $profile_photo, $about, $loca
                $urlparts = parse_url($profile_url);
                $nick = end(explode("/", $urlparts["path"]));
                $addr = "";
+               $alias = "";
+               $notify = "";
        }
 
        if ((($network == "") OR ($name == "") OR ($addr == "") OR ($profile_photo == "") OR ($server_url == "") OR $alternate)
@@ -246,6 +251,8 @@ function poco_check($profile_url, $name, $network, $profile_photo, $about, $loca
                $name = $data["name"];
                $nick = $data["nick"];
                $addr = $data["addr"];
+               $alias = $data["alias"];
+               $notify = $data["notify"];
                $profile_url = $data["url"];
                $profile_photo = $data["photo"];
                $server_url = $data["baseurl"];
@@ -283,76 +290,25 @@ function poco_check($profile_url, $name, $network, $profile_photo, $about, $loca
 
        poco_check_server($server_url, $network);
 
-       if(count($x)) {
-               $gcid = $x[0]['id'];
-
-               if (($location == "") AND ($x[0]['location'] != ""))
-                       $location = $x[0]['location'];
-
-               if (($about == "") AND ($x[0]['about'] != ""))
-                       $about = $x[0]['about'];
-
-               if (($gender == "") AND ($x[0]['gender'] != ""))
-                       $gender = $x[0]['gender'];
-
-               if (($keywords == "") AND ($x[0]['keywords'] != ""))
-                       $keywords = $x[0]['keywords'];
-
-               if (($addr == "") AND ($x[0]['addr'] != ""))
-                       $addr = $x[0]['addr'];
-
-               if (($generation == 0) AND ($x[0]['generation'] > 0))
-                       $generation = $x[0]['generation'];
-
-               if($x[0]['name'] != $name || $x[0]['photo'] != $profile_photo || $x[0]['updated'] < $updated) {
-                       q("UPDATE `gcontact` SET `name` = '%s', `addr` = '%s', `network` = '%s', `photo` = '%s', `connect` = '%s', `url` = '%s', `server_url` = '%s',
-                               `updated` = '%s', `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s', `generation` = %d
-                               WHERE (`generation` >= %d OR `generation` = 0) AND `nurl` = '%s'",
-                               dbesc($name),
-                               dbesc($addr),
-                               dbesc($network),
-                               dbesc($profile_photo),
-                               dbesc($connect_url),
-                               dbesc($profile_url),
-                               dbesc($server_url),
-                               dbesc($updated),
-                               dbesc($location),
-                               dbesc($about),
-                               dbesc($keywords),
-                               dbesc($gender),
-                               intval($generation),
-                               intval($generation),
-                               dbesc(normalise_link($profile_url))
-                       );
-               }
-       } else {
-               q("INSERT INTO `gcontact` (`name`, `nick`, `addr`, `network`, `url`, `nurl`, `photo`, `connect`, `server_url`, `created`, `updated`, `location`, `about`, `keywords`, `gender`, `generation`)
-                       VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d)",
-                       dbesc($name),
-                       dbesc($nick),
-                       dbesc($addr),
-                       dbesc($network),
-                       dbesc($profile_url),
-                       dbesc(normalise_link($profile_url)),
-                       dbesc($profile_photo),
-                       dbesc($connect_url),
-                       dbesc($server_url),
-                       dbesc(datetime_convert()),
-                       dbesc($updated),
-                       dbesc($location),
-                       dbesc($about),
-                       dbesc($keywords),
-                       dbesc($gender),
-                       intval($generation)
-               );
-               $x = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1",
-                       dbesc(normalise_link($profile_url))
-               );
-               if(count($x))
-                       $gcid = $x[0]['id'];
-       }
-
-       if(! $gcid)
+       $gcontact = array("url" => $profile_url,
+                       "addr" => $addr,
+                       "alias" => $alias,
+                       "name" => $name,
+                       "network" => $network,
+                       "photo" => $profile_photo,
+                       "about" => $about,
+                       "location" => $location,
+                       "gender" => $gender,
+                       "keywords" => $keywords,
+                       "server_url" => $server_url,
+                       "connect" => $connect_url,
+                       "notify" => $notify,
+                       "updated" => $updated,
+                       "generation" => $generation);
+
+       $gcid = update_gcontact($gcontact);
+
+       if(!$gcid)
                return $gcid;
 
        $r = q("SELECT * FROM `glink` WHERE `cid` = %d AND `uid` = %d AND `gcid` = %d AND `zcid` = %d LIMIT 1",
@@ -379,13 +335,6 @@ function poco_check($profile_url, $name, $network, $profile_photo, $about, $loca
                );
        }
 
-       // For unknown reasons there are sometimes duplicates
-       q("DELETE FROM `gcontact` WHERE `nurl` = '%s' AND `id` != %d AND
-               NOT EXISTS (SELECT `gcid` FROM `glink` WHERE `gcid` = `gcontact`.`id`)",
-               dbesc(normalise_link($profile_url)),
-               intval($gcid)
-       );
-
        return $gcid;
 }
 
@@ -698,6 +647,10 @@ function poco_to_boolean($val) {
 
 function poco_check_server($server_url, $network = "", $force = false) {
 
+       // Unify the server address
+       $server_url = trim($server_url, "/");
+       $server_url = str_replace("/index.php", "", $server_url);
+
        if ($server_url == "")
                return false;
 
@@ -769,17 +722,23 @@ function poco_check_server($server_url, $network = "", $force = false) {
                // Test for Diaspora
                $serverret = z_fetch_url($server_url);
 
-               $lines = explode("\n",$serverret["header"]);
-               if(count($lines))
-                       foreach($lines as $line) {
-                               $line = trim($line);
-                               if(stristr($line,'X-Diaspora-Version:')) {
-                                       $platform = "Diaspora";
-                                       $version = trim(str_replace("X-Diaspora-Version:", "", $line));
-                                       $version = trim(str_replace("x-diaspora-version:", "", $version));
-                                       $network = NETWORK_DIASPORA;
+               if (!$serverret["success"] OR ($serverret["body"] == ""))
+                       $failure = true;
+               else {
+                       $lines = explode("\n",$serverret["header"]);
+                       if(count($lines))
+                               foreach($lines as $line) {
+                                       $line = trim($line);
+                                       if(stristr($line,'X-Diaspora-Version:')) {
+                                               $platform = "Diaspora";
+                                               $version = trim(str_replace("X-Diaspora-Version:", "", $line));
+                                               $version = trim(str_replace("x-diaspora-version:", "", $version));
+                                               $network = NETWORK_DIASPORA;
+                                               $versionparts = explode("-", $version);
+                                               $version = $versionparts[0];
+                                       }
                                }
-                       }
+               }
        }
 
        if (!$failure) {
@@ -1290,18 +1249,30 @@ function poco_discover_federation() {
                        return;
        }
 
+       // Discover Friendica, Hubzilla and Diaspora servers
        $serverdata = fetch_url("http://the-federation.info/pods.json");
 
-       if (!$serverdata)
-               return;
+       if ($serverdata) {
+               $servers = json_decode($serverdata);
 
-       $servers = json_decode($serverdata);
+               foreach($servers->pods AS $server)
+                       poco_check_server("https://".$server->host);
+       }
 
-       foreach($servers->pods AS $server)
-               poco_check_server("https://".$server->host);
+       // Discover GNU Social Servers
+       if (!get_config('system','ostatus_disabled')) {
+               $serverdata = "http://gstools.org/api/get_open_instances/";
 
-       set_config('poco','last_federation_discovery', time());
+               $result = z_fetch_url($serverdata);
+               if ($result["success"]) {
+                       $servers = json_decode($result["body"]);
 
+                       foreach($servers->data AS $server)
+                               poco_check_server($server->instance_address);
+               }
+       }
+
+       set_config('poco','last_federation_discovery', time());
 }
 
 function poco_discover($complete = false) {
@@ -1481,4 +1452,214 @@ function poco_discover_server($data, $default_generation = 0) {
        }
        return $success;
 }
+
+/**
+ * @brief Fetch the gcontact id, add an entry if not existed
+ *
+ * @param arr $contact contact array
+ * @return bool|int Returns false if not found, integer if contact was found
+ */
+function get_gcontact_id($contact) {
+
+       $gcontact_id = 0;
+
+       if ($contact["network"] == NETWORK_STATUSNET)
+               $contact["network"] = NETWORK_OSTATUS;
+
+       $r = q("SELECT `id` FROM `gcontact` WHERE `nurl` = '%s' ORDER BY `id` LIMIT 2",
+               dbesc(normalise_link($contact["url"])));
+
+       if ($r)
+               $gcontact_id = $r[0]["id"];
+       else {
+               q("INSERT INTO `gcontact` (`name`, `nick`, `addr` , `network`, `url`, `nurl`, `photo`, `created`, `updated`, `location`, `about`, `generation`)
+                       VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d)",
+                       dbesc($contact["name"]),
+                       dbesc($contact["nick"]),
+                       dbesc($contact["addr"]),
+                       dbesc($contact["network"]),
+                       dbesc($contact["url"]),
+                       dbesc(normalise_link($contact["url"])),
+                       dbesc($contact["photo"]),
+                       dbesc(datetime_convert()),
+                       dbesc(datetime_convert()),
+                       dbesc($contact["location"]),
+                       dbesc($contact["about"]),
+                       intval($contact["generation"])
+               );
+
+               $r = q("SELECT `id` FROM `gcontact` WHERE `nurl` = '%s' ORDER BY `id` LIMIT 2",
+                       dbesc(normalise_link($contact["url"])));
+
+               if ($r)
+                       $gcontact_id = $r[0]["id"];
+       }
+
+       if ((count($r) > 1) AND ($gcontact_id > 0) AND ($contact["url"] != ""))
+        q("DELETE FROM `gcontact` WHERE `nurl` = '%s' AND `id` != %d",
+               dbesc(normalise_link($contact["url"])),
+               intval($gcontact_id));
+
+       return $gcontact_id;
+}
+
+/**
+ * @brief Updates the gcontact table from a given array
+ *
+ * @param arr $contact contact array
+ * @return bool|int Returns false if not found, integer if contact was found
+ */
+function update_gcontact($contact) {
+
+       /// @todo update contact table as well
+
+       $gcontact_id = get_gcontact_id($contact);
+
+       if (!$gcontact_id)
+               return false;
+
+       $r = q("SELECT `name`, `nick`, `photo`, `location`, `about`, `addr`, `generation`, `birthday`, `gender`, `keywords`,
+                       `hide`, `nsfw`, `network`, `alias`, `notify`, `server_url`, `connect`, `updated`, `url`
+               FROM `gcontact` WHERE `id` = %d LIMIT 1",
+               intval($gcontact_id));
+
+       // Get all field names
+       $fields = array();
+       foreach ($r[0] AS $field => $data)
+               $fields[$field] = $data;
+
+       unset($fields["url"]);
+       unset($fields["updated"]);
+
+       // assign all unassigned fields from the database entry
+       foreach ($fields AS $field => $data)
+               if (!isset($contact[$field]))
+                       $contact[$field] = $r[0][$field];
+
+       if ($contact["network"] == NETWORK_STATUSNET)
+               $contact["network"] = NETWORK_OSTATUS;
+
+       if (!isset($contact["updated"]))
+               $contact["updated"] = datetime_convert();
+
+       // Check if any field changed
+       $update = false;
+       unset($fields["generation"]);
+
+       foreach ($fields AS $field => $data)
+               if ($contact[$field] != $r[0][$field])
+                       $update = true;
+
+       if ($contact["generation"] < $r[0]["generation"])
+               $update = true;
+
+       if ($update) {
+               q("UPDATE `gcontact` SET `photo` = '%s', `name` = '%s', `nick` = '%s', `addr` = '%s', `network` = '%s',
+                                       `birthday` = '%s', `gender` = '%s', `keywords` = '%s', `hide` = %d, `nsfw` = %d,
+                                       `alias` = '%s', `notify` = '%s', `url` = '%s',
+                                       `location` = '%s', `about` = '%s', `generation` = %d, `updated` = '%s',
+                                       `server_url` = '%s', `connect` = '%s'
+                               WHERE `nurl` = '%s' AND (`generation` = 0 OR `generation` >= %d)",
+                       dbesc($contact["photo"]), dbesc($contact["name"]), dbesc($contact["nick"]),
+                       dbesc($contact["addr"]), dbesc($contact["network"]), dbesc($contact["birthday"]),
+                       dbesc($contact["gender"]), dbesc($contact["keywords"]), intval($contact["hide"]),
+                       intval($contact["nsfw"]), dbesc($contact["alias"]), dbesc($contact["notify"]),
+                       dbesc($contact["url"]), dbesc($contact["location"]), dbesc($contact["about"]),
+                       intval($contact["generation"]), dbesc($contact["updated"]),
+                       dbesc($contact["server_url"]), dbesc($contact["connect"]),
+                       dbesc(normalise_link($contact["url"])), intval($contact["generation"]));
+       }
+
+       return $gcontact_id;
+}
+
+/**
+ * @brief Updates the gcontact entry from probe
+ *
+ * @param str $url profile link
+ */
+function update_gcontact_from_probe($url) {
+       $data = probe_url($url);
+
+       if ($data["network"] != NETWORK_PHANTOM)
+               update_gcontact($data);
+}
+
+/**
+ * @brief Fetches users of given GNU Social server
+ *
+ * If the "Statistics" plugin is enabled (See http://gstools.org/ for details) we query user data with this.
+ *
+ * @param str $server Server address
+ */
+function gs_fetch_users($server) {
+
+       logger("Fetching users from GNU Social server ".$server, LOGGER_DEBUG);
+
+       $a = get_app();
+
+       $url = $server."/main/statistics";
+
+       $result = z_fetch_url($url);
+       if (!$result["success"])
+               return false;
+
+       $statistics = json_decode($result["body"]);
+
+       if (is_object($statistics->config)) {
+               if ($statistics->config->instance_with_ssl)
+                       $server = "https://";
+               else
+                       $server = "http://";
+
+               $server .= $statistics->config->instance_address;
+
+               $hostname = $statistics->config->instance_address;
+       } else {
+               if ($statistics->instance_with_ssl)
+                       $server = "https://";
+               else
+                       $server = "http://";
+
+               $server .= $statistics->instance_address;
+
+               $hostname = $statistics->instance_address;
+       }
+
+       if (is_object($statistics->users))
+               foreach ($statistics->users AS $nick => $user) {
+                       $profile_url = $server."/".$user->nickname;
+
+                       $contact = array("url" => $profile_url,
+                                       "name" => $user->fullname,
+                                       "addr" => $user->nickname."@".$hostname,
+                                       "nick" => $user->nickname,
+                                       "about" => $user->bio,
+                                       "network" => NETWORK_OSTATUS,
+                                       "photo" => $a->get_baseurl()."/images/person-175.jpg");
+                       get_gcontact_id($contact);
+               }
+}
+
+/**
+ * @brief Asking GNU Social server on a regular base for their user data
+ *
+ */
+function gs_discover() {
+
+       $requery_days = intval(get_config("system", "poco_requery_days"));
+
+       $last_update = date("c", time() - (60 * 60 * 24 * $requery_days));
+
+       $r = q("SELECT `nurl`, `url` FROM `gserver` WHERE `last_contact` >= `last_failure` AND `network` = '%s' AND `last_poco_query` < '%s' ORDER BY RAND() LIMIT 5",
+               dbesc(NETWORK_OSTATUS), dbesc($last_update));
+
+       if (!$r)
+               return;
+
+       foreach ($r AS $server) {
+               gs_fetch_users($server["url"]);
+               q("UPDATE `gserver` SET `last_poco_query` = '%s' WHERE `nurl` = '%s'", dbesc(datetime_convert()), dbesc($server["nurl"]));
+       }
+}
 ?>
index 1b03c39d26775b08fc4c30231bbab7bb956c7d59..c7681a4d58cb2485a8e34ac623d89440a4a36404 100644 (file)
@@ -20,10 +20,10 @@ function replace_macros($s,$r) {
        $stamp1 = microtime(true);
 
        $a = get_app();
-       
+
        // pass $baseurl to all templates
-       $r['$baseurl'] = z_root();
-       
+       $r['$baseurl'] = $a->get_baseurl();
+
 
        $t = $a->template_engine();
        try {
@@ -829,35 +829,6 @@ function qp($s) {
 return str_replace ("%","=",rawurlencode($s));
 }}
 
-
-
-if(! function_exists('get_mentions')) {
-/**
- * @param array $item
- * @return string html for mentions #FIXME: remove html
- */
-function get_mentions($item) {
-       $o = '';
-       if(! strlen($item['tag']))
-               return $o;
-
-       $arr = explode(',',$item['tag']);
-       foreach($arr as $x) {
-               $matches = null;
-               if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) {
-                       $o .= "\t\t" . '<link rel="ostatus:attention" href="' . $matches[1] . '" />' . "\r\n";
-                       $o .= "\t\t" . '<link rel="mentioned" href="' . $matches[1] . '" />' . "\r\n";
-               }
-       }
-
-       if (!$item['private']) {
-                       $o .= "\t\t".'<link rel="ostatus:attention" href="http://activityschema.org/collection/public"/>'."\r\n";
-                       $o .= "\t\t".'<link rel="mentioned" href="http://activityschema.org/collection/public"/>'."\r\n";
-       }
-
-       return $o;
-}}
-
 if(! function_exists('contact_block')) {
 /**
  * Get html for contact block.
@@ -895,9 +866,9 @@ function contact_block() {
                $micropro = Null;
 
        } else {
-               $r = q("SELECT * FROM `contact`
-                               WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0
-                                       AND `hidden` = 0 AND `archive` = 0
+               $r = q("SELECT `id`, `uid`, `addr`, `url`, `name`, `micro`, `network` FROM `contact`
+                               WHERE `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `pending`
+                                       AND NOT `hidden` AND NOT `archive`
                                AND `network` IN ('%s', '%s', '%s') ORDER BY RAND() LIMIT %d",
                                intval($a->profile['uid']),
                                dbesc(NETWORK_DFRN),
@@ -1415,7 +1386,14 @@ function prepare_body(&$item,$attach = false, $preview = false) {
        $item['hashtags'] = $hashtags;
        $item['mentions'] = $mentions;
 
-       put_item_in_cache($item, true);
+       // Update the cached values if there is no "zrl=..." on the links
+       $update = (!local_user() and !remote_user() and ($item["uid"] == 0));
+
+       // Or update it if the current viewer is the intented viewer
+       if (($item["uid"] == local_user()) AND ($item["uid"] != 0))
+               $update = true;
+
+       put_item_in_cache($item, $update);
        $s = $item["rendered-html"];
 
        $prep_arr = array('item' => $item, 'html' => $s, 'preview' => $preview);
@@ -1526,7 +1504,7 @@ function prepare_body(&$item,$attach = false, $preview = false) {
 
                $pos = strpos($s, $spoilersearch);
                $rnd = random_string(8);
-               $spoilerreplace = '<br /> <span id="spoiler-wrap-'.$rnd.'" style="white-space:nowrap;" class="fakelink" onclick="openClose(\'spoiler-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
+               $spoilerreplace = '<br /> <span id="spoiler-wrap-'.$rnd.'" class="spoiler-wrap fakelink" onclick="openClose(\'spoiler-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
                                        '<blockquote class="spoiler" id="spoiler-'.$rnd.'" style="display: none;">';
                $s = substr($s, 0, $pos).$spoilerreplace.substr($s, $pos+strlen($spoilersearch));
        }
@@ -1538,7 +1516,7 @@ function prepare_body(&$item,$attach = false, $preview = false) {
 
                $pos = strpos($s, $authorsearch);
                $rnd = random_string(8);
-               $authorreplace = '<br /> <span id="author-wrap-'.$rnd.'" style="white-space:nowrap;" class="fakelink" onclick="openClose(\'author-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
+               $authorreplace = '<br /> <span id="author-wrap-'.$rnd.'" class="author-wrap fakelink" onclick="openClose(\'author-'.$rnd.'\');">'.sprintf(t('Click to open/close')).'</span>'.
                                        '<blockquote class="author" id="author-'.$rnd.'" style="display: block;">';
                $s = substr($s, 0, $pos).$authorreplace.substr($s, $pos+strlen($authorsearch));
        }
@@ -1650,54 +1628,6 @@ function get_cats_and_terms($item) {
        return array($categories, $folders);
 }
 
-
-
-if(! function_exists('feed_hublinks')) {
-/**
- * return atom link elements for all of our hubs
- * @return string hub link xml elements
- */
-function feed_hublinks() {
-       $a = get_app();
-       $hub = get_config('system','huburl');
-
-       $hubxml = '';
-       if(strlen($hub)) {
-               $hubs = explode(',', $hub);
-               if(count($hubs)) {
-                       foreach($hubs as $h) {
-                               $h = trim($h);
-                               if(! strlen($h))
-                                       continue;
-                               if ($h === '[internal]')
-                                       $h = z_root() . '/pubsubhubbub';
-                               $hubxml .= '<link rel="hub" href="' . xmlify($h) . '" />' . "\n" ;
-                       }
-               }
-       }
-       return $hubxml;
-}}
-
-
-if(! function_exists('feed_salmonlinks')) {
-/**
- * return atom link elements for salmon endpoints
- * @param string $nick user nickname
- * @return string salmon link xml elements
- */
-function feed_salmonlinks($nick) {
-
-       $a = get_app();
-
-       $salmon  = '<link rel="salmon" href="' . xmlify(z_root() . '/salmon/' . $nick) . '" />' . "\n" ;
-
-       // old style links that status.net still needed as of 12/2010
-
-       $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-replies" href="' . xmlify(z_root() . '/salmon/' . $nick) . '" />' . "\n" ;
-       $salmon .= '  <link rel="http://salmon-protocol.org/ns/salmon-mention" href="' . xmlify(z_root() . '/salmon/' . $nick) . '" />' . "\n" ;
-       return $salmon;
-}}
-
 if(! function_exists('get_plink')) {
 /**
  * get private link for item
index c12f43bb81154c62256b9fac99a823d8ee144e84..0320eaa018e98b28a04179f79aae2b020bbd68da 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 function add_thread($itemid, $onlyshadow = false) {
-       $items = q("SELECT `uid`, `created`, `edited`, `commented`, `received`, `changed`, `wall`, `private`, `pubmail`, `moderated`, `visible`, `spam`, `starred`, `bookmark`, `contact-id`,
+       $items = q("SELECT `uid`, `created`, `edited`, `commented`, `received`, `changed`, `wall`, `private`, `pubmail`, `moderated`, `visible`, `spam`, `starred`, `bookmark`, `contact-id`, `gcontact-id`,
                        `deleted`, `origin`, `forum_mode`, `mention`, `network`  FROM `item` WHERE `id` = %d AND (`parent` = %d OR `parent` = 0) LIMIT 1", intval($itemid), intval($itemid));
 
        if (!$items)
@@ -66,6 +66,7 @@ function add_thread($itemid, $onlyshadow = false) {
 
                        unset($item[0]['id']);
                        $item[0]['uid'] = 0;
+                       $item[0]['origin'] = 0;
                        $item[0]['contact-id'] = get_contact($item[0]['author-link'], 0);
                        $public_shadow = item_store($item[0], false, false, true);
 
@@ -111,8 +112,8 @@ function update_thread_uri($itemuri, $uid) {
 }
 
 function update_thread($itemid, $setmention = false) {
-       $items = q("SELECT `uid`, `guid`, `title`, `body`, `created`, `edited`, `commented`, `received`, `changed`, `wall`, `private`, `pubmail`, `moderated`, `visible`, `spam`, `starred`, `bookmark`, `contact-id`,
-                       `deleted`, `origin`, `forum_mode`, `network`  FROM `item` WHERE `id` = %d AND (`parent` = %d OR `parent` = 0) LIMIT 1", intval($itemid), intval($itemid));
+       $items = q("SELECT `uid`, `guid`, `title`, `body`, `created`, `edited`, `commented`, `received`, `changed`, `wall`, `private`, `pubmail`, `moderated`, `visible`, `spam`, `starred`, `bookmark`, `contact-id`, `gcontact-id`,
+                       `deleted`, `origin`, `forum_mode`, `network`, `rendered-html`, `rendered-hash` FROM `item` WHERE `id` = %d AND (`parent` = %d OR `parent` = 0) LIMIT 1", intval($itemid), intval($itemid));
 
        if (!$items)
                return;
@@ -125,7 +126,7 @@ function update_thread($itemid, $setmention = false) {
        $sql = "";
 
        foreach ($item AS $field => $data)
-               if (!in_array($field, array("guid", "title", "body"))) {
+               if (!in_array($field, array("guid", "title", "body", "rendered-html", "rendered-hash"))) {
                        if ($sql != "")
                                $sql .= ", ";
 
@@ -142,9 +143,11 @@ function update_thread($itemid, $setmention = false) {
        if (!$items)
                return;
 
-       $result = q("UPDATE `item` SET `title` = '%s', `body` = '%s' WHERE `id` = %d",
+       $result = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `rendered-html` = '%s', `rendered-hash` = '%s' WHERE `id` = %d",
                        dbesc($item["title"]),
                        dbesc($item["body"]),
+                       dbesc($item["rendered-html"]),
+                       dbesc($item["rendered-hash"]),
                        intval($items[0]["id"])
                );
        logger("Updating public shadow for post ".$items[0]["id"]." - guid ".$item["guid"]." Result: ".print_r($result, true), LOGGER_DEBUG);
index 89ed058465db00a7e7e9177e65632d95b6042b85..8471735d016af1fd617d908a614c8ded21013db5 100644 (file)
--- a/index.php
+++ b/index.php
@@ -407,15 +407,6 @@ if(x($_SESSION,'sysmsg_info')) {
 call_hooks('page_end', $a->page['content']);
 
 
-/**
- *
- * Add a place for the pause/resume Ajax indicator
- *
- */
-
-$a->page['content'] .=  '<div id="pause"></div>';
-
-
 /**
  *
  * Add the navigation (menu) template
@@ -556,6 +547,7 @@ EOT;
 $page    = $a->page;
 $profile = $a->profile;
 
+header("X-Friendica-Version: ".FRIENDICA_VERSION);
 header("Content-type: text/html; charset=utf-8");
 
 
index 487ffafc770032578f1437bd84db44e9b39c77c8..3c96d134694146068d800641406238ed2dc5141f 100644 (file)
--- a/js/acl.js
+++ b/js/acl.js
@@ -1,49 +1,56 @@
-function ACL(backend_url, preset, automention){
-       that = this;
+function ACL(backend_url, preset, automention, is_mobile){
        
-       that.url = backend_url;
-       that.automention = automention;
+       this.url = backend_url;
+       this.automention = automention;
+       this.is_mobile = is_mobile;
        
-       that.kp_timer = null;
+       
+       this.kp_timer = null;
        
        if (preset==undefined) preset = [];
-       that.allow_cid = (preset[0] || []);
-       that.allow_gid = (preset[1] || []);
-       that.deny_cid  = (preset[2] || []);
-       that.deny_gid  = (preset[3] || []);
-       that.group_uids = [];
-       that.nw = 4; //items per row. should be calulated from #acl-list.width
+       this.allow_cid = (preset[0] || []);
+       this.allow_gid = (preset[1] || []);
+       this.deny_cid  = (preset[2] || []);
+       this.deny_gid  = (preset[3] || []);
+       this.group_uids = [];
+
+       if (this.is_mobile) {
+               this.nw = 1;
+       } else {
+               this.nw = 4;
+       }
        
-       that.list_content = $("#acl-list-content");
-       that.item_tpl = unescape($(".acl-list-item[rel=acl-template]").html());
-       that.showall = $("#acl-showall");
+       
+       this.list_content = $("#acl-list-content");
+       this.item_tpl = unescape($(".acl-list-item[rel=acl-template]").html());
+       this.showall = $("#acl-showall");
 
-       if (preset.length==0) that.showall.addClass("selected");
+       if (preset.length==0) this.showall.addClass("selected");
        
        /*events*/
-       that.showall.click(that.on_showall);
-       $(document).on("click", ".acl-button-show", that.on_button_show);
-       $(document).on("click", ".acl-button-hide", that.on_button_hide);
-       $("#acl-search").keypress(that.on_search);
-       $("#acl-wrapper").parents("form").submit(that.on_submit);
+       this.showall.click(this.on_showall.bind(this));
+       $(document).on("click", ".acl-button-show", this.on_button_show.bind(this));
+       $(document).on("click", ".acl-button-hide", this.on_button_hide.bind(this));
+       $("#acl-search").keypress(this.on_search.bind(this));
+       $("#acl-wrapper").parents("form").submit(this.on_submit.bind(this));
        
        /* add/remove mentions  */
-       that.element = $("#profile-jot-text");
-       that.htmlelm = that.element.get()[0];
+       this.element = $("#profile-jot-text");
+       this.htmlelm = this.element.get()[0];
        
        /* startup! */
-       that.get(0,100);
+       this.get(0,100);
 }
 
 ACL.prototype.remove_mention = function(id) {
-       if (!that.automention) return;
-       var nick = that.data[id].nick;
+       if (!this.automention) return;
+       var nick = this.data[id].nick;
        var searchText = "@"+nick+"+"+id+" ";
        if (tinyMCE.activeEditor===null) {
-               start = that.element.val().indexOf(searchText); 
+               start = this.element.val().indexOf(searchText); 
                if ( start<0) return;
                end = start+searchText.length;
-               that.element.setSelection(start,end).replaceSelectedText('').collapseSelection(false);
+               this.element.setSelection(start,end).replaceSelectedText('').collapseSelection(false);
        } else {
                start =  tinyMCE.activeEditor.getContent({format : 'raw'}).search( searchText );
                if ( start<0 ) return;
@@ -54,12 +61,12 @@ ACL.prototype.remove_mention = function(id) {
 }
 
 ACL.prototype.add_mention = function(id) {
-       if (!that.automention) return;
-       var nick = that.data[id].nick;
+       if (!this.automention) return;
+       var nick = this.data[id].nick;
        var searchText =  "@"+nick+"+"+id+" ";
        if (tinyMCE.activeEditor===null) {
-               if ( that.element.val().indexOf( searchText) >= 0 ) return;
-               that.element.val( searchText + that.element.val() );
+               if ( this.element.val().indexOf( searchText) >= 0 ) return;
+               this.element.val( searchText + this.element.val() );
        } else {
                if ( tinyMCE.activeEditor.getContent({format : 'raw'}).search(searchText) >= 0 ) return;
                tinyMCE.activeEditor.dom.add(tinyMCE.activeEditor.getBody(), 'dummy', {}, searchText);
@@ -68,46 +75,46 @@ ACL.prototype.add_mention = function(id) {
 
 ACL.prototype.on_submit = function(){
        aclfileds = $("#acl-fields").html("");
-       $(that.allow_gid).each(function(i,v){
+       $(this.allow_gid).each(function(i,v){
                aclfileds.append("<input type='hidden' name='group_allow[]' value='"+v+"'>");
        });
-       $(that.allow_cid).each(function(i,v){
+       $(this.allow_cid).each(function(i,v){
                aclfileds.append("<input type='hidden' name='contact_allow[]' value='"+v+"'>");
        });
-       $(that.deny_gid).each(function(i,v){
+       $(this.deny_gid).each(function(i,v){
                aclfileds.append("<input type='hidden' name='group_deny[]' value='"+v+"'>");
        });
-       $(that.deny_cid).each(function(i,v){
+       $(this.deny_cid).each(function(i,v){
                aclfileds.append("<input type='hidden' name='contact_deny[]' value='"+v+"'>");
        });     
 }
 
 ACL.prototype.search = function(){
        var srcstr = $("#acl-search").val();
-       that.list_content.html("");
-       that.get(0,100, srcstr);
+       this.list_content.html("");
+       this.get(0,100, srcstr);
 }
 
 ACL.prototype.on_search = function(event){
-       if (that.kp_timer) clearTimeout(that.kp_timer);
-       that.kp_timer = setTimeout( that.search, 1000);
+       if (this.kp_timer) clearTimeout(this.kp_timer);
+       this.kp_timer = setTimeout( this.search.bind(this), 1000);
 }
 
 ACL.prototype.on_showall = function(event){
        event.preventDefault()
        event.stopPropagation();
        
-       if (that.showall.hasClass("selected")){
+       if (this.showall.hasClass("selected")){
                return false;
        }
-       that.showall.addClass("selected");
+       this.showall.addClass("selected");
        
-       that.allow_cid = [];
-       that.allow_gid = [];
-       that.deny_cid  = [];
-       that.deny_gid  = [];
+       this.allow_cid = [];
+       this.allow_gid = [];
+       this.deny_cid  = [];
+       this.deny_gid  = [];
        
-       that.update_view();
+       this.update_view();
        
        return false;
 }
@@ -116,12 +123,8 @@ ACL.prototype.on_button_show = function(event){
        event.preventDefault()
        event.stopImmediatePropagation()
        event.stopPropagation();
-
-       /*that.showall.removeClass("selected");
-       $(this).siblings(".acl-button-hide").removeClass("selected");
-       $(this).toggleClass("selected");*/
-
-       that.set_allow($(this).parent().attr('id'));
+       
+       this.set_allow($(event.target).parent().attr('id'));
 
        return false;
 }
@@ -130,11 +133,7 @@ ACL.prototype.on_button_hide = function(event){
        event.stopImmediatePropagation()
        event.stopPropagation();
 
-       /*that.showall.removeClass("selected");
-       $(this).siblings(".acl-button-show").removeClass("selected");
-       $(this).toggleClass("selected");*/
-
-       that.set_deny($(this).parent().attr('id'));
+       this.set_deny($(event.target).parent().attr('id'));
 
        return false;
 }
@@ -145,25 +144,25 @@ ACL.prototype.set_allow = function(itemid){
        
        switch(type){
                case "g":
-                       if (that.allow_gid.indexOf(id)<0){
-                               that.allow_gid.push(id)
+                       if (this.allow_gid.indexOf(id)<0){
+                               this.allow_gid.push(id)
                        }else {
-                               that.allow_gid.remove(id);
+                               this.allow_gid.remove(id);
                        }
-                       if (that.deny_gid.indexOf(id)>=0) that.deny_gid.remove(id);
+                       if (this.deny_gid.indexOf(id)>=0) this.deny_gid.remove(id);
                        break;
                case "c":
-                       if (that.allow_cid.indexOf(id)<0){
-                               that.allow_cid.push(id)
-                               if (that.data[id].forum=="1") that.add_mention(id);
+                       if (this.allow_cid.indexOf(id)<0){
+                               this.allow_cid.push(id)
+                               if (this.data[id].forum=="1") this.add_mention(id);
                        } else {
-                               that.allow_cid.remove(id);
-                               if (that.data[id].forum=="1") that.remove_mention(id);
+                               this.allow_cid.remove(id);
+                               if (this.data[id].forum=="1") this.remove_mention(id);
                        }
-                       if (that.deny_cid.indexOf(id)>=0) that.deny_cid.remove(id);                     
+                       if (this.deny_cid.indexOf(id)>=0) this.deny_cid.remove(id);                     
                        break;
        }
-       that.update_view();
+       this.update_view();
 }
 
 ACL.prototype.set_deny = function(itemid){
@@ -172,34 +171,34 @@ ACL.prototype.set_deny = function(itemid){
        
        switch(type){
                case "g":
-                       if (that.deny_gid.indexOf(id)<0){
-                               that.deny_gid.push(id)
+                       if (this.deny_gid.indexOf(id)<0){
+                               this.deny_gid.push(id)
                        } else {
-                               that.deny_gid.remove(id);
+                               this.deny_gid.remove(id);
                        }
-                       if (that.allow_gid.indexOf(id)>=0) that.allow_gid.remove(id);
+                       if (this.allow_gid.indexOf(id)>=0) this.allow_gid.remove(id);
                        break;
                case "c":
-                       if (that.data[id].forum=="1") that.remove_mention(id);
-                       if (that.deny_cid.indexOf(id)<0){
-                               that.deny_cid.push(id)
+                       if (this.data[id].forum=="1") this.remove_mention(id);
+                       if (this.deny_cid.indexOf(id)<0){
+                               this.deny_cid.push(id)
                        } else {
-                               that.deny_cid.remove(id);
+                               this.deny_cid.remove(id);
                        }
-                       if (that.allow_cid.indexOf(id)>=0) that.allow_cid.remove(id);
+                       if (this.allow_cid.indexOf(id)>=0) this.allow_cid.remove(id);
                        break;
        }
-       that.update_view();
+       this.update_view();
 }
 
 ACL.prototype.is_show_all = function() {
-       return (that.allow_gid.length==0 && that.allow_cid.length==0 &&
-               that.deny_gid.length==0 && that.deny_cid.length==0);
+       return (this.allow_gid.length==0 && this.allow_cid.length==0 &&
+               this.deny_gid.length==0 && this.deny_cid.length==0);
 }
 
 ACL.prototype.update_view = function(){
        if (this.is_show_all()){
-                       that.showall.addClass("selected");
+                       this.showall.addClass("selected");
                        /* jot acl */
                                $('#jot-perms-icon').removeClass('lock').addClass('unlock');
                                $('#jot-public').show();
@@ -209,7 +208,7 @@ ACL.prototype.update_view = function(){
                                }
                        
        } else {
-                       that.showall.removeClass("selected");
+                       this.showall.removeClass("selected");
                        /* jot acl */
                                $('#jot-perms-icon').removeClass('unlock').addClass('lock');
                                $('#jot-public').hide();
@@ -220,29 +219,29 @@ ACL.prototype.update_view = function(){
                $(this).removeClass("groupshow grouphide");
        });
        
-       $("#acl-list-content .acl-list-item").each(function(){
-               itemid = $(this).attr('id');
+       $("#acl-list-content .acl-list-item").each(function(index, element){
+               itemid = $(element).attr('id');
                type = itemid[0];
                id       = parseInt(itemid.substr(1));
                
-               btshow = $(this).children(".acl-button-show").removeClass("selected");
-               bthide = $(this).children(".acl-button-hide").removeClass("selected");  
+               btshow = $(element).children(".acl-button-show").removeClass("selected");
+               bthide = $(element).children(".acl-button-hide").removeClass("selected");       
                
                switch(type){
                        case "g":
                                var uclass = "";
-                               if (that.allow_gid.indexOf(id)>=0){
+                               if (this.allow_gid.indexOf(id)>=0){
                                        btshow.addClass("selected");
                                        bthide.removeClass("selected");
                                        uclass="groupshow";
                                }
-                               if (that.deny_gid.indexOf(id)>=0){
+                               if (this.deny_gid.indexOf(id)>=0){
                                        btshow.removeClass("selected");
                                        bthide.addClass("selected");
                                        uclass="grouphide";
                                }
                                
-                               $(that.group_uids[id]).each(function(i,v) {
+                               $(this.group_uids[id]).each(function(i,v) {
                                        if(uclass == "grouphide")
                                                $("#c"+v).removeClass("groupshow");
                                        if(uclass != "") {
@@ -257,17 +256,17 @@ ACL.prototype.update_view = function(){
                                
                                break;
                        case "c":
-                               if (that.allow_cid.indexOf(id)>=0){
+                               if (this.allow_cid.indexOf(id)>=0){
                                        btshow.addClass("selected");
                                        bthide.removeClass("selected");
                                }
-                               if (that.deny_cid.indexOf(id)>=0){
+                               if (this.deny_cid.indexOf(id)>=0){
                                        btshow.removeClass("selected");
                                        bthide.addClass("selected");
                                }                       
                }
                
-       });
+       }.bind(this));
        
 }
 
@@ -281,30 +280,30 @@ ACL.prototype.get = function(start,count, search){
        
        $.ajax({
                type:'POST',
-               url: that.url,
+               url: this.url,
                data: postdata,
                dataType: 'json',
-               success:that.populate
+               success:this.populate.bind(this)
        });
 }
 
 ACL.prototype.populate = function(data){
-       var height = Math.ceil(data.tot / that.nw) * 42;
-       that.list_content.height(height);
-       that.data = {};
-       $(data.items).each(function(){
-               html = "<div class='acl-list-item {4} {5} type{2}' title='{6}' id='{2}{3}'>"+that.item_tpl+"</div>";
-               html = html.format(this.photo, this.name, this.type, this.id, (this.forum=='1'?'forum':''), this.network, this.link);
-               if (this.uids!=undefined) that.group_uids[this.id] = this.uids;
-               //console.log(html);
-               that.list_content.append(html);
-               that.data[this.id] = this;
-       });
-       $(".acl-list-item img[data-src]", that.list_content).each(function(i, el){
+       var height = Math.ceil(data.tot / this.nw) * 42;
+       this.list_content.height(height);
+       this.data = {};
+       $(data.items).each(function(index, item){
+               html = "<div class='acl-list-item {4} {5} type{2}' title='{6}' id='{2}{3}'>"+this.item_tpl+"</div>";
+               html = html.format(item.photo, item.name, item.type, item.id, (item.forum=='1'?'forum':''), item.network, item.link);
+               if (item.uids!=undefined) this.group_uids[item.id] = item.uids;
+               
+               this.list_content.append(html);
+               this.data[item.id] = item;
+       }.bind(this));
+       $(".acl-list-item img[data-src]", this.list_content).each(function(i, el){
                // Add src attribute for images with a data-src attribute
                $(el).attr('src', $(el).data("src"));
        });
        
-       that.update_view();
+       this.update_view();
 }
 
index 5bcbc35bfb3e30c0cab74fff46a20b50b3ececc0..5a1affe3cb7aa3cb815d3eca84622aa96b52fff8 100644 (file)
@@ -9,7 +9,7 @@
                if (h==ch) {
                        return;
                }
-               console.log("_resizeIframe", obj, desth, ch);
+               //console.log("_resizeIframe", obj, desth, ch);
                if (desth!=ch) {
                        setTimeout(_resizeIframe, 500, obj, ch);
                } else {
                        } else {
                                last_popup_menu = menu;
                                last_popup_button = parent;
+                               $('#nav-notifications-menu').perfectScrollbar('update');
                        }
                        return false;
                });
                        'inline' : true,
                        'transition' : 'elastic'
                });
-
+               $("a.ajax-popupbox").colorbox({
+                       'transition' : 'elastic'
+               });
 
                /* notifications template */
                var notifications_tpl= unescape($("#nav-notifications-template[rel=template]").html());
                var notifications_mark = unescape($('<div>').append( $("#nav-notifications-mark-all").clone() ).html()); //outerHtml hack
                var notifications_empty = unescape($("#nav-notifications-menu").html());
 
+               /* enable perfect-scrollbars for different elements */
+               $('#nav-notifications-menu, aside').perfectScrollbar();
+
                /* nav update event  */
                $('nav').bind('nav-update', function(e,data){
                        var invalid = $(data).find('invalid').text();
                                $.jGrowl(text, { sticky: false, theme: 'info', life: 5000 });
                        });
 
+                       /* update the js scrollbars */
+                       $('#nav-notifications-menu').perfectScrollbar('update');
+
                });
 
                NavUpdate();
diff --git a/library/Chart.js-1.0.2/Chart.min.js b/library/Chart.js-1.0.2/Chart.min.js
new file mode 100644 (file)
index 0000000..3a0a2c8
--- /dev/null
@@ -0,0 +1,11 @@
+/*!
+ * Chart.js
+ * http://chartjs.org/
+ * Version: 1.0.2
+ *
+ * Copyright 2015 Nick Downie
+ * Released under the MIT license
+ * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
+ */
+(function(){"use strict";var t=this,i=t.Chart,e=function(t){this.canvas=t.canvas,this.ctx=t;var i=function(t,i){return t["offset"+i]?t["offset"+i]:document.defaultView.getComputedStyle(t).getPropertyValue(i)},e=this.width=i(t.canvas,"Width"),n=this.height=i(t.canvas,"Height");t.canvas.width=e,t.canvas.height=n;var e=this.width=t.canvas.width,n=this.height=t.canvas.height;return this.aspectRatio=this.width/this.height,s.retinaScale(this),this};e.defaults={global:{animation:!0,animationSteps:60,animationEasing:"easeOutQuart",showScale:!0,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleIntegersOnly:!0,scaleBeginAtZero:!1,scaleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",responsive:!1,maintainAspectRatio:!0,showTooltips:!0,customTooltips:!1,tooltipEvents:["mousemove","touchstart","touchmove","mouseout"],tooltipFillColor:"rgba(0,0,0,0.8)",tooltipFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipFontSize:14,tooltipFontStyle:"normal",tooltipFontColor:"#fff",tooltipTitleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipTitleFontSize:14,tooltipTitleFontStyle:"bold",tooltipTitleFontColor:"#fff",tooltipYPadding:6,tooltipXPadding:6,tooltipCaretSize:8,tooltipCornerRadius:6,tooltipXOffset:10,tooltipTemplate:"<%if (label){%><%=label%>: <%}%><%= value %>",multiTooltipTemplate:"<%= value %>",multiTooltipKeyBackground:"#fff",onAnimationProgress:function(){},onAnimationComplete:function(){}}},e.types={};var s=e.helpers={},n=s.each=function(t,i,e){var s=Array.prototype.slice.call(arguments,3);if(t)if(t.length===+t.length){var n;for(n=0;n<t.length;n++)i.apply(e,[t[n],n].concat(s))}else for(var o in t)i.apply(e,[t[o],o].concat(s))},o=s.clone=function(t){var i={};return n(t,function(e,s){t.hasOwnProperty(s)&&(i[s]=e)}),i},a=s.extend=function(t){return n(Array.prototype.slice.call(arguments,1),function(i){n(i,function(e,s){i.hasOwnProperty(s)&&(t[s]=e)})}),t},h=s.merge=function(){var t=Array.prototype.slice.call(arguments,0);return t.unshift({}),a.apply(null,t)},l=s.indexOf=function(t,i){if(Array.prototype.indexOf)return t.indexOf(i);for(var e=0;e<t.length;e++)if(t[e]===i)return e;return-1},r=(s.where=function(t,i){var e=[];return s.each(t,function(t){i(t)&&e.push(t)}),e},s.findNextWhere=function(t,i,e){e||(e=-1);for(var s=e+1;s<t.length;s++){var n=t[s];if(i(n))return n}},s.findPreviousWhere=function(t,i,e){e||(e=t.length);for(var s=e-1;s>=0;s--){var n=t[s];if(i(n))return n}},s.inherits=function(t){var i=this,e=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return i.apply(this,arguments)},s=function(){this.constructor=e};return s.prototype=i.prototype,e.prototype=new s,e.extend=r,t&&a(e.prototype,t),e.__super__=i.prototype,e}),c=s.noop=function(){},u=s.uid=function(){var t=0;return function(){return"chart-"+t++}}(),d=s.warn=function(t){window.console&&"function"==typeof window.console.warn&&console.warn(t)},p=s.amd="function"==typeof define&&define.amd,f=s.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},g=s.max=function(t){return Math.max.apply(Math,t)},m=s.min=function(t){return Math.min.apply(Math,t)},v=(s.cap=function(t,i,e){if(f(i)){if(t>i)return i}else if(f(e)&&e>t)return e;return t},s.getDecimalPlaces=function(t){return t%1!==0&&f(t)?t.toString().split(".")[1].length:0}),S=s.radians=function(t){return t*(Math.PI/180)},x=(s.getAngleFromPoint=function(t,i){var e=i.x-t.x,s=i.y-t.y,n=Math.sqrt(e*e+s*s),o=2*Math.PI+Math.atan2(s,e);return 0>e&&0>s&&(o+=2*Math.PI),{angle:o,distance:n}},s.aliasPixel=function(t){return t%2===0?0:.5}),y=(s.splineCurve=function(t,i,e,s){var n=Math.sqrt(Math.pow(i.x-t.x,2)+Math.pow(i.y-t.y,2)),o=Math.sqrt(Math.pow(e.x-i.x,2)+Math.pow(e.y-i.y,2)),a=s*n/(n+o),h=s*o/(n+o);return{inner:{x:i.x-a*(e.x-t.x),y:i.y-a*(e.y-t.y)},outer:{x:i.x+h*(e.x-t.x),y:i.y+h*(e.y-t.y)}}},s.calculateOrderOfMagnitude=function(t){return Math.floor(Math.log(t)/Math.LN10)}),C=(s.calculateScaleRange=function(t,i,e,s,n){var o=2,a=Math.floor(i/(1.5*e)),h=o>=a,l=g(t),r=m(t);l===r&&(l+=.5,r>=.5&&!s?r-=.5:l+=.5);for(var c=Math.abs(l-r),u=y(c),d=Math.ceil(l/(1*Math.pow(10,u)))*Math.pow(10,u),p=s?0:Math.floor(r/(1*Math.pow(10,u)))*Math.pow(10,u),f=d-p,v=Math.pow(10,u),S=Math.round(f/v);(S>a||a>2*S)&&!h;)if(S>a)v*=2,S=Math.round(f/v),S%1!==0&&(h=!0);else if(n&&u>=0){if(v/2%1!==0)break;v/=2,S=Math.round(f/v)}else v/=2,S=Math.round(f/v);return h&&(S=o,v=f/S),{steps:S,stepValue:v,min:p,max:p+S*v}},s.template=function(t,i){function e(t,i){var e=/\W/.test(t)?new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+t.replace(/[\r\t\n]/g," ").split("<%").join("       ").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("   ").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"):s[t]=s[t];return i?e(i):e}if(t instanceof Function)return t(i);var s={};return e(t,i)}),w=(s.generateLabels=function(t,i,e,s){var o=new Array(i);return labelTemplateString&&n(o,function(i,n){o[n]=C(t,{value:e+s*(n+1)})}),o},s.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-0.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:1==(t/=1)?1:(e||(e=.3),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),-(s*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e)))},easeOutElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:1==(t/=1)?1:(e||(e=.3),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),s*Math.pow(2,-10*t)*Math.sin(2*(1*t-i)*Math.PI/e)+1)},easeInOutElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:2==(t/=.5)?1:(e||(e=.3*1.5),s<Math.abs(1)?(s=1,i=e/4):i=e/(2*Math.PI)*Math.asin(1/s),1>t?-.5*s*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e):s*Math.pow(2,-10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e)*.5+1)},easeInBack:function(t){var i=1.70158;return 1*(t/=1)*t*((i+1)*t-i)},easeOutBack:function(t){var i=1.70158;return 1*((t=t/1-1)*t*((i+1)*t+i)+1)},easeInOutBack:function(t){var i=1.70158;return(t/=.5)<1?.5*t*t*(((i*=1.525)+1)*t-i):.5*((t-=2)*t*(((i*=1.525)+1)*t+i)+2)},easeInBounce:function(t){return 1-w.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?7.5625*t*t:2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*w.easeInBounce(2*t):.5*w.easeOutBounce(2*t-1)+.5}}),b=s.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),P=s.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),L=(s.animationLoop=function(t,i,e,s,n,o){var a=0,h=w[e]||w.linear,l=function(){a++;var e=a/i,r=h(e);t.call(o,r,e,a),s.call(o,r,e),i>a?o.animationFrame=b(l):n.apply(o)};b(l)},s.getRelativePosition=function(t){var i,e,s=t.originalEvent||t,n=t.currentTarget||t.srcElement,o=n.getBoundingClientRect();return s.touches?(i=s.touches[0].clientX-o.left,e=s.touches[0].clientY-o.top):(i=s.clientX-o.left,e=s.clientY-o.top),{x:i,y:e}},s.addEvent=function(t,i,e){t.addEventListener?t.addEventListener(i,e):t.attachEvent?t.attachEvent("on"+i,e):t["on"+i]=e}),k=s.removeEvent=function(t,i,e){t.removeEventListener?t.removeEventListener(i,e,!1):t.detachEvent?t.detachEvent("on"+i,e):t["on"+i]=c},F=(s.bindEvents=function(t,i,e){t.events||(t.events={}),n(i,function(i){t.events[i]=function(){e.apply(t,arguments)},L(t.chart.canvas,i,t.events[i])})},s.unbindEvents=function(t,i){n(i,function(i,e){k(t.chart.canvas,e,i)})}),R=s.getMaximumWidth=function(t){var i=t.parentNode;return i.clientWidth},T=s.getMaximumHeight=function(t){var i=t.parentNode;return i.clientHeight},A=(s.getMaximumSize=s.getMaximumWidth,s.retinaScale=function(t){var i=t.ctx,e=t.canvas.width,s=t.canvas.height;window.devicePixelRatio&&(i.canvas.style.width=e+"px",i.canvas.style.height=s+"px",i.canvas.height=s*window.devicePixelRatio,i.canvas.width=e*window.devicePixelRatio,i.scale(window.devicePixelRatio,window.devicePixelRatio))}),M=s.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},W=s.fontString=function(t,i,e){return i+" "+t+"px "+e},z=s.longestText=function(t,i,e){t.font=i;var s=0;return n(e,function(i){var e=t.measureText(i).width;s=e>s?e:s}),s},B=s.drawRoundedRectangle=function(t,i,e,s,n,o){t.beginPath(),t.moveTo(i+o,e),t.lineTo(i+s-o,e),t.quadraticCurveTo(i+s,e,i+s,e+o),t.lineTo(i+s,e+n-o),t.quadraticCurveTo(i+s,e+n,i+s-o,e+n),t.lineTo(i+o,e+n),t.quadraticCurveTo(i,e+n,i,e+n-o),t.lineTo(i,e+o),t.quadraticCurveTo(i,e,i+o,e),t.closePath()};e.instances={},e.Type=function(t,i,s){this.options=i,this.chart=s,this.id=u(),e.instances[this.id]=this,i.responsive&&this.resize(),this.initialize.call(this,t)},a(e.Type.prototype,{initialize:function(){return this},clear:function(){return M(this.chart),this},stop:function(){return P(this.animationFrame),this},resize:function(t){this.stop();var i=this.chart.canvas,e=R(this.chart.canvas),s=this.options.maintainAspectRatio?e/this.chart.aspectRatio:T(this.chart.canvas);return i.width=this.chart.width=e,i.height=this.chart.height=s,A(this.chart),"function"==typeof t&&t.apply(this,Array.prototype.slice.call(arguments,1)),this},reflow:c,render:function(t){return t&&this.reflow(),this.options.animation&&!t?s.animationLoop(this.draw,this.options.animationSteps,this.options.animationEasing,this.options.onAnimationProgress,this.options.onAnimationComplete,this):(this.draw(),this.options.onAnimationComplete.call(this)),this},generateLegend:function(){return C(this.options.legendTemplate,this)},destroy:function(){this.clear(),F(this,this.events);var t=this.chart.canvas;t.width=this.chart.width,t.height=this.chart.height,t.style.removeProperty?(t.style.removeProperty("width"),t.style.removeProperty("height")):(t.style.removeAttribute("width"),t.style.removeAttribute("height")),delete e.instances[this.id]},showTooltip:function(t,i){"undefined"==typeof this.activeElements&&(this.activeElements=[]);var o=function(t){var i=!1;return t.length!==this.activeElements.length?i=!0:(n(t,function(t,e){t!==this.activeElements[e]&&(i=!0)},this),i)}.call(this,t);if(o||i){if(this.activeElements=t,this.draw(),this.options.customTooltips&&this.options.customTooltips(!1),t.length>0)if(this.datasets&&this.datasets.length>1){for(var a,h,r=this.datasets.length-1;r>=0&&(a=this.datasets[r].points||this.datasets[r].bars||this.datasets[r].segments,h=l(a,t[0]),-1===h);r--);var c=[],u=[],d=function(){var t,i,e,n,o,a=[],l=[],r=[];return s.each(this.datasets,function(i){t=i.points||i.bars||i.segments,t[h]&&t[h].hasValue()&&a.push(t[h])}),s.each(a,function(t){l.push(t.x),r.push(t.y),c.push(s.template(this.options.multiTooltipTemplate,t)),u.push({fill:t._saved.fillColor||t.fillColor,stroke:t._saved.strokeColor||t.strokeColor})},this),o=m(r),e=g(r),n=m(l),i=g(l),{x:n>this.chart.width/2?n:i,y:(o+e)/2}}.call(this,h);new e.MultiTooltip({x:d.x,y:d.y,xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,xOffset:this.options.tooltipXOffset,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,titleTextColor:this.options.tooltipTitleFontColor,titleFontFamily:this.options.tooltipTitleFontFamily,titleFontStyle:this.options.tooltipTitleFontStyle,titleFontSize:this.options.tooltipTitleFontSize,cornerRadius:this.options.tooltipCornerRadius,labels:c,legendColors:u,legendColorBackground:this.options.multiTooltipKeyBackground,title:t[0].label,chart:this.chart,ctx:this.chart.ctx,custom:this.options.customTooltips}).draw()}else n(t,function(t){var i=t.tooltipPosition();new e.Tooltip({x:Math.round(i.x),y:Math.round(i.y),xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,caretHeight:this.options.tooltipCaretSize,cornerRadius:this.options.tooltipCornerRadius,text:C(this.options.tooltipTemplate,t),chart:this.chart,custom:this.options.customTooltips}).draw()},this);return this}},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)}}),e.Type.extend=function(t){var i=this,s=function(){return i.apply(this,arguments)};if(s.prototype=o(i.prototype),a(s.prototype,t),s.extend=e.Type.extend,t.name||i.prototype.name){var n=t.name||i.prototype.name,l=e.defaults[i.prototype.name]?o(e.defaults[i.prototype.name]):{};e.defaults[n]=a(l,t.defaults),e.types[n]=s,e.prototype[n]=function(t,i){var o=h(e.defaults.global,e.defaults[n],i||{});return new s(t,o,this)}}else d("Name not provided for this chart, so it hasn't been registered");return i},e.Element=function(t){a(this,t),this.initialize.apply(this,arguments),this.save()},a(e.Element.prototype,{initialize:function(){},restore:function(t){return t?n(t,function(t){this[t]=this._saved[t]},this):a(this,this._saved),this},save:function(){return this._saved=o(this),delete this._saved._saved,this},update:function(t){return n(t,function(t,i){this._saved[i]=this[i],this[i]=t},this),this},transition:function(t,i){return n(t,function(t,e){this[e]=(t-this._saved[e])*i+this._saved[e]},this),this},tooltipPosition:function(){return{x:this.x,y:this.y}},hasValue:function(){return f(this.value)}}),e.Element.extend=r,e.Point=e.Element.extend({display:!0,inRange:function(t,i){var e=this.hitDetectionRadius+this.radius;return Math.pow(t-this.x,2)+Math.pow(i-this.y,2)<Math.pow(e,2)},draw:function(){if(this.display){var t=this.ctx;t.beginPath(),t.arc(this.x,this.y,this.radius,0,2*Math.PI),t.closePath(),t.strokeStyle=this.strokeColor,t.lineWidth=this.strokeWidth,t.fillStyle=this.fillColor,t.fill(),t.stroke()}}}),e.Arc=e.Element.extend({inRange:function(t,i){var e=s.getAngleFromPoint(this,{x:t,y:i}),n=e.angle>=this.startAngle&&e.angle<=this.endAngle,o=e.distance>=this.innerRadius&&e.distance<=this.outerRadius;return n&&o},tooltipPosition:function(){var t=this.startAngle+(this.endAngle-this.startAngle)/2,i=(this.outerRadius-this.innerRadius)/2+this.innerRadius;return{x:this.x+Math.cos(t)*i,y:this.y+Math.sin(t)*i}},draw:function(t){var i=this.ctx;i.beginPath(),i.arc(this.x,this.y,this.outerRadius,this.startAngle,this.endAngle),i.arc(this.x,this.y,this.innerRadius,this.endAngle,this.startAngle,!0),i.closePath(),i.strokeStyle=this.strokeColor,i.lineWidth=this.strokeWidth,i.fillStyle=this.fillColor,i.fill(),i.lineJoin="bevel",this.showStroke&&i.stroke()}}),e.Rectangle=e.Element.extend({draw:function(){var t=this.ctx,i=this.width/2,e=this.x-i,s=this.x+i,n=this.base-(this.base-this.y),o=this.strokeWidth/2;this.showStroke&&(e+=o,s-=o,n+=o),t.beginPath(),t.fillStyle=this.fillColor,t.strokeStyle=this.strokeColor,t.lineWidth=this.strokeWidth,t.moveTo(e,this.base),t.lineTo(e,n),t.lineTo(s,n),t.lineTo(s,this.base),t.fill(),this.showStroke&&t.stroke()},height:function(){return this.base-this.y},inRange:function(t,i){return t>=this.x-this.width/2&&t<=this.x+this.width/2&&i>=this.y&&i<=this.base}}),e.Tooltip=e.Element.extend({draw:function(){var t=this.chart.ctx;t.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.xAlign="center",this.yAlign="above";var i=this.caretPadding=2,e=t.measureText(this.text).width+2*this.xPadding,s=this.fontSize+2*this.yPadding,n=s+this.caretHeight+i;this.x+e/2>this.chart.width?this.xAlign="left":this.x-e/2<0&&(this.xAlign="right"),this.y-n<0&&(this.yAlign="below");var o=this.x-e/2,a=this.y-n;if(t.fillStyle=this.fillColor,this.custom)this.custom(this);else{switch(this.yAlign){case"above":t.beginPath(),t.moveTo(this.x,this.y-i),t.lineTo(this.x+this.caretHeight,this.y-(i+this.caretHeight)),t.lineTo(this.x-this.caretHeight,this.y-(i+this.caretHeight)),t.closePath(),t.fill();break;case"below":a=this.y+i+this.caretHeight,t.beginPath(),t.moveTo(this.x,this.y+i),t.lineTo(this.x+this.caretHeight,this.y+i+this.caretHeight),t.lineTo(this.x-this.caretHeight,this.y+i+this.caretHeight),t.closePath(),t.fill()}switch(this.xAlign){case"left":o=this.x-e+(this.cornerRadius+this.caretHeight);break;case"right":o=this.x-(this.cornerRadius+this.caretHeight)}B(t,o,a,e,s,this.cornerRadius),t.fill(),t.fillStyle=this.textColor,t.textAlign="center",t.textBaseline="middle",t.fillText(this.text,o+e/2,a+s/2)}}}),e.MultiTooltip=e.Element.extend({initialize:function(){this.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.titleFont=W(this.titleFontSize,this.titleFontStyle,this.titleFontFamily),this.height=this.labels.length*this.fontSize+(this.labels.length-1)*(this.fontSize/2)+2*this.yPadding+1.5*this.titleFontSize,this.ctx.font=this.titleFont;var t=this.ctx.measureText(this.title).width,i=z(this.ctx,this.font,this.labels)+this.fontSize+3,e=g([i,t]);this.width=e+2*this.xPadding;var s=this.height/2;this.y-s<0?this.y=s:this.y+s>this.chart.height&&(this.y=this.chart.height-s),this.x>this.chart.width/2?this.x-=this.xOffset+this.width:this.x+=this.xOffset},getLineHeight:function(t){var i=this.y-this.height/2+this.yPadding,e=t-1;return 0===t?i+this.titleFontSize/2:i+(1.5*this.fontSize*e+this.fontSize/2)+1.5*this.titleFontSize},draw:function(){if(this.custom)this.custom(this);else{B(this.ctx,this.x,this.y-this.height/2,this.width,this.height,this.cornerRadius);var t=this.ctx;t.fillStyle=this.fillColor,t.fill(),t.closePath(),t.textAlign="left",t.textBaseline="middle",t.fillStyle=this.titleTextColor,t.font=this.titleFont,t.fillText(this.title,this.x+this.xPadding,this.getLineHeight(0)),t.font=this.font,s.each(this.labels,function(i,e){t.fillStyle=this.textColor,t.fillText(i,this.x+this.xPadding+this.fontSize+3,this.getLineHeight(e+1)),t.fillStyle=this.legendColorBackground,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize),t.fillStyle=this.legendColors[e].fill,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize)},this)}}}),e.Scale=e.Element.extend({initialize:function(){this.fit()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}));this.yLabelWidth=this.display&&this.showLabels?z(this.ctx,this.font,this.yLabels):0},addXLabel:function(t){this.xLabels.push(t),this.valuesCount++,this.fit()},removeXLabel:function(){this.xLabels.shift(),this.valuesCount--,this.fit()},fit:function(){this.startPoint=this.display?this.fontSize:0,this.endPoint=this.display?this.height-1.5*this.fontSize-5:this.height,this.startPoint+=this.padding,this.endPoint-=this.padding;var t,i=this.endPoint-this.startPoint;for(this.calculateYRange(i),this.buildYLabels(),this.calculateXLabelRotation();i>this.endPoint-this.startPoint;)i=this.endPoint-this.startPoint,t=this.yLabelWidth,this.calculateYRange(i),this.buildYLabels(),t<this.yLabelWidth&&this.calculateXLabelRotation()},calculateXLabelRotation:function(){this.ctx.font=this.font;var t,i,e=this.ctx.measureText(this.xLabels[0]).width,s=this.ctx.measureText(this.xLabels[this.xLabels.length-1]).width;if(this.xScalePaddingRight=s/2+3,this.xScalePaddingLeft=e/2>this.yLabelWidth+10?e/2:this.yLabelWidth+10,this.xLabelRotation=0,this.display){var n,o=z(this.ctx,this.font,this.xLabels);this.xLabelWidth=o;for(var a=Math.floor(this.calculateX(1)-this.calculateX(0))-6;this.xLabelWidth>a&&0===this.xLabelRotation||this.xLabelWidth>a&&this.xLabelRotation<=90&&this.xLabelRotation>0;)n=Math.cos(S(this.xLabelRotation)),t=n*e,i=n*s,t+this.fontSize/2>this.yLabelWidth+8&&(this.xScalePaddingLeft=t+this.fontSize/2),this.xScalePaddingRight=this.fontSize/2,this.xLabelRotation++,this.xLabelWidth=n*o;this.xLabelRotation>0&&(this.endPoint-=Math.sin(S(this.xLabelRotation))*o+3)}else this.xLabelWidth=0,this.xScalePaddingRight=this.padding,this.xScalePaddingLeft=this.padding},calculateYRange:c,drawingArea:function(){return this.startPoint-this.endPoint},calculateY:function(t){var i=this.drawingArea()/(this.min-this.max);return this.endPoint-i*(t-this.min)},calculateX:function(t){var i=(this.xLabelRotation>0,this.width-(this.xScalePaddingLeft+this.xScalePaddingRight)),e=i/Math.max(this.valuesCount-(this.offsetGridLines?0:1),1),s=e*t+this.xScalePaddingLeft;return this.offsetGridLines&&(s+=e/2),Math.round(s)},update:function(t){s.extend(this,t),this.fit()},draw:function(){var t=this.ctx,i=(this.endPoint-this.startPoint)/this.steps,e=Math.round(this.xScalePaddingLeft);this.display&&(t.fillStyle=this.textColor,t.font=this.font,n(this.yLabels,function(n,o){var a=this.endPoint-i*o,h=Math.round(a),l=this.showHorizontalLines;t.textAlign="right",t.textBaseline="middle",this.showLabels&&t.fillText(n,e-10,a),0!==o||l||(l=!0),l&&t.beginPath(),o>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),h+=s.aliasPixel(t.lineWidth),l&&(t.moveTo(e,h),t.lineTo(this.width,h),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(e-5,h),t.lineTo(e,h),t.stroke(),t.closePath()},this),n(this.xLabels,function(i,e){var s=this.calculateX(e)+x(this.lineWidth),n=this.calculateX(e-(this.offsetGridLines?.5:0))+x(this.lineWidth),o=this.xLabelRotation>0,a=this.showVerticalLines;0!==e||a||(a=!0),a&&t.beginPath(),e>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),a&&(t.moveTo(n,this.endPoint),t.lineTo(n,this.startPoint-3),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(n,this.endPoint),t.lineTo(n,this.endPoint+5),t.stroke(),t.closePath(),t.save(),t.translate(s,o?this.endPoint+12:this.endPoint+8),t.rotate(-1*S(this.xLabelRotation)),t.font=this.font,t.textAlign=o?"right":"center",t.textBaseline=o?"middle":"top",t.fillText(i,0,0),t.restore()},this))}}),e.RadialScale=e.Element.extend({initialize:function(){this.size=m([this.height,this.width]),this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2},calculateCenterOffset:function(t){var i=this.drawingArea/(this.max-this.min);return(t-this.min)*i},update:function(){this.lineArc?this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2:this.setScaleSize(),this.buildYLabels()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}))},getCircumference:function(){return 2*Math.PI/this.valuesCount},setScaleSize:function(){var t,i,e,s,n,o,a,h,l,r,c,u,d=m([this.height/2-this.pointLabelFontSize-5,this.width/2]),p=this.width,g=0;for(this.ctx.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),i=0;i<this.valuesCount;i++)t=this.getPointPosition(i,d),e=this.ctx.measureText(C(this.templateString,{value:this.labels[i]})).width+5,0===i||i===this.valuesCount/2?(s=e/2,t.x+s>p&&(p=t.x+s,n=i),t.x-s<g&&(g=t.x-s,a=i)):i<this.valuesCount/2?t.x+e>p&&(p=t.x+e,n=i):i>this.valuesCount/2&&t.x-e<g&&(g=t.x-e,a=i);l=g,r=Math.ceil(p-this.width),o=this.getIndexAngle(n),h=this.getIndexAngle(a),c=r/Math.sin(o+Math.PI/2),u=l/Math.sin(h+Math.PI/2),c=f(c)?c:0,u=f(u)?u:0,this.drawingArea=d-(u+c)/2,this.setCenterPoint(u,c)},setCenterPoint:function(t,i){var e=this.width-i-this.drawingArea,s=t+this.drawingArea;this.xCenter=(s+e)/2,this.yCenter=this.height/2},getIndexAngle:function(t){var i=2*Math.PI/this.valuesCount;return t*i-Math.PI/2},getPointPosition:function(t,i){var e=this.getIndexAngle(t);return{x:Math.cos(e)*i+this.xCenter,y:Math.sin(e)*i+this.yCenter}},draw:function(){if(this.display){var t=this.ctx;if(n(this.yLabels,function(i,e){if(e>0){var s,n=e*(this.drawingArea/this.steps),o=this.yCenter-n;if(this.lineWidth>0)if(t.strokeStyle=this.lineColor,t.lineWidth=this.lineWidth,this.lineArc)t.beginPath(),t.arc(this.xCenter,this.yCenter,n,0,2*Math.PI),t.closePath(),t.stroke();else{t.beginPath();for(var a=0;a<this.valuesCount;a++)s=this.getPointPosition(a,this.calculateCenterOffset(this.min+e*this.stepValue)),0===a?t.moveTo(s.x,s.y):t.lineTo(s.x,s.y);t.closePath(),t.stroke()}if(this.showLabels){if(t.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.showLabelBackdrop){var h=t.measureText(i).width;t.fillStyle=this.backdropColor,t.fillRect(this.xCenter-h/2-this.backdropPaddingX,o-this.fontSize/2-this.backdropPaddingY,h+2*this.backdropPaddingX,this.fontSize+2*this.backdropPaddingY)}t.textAlign="center",t.textBaseline="middle",t.fillStyle=this.fontColor,t.fillText(i,this.xCenter,o)}}},this),!this.lineArc){t.lineWidth=this.angleLineWidth,t.strokeStyle=this.angleLineColor;for(var i=this.valuesCount-1;i>=0;i--){if(this.angleLineWidth>0){var e=this.getPointPosition(i,this.calculateCenterOffset(this.max));t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(e.x,e.y),t.stroke(),t.closePath()}var s=this.getPointPosition(i,this.calculateCenterOffset(this.max)+5);t.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),t.fillStyle=this.pointLabelFontColor;var o=this.labels.length,a=this.labels.length/2,h=a/2,l=h>i||i>o-h,r=i===h||i===o-h;t.textAlign=0===i?"center":i===a?"center":a>i?"left":"right",t.textBaseline=r?"middle":l?"bottom":"top",t.fillText(this.labels[i],s.x,s.y)}}}}}),s.addEvent(window,"resize",function(){var t;return function(){clearTimeout(t),t=setTimeout(function(){n(e.instances,function(t){t.options.responsive&&t.resize(t.render,!0)})},50)}}()),p?define(function(){return e}):"object"==typeof module&&module.exports&&(module.exports=e),t.Chart=e,e.noConflict=function(){return t.Chart=i,e}}).call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleBeginAtZero:!0,scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,scaleShowHorizontalLines:!0,scaleShowVerticalLines:!0,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].fillColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Bar",defaults:s,initialize:function(t){var s=this.options;this.ScaleClass=i.Scale.extend({offsetGridLines:!0,calculateBarX:function(t,i,e){var n=this.calculateBaseWidth(),o=this.calculateX(e)-n/2,a=this.calculateBarWidth(t);return o+a*i+i*s.barDatasetSpacing+a/2},calculateBaseWidth:function(){return this.calculateX(1)-this.calculateX(0)-2*s.barValueSpacing},calculateBarWidth:function(t){var i=this.calculateBaseWidth()-(t-1)*s.barDatasetSpacing;return i/t}}),this.datasets=[],this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getBarsAtEvent(t):[];this.eachBars(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),this.BarClass=i.Rectangle.extend({strokeWidth:this.options.barStrokeWidth,showStroke:this.options.barShowStroke,ctx:this.chart.ctx}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,bars:[]};this.datasets.push(s),e.each(i.data,function(e,n){s.bars.push(new this.BarClass({value:e,label:t.labels[n],datasetLabel:i.label,strokeColor:i.strokeColor,fillColor:i.fillColor,highlightFill:i.highlightFill||i.fillColor,highlightStroke:i.highlightStroke||i.strokeColor}))},this)},this),this.buildScale(t.labels),this.BarClass.prototype.base=this.scale.endPoint,this.eachBars(function(t,i,s){e.extend(t,{width:this.scale.calculateBarWidth(this.datasets.length),x:this.scale.calculateBarX(this.datasets.length,s,i),y:this.scale.endPoint}),t.save()},this),this.render()},update:function(){this.scale.update(),e.each(this.activeElements,function(t){t.restore(["fillColor","strokeColor"])}),this.eachBars(function(t){t.save()}),this.render()},eachBars:function(t){e.each(this.datasets,function(i,s){e.each(i.bars,t,this,s)},this)},getBarsAtEvent:function(t){for(var i,s=[],n=e.getRelativePosition(t),o=function(t){s.push(t.bars[i])},a=0;a<this.datasets.length;a++)for(i=0;i<this.datasets[a].bars.length;i++)if(this.datasets[a].bars[i].inRange(n.x,n.y))return e.each(this.datasets,o),s;return s},buildScale:function(t){var i=this,s=function(){var t=[];return i.eachBars(function(i){t.push(i.value)}),t},n={templateString:this.options.scaleLabel,height:this.chart.height,width:this.chart.width,ctx:this.chart.ctx,textColor:this.options.scaleFontColor,fontSize:this.options.scaleFontSize,fontStyle:this.options.scaleFontStyle,fontFamily:this.options.scaleFontFamily,valuesCount:t.length,beginAtZero:this.options.scaleBeginAtZero,integersOnly:this.options.scaleIntegersOnly,calculateYRange:function(t){var i=e.calculateScaleRange(s(),t,this.fontSize,this.beginAtZero,this.integersOnly);e.extend(this,i)},xLabels:t,font:e.fontString(this.options.scaleFontSize,this.options.scaleFontStyle,this.options.scaleFontFamily),lineWidth:this.options.scaleLineWidth,lineColor:this.options.scaleLineColor,showHorizontalLines:this.options.scaleShowHorizontalLines,showVerticalLines:this.options.scaleShowVerticalLines,gridLineWidth:this.options.scaleShowGridLines?this.options.scaleGridLineWidth:0,gridLineColor:this.options.scaleShowGridLines?this.options.scaleGridLineColor:"rgba(0,0,0,0)",padding:this.options.showScale?0:this.options.barShowStroke?this.options.barStrokeWidth:0,showLabels:this.options.scaleShowLabels,display:this.options.showScale};this.options.scaleOverride&&e.extend(n,{calculateYRange:e.noop,steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}),this.scale=new this.ScaleClass(n)},addData:function(t,i){e.each(t,function(t,e){this.datasets[e].bars.push(new this.BarClass({value:t,label:i,x:this.scale.calculateBarX(this.datasets.length,e,this.scale.valuesCount+1),y:this.scale.endPoint,width:this.scale.calculateBarWidth(this.datasets.length),base:this.scale.endPoint,strokeColor:this.datasets[e].strokeColor,fillColor:this.datasets[e].fillColor}))
+},this),this.scale.addXLabel(i),this.update()},removeData:function(){this.scale.removeXLabel(),e.each(this.datasets,function(t){t.bars.shift()},this),this.update()},reflow:function(){e.extend(this.BarClass.prototype,{y:this.scale.endPoint,base:this.scale.endPoint});var t=e.extend({height:this.chart.height,width:this.chart.width});this.scale.update(t)},draw:function(t){var i=t||1;this.clear();this.chart.ctx;this.scale.draw(i),e.each(this.datasets,function(t,s){e.each(t.bars,function(t,e){t.hasValue()&&(t.base=this.scale.endPoint,t.transition({x:this.scale.calculateBarX(this.datasets.length,s,e),y:this.scale.calculateY(t.value),width:this.scale.calculateBarWidth(this.datasets.length)},i).draw())},this)},this)}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,percentageInnerCutout:50,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<segments.length; i++){%><li><span style="background-color:<%=segments[i].fillColor%>"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Doughnut",defaults:s,initialize:function(t){this.segments=[],this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,this.SegmentArc=i.Arc.extend({ctx:this.chart.ctx,x:this.chart.width/2,y:this.chart.height/2}),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.calculateTotal(t),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({value:t.value,outerRadius:this.options.animateScale?0:this.outerRadius,innerRadius:this.options.animateScale?0:this.outerRadius/100*this.options.percentageInnerCutout,fillColor:t.color,highlightColor:t.highlight||t.color,showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,startAngle:1.5*Math.PI,circumference:this.options.animateRotate?0:this.calculateCircumference(t.value),label:t.label})),e||(this.reflow(),this.update())},calculateCircumference:function(t){return 2*Math.PI*(Math.abs(t)/this.total)},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=Math.abs(t.value)},this)},update:function(){this.calculateTotal(this.segments),e.each(this.activeElements,function(t){t.restore(["fillColor"])}),e.each(this.segments,function(t){t.save()}),this.render()},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,e.each(this.segments,function(t){t.update({outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout})},this)},draw:function(t){var i=t?t:1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.calculateCircumference(t.value),outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout},i),t.endAngle=t.startAngle+t.circumference,t.draw(),0===e&&(t.startAngle=1.5*Math.PI),e<this.segments.length-1&&(this.segments[e+1].startAngle=t.endAngle)},this)}}),i.types.Doughnut.extend({name:"Pie",defaults:e.merge(s,{percentageInnerCutout:0})})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,scaleShowHorizontalLines:!0,scaleShowVerticalLines:!0,bezierCurve:!0,bezierCurveTension:.4,pointDot:!0,pointDotRadius:4,pointDotStrokeWidth:1,pointHitDetectionRadius:20,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].strokeColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"Line",defaults:s,initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx,inRange:function(t){return Math.pow(t-this.x,2)<Math.pow(this.radius+this.hitDetectionRadius,2)}}),this.datasets=[],this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,pointColor:i.pointColor,pointStrokeColor:i.pointStrokeColor,points:[]};this.datasets.push(s),e.each(i.data,function(e,n){s.points.push(new this.PointClass({value:e,label:t.labels[n],datasetLabel:i.label,strokeColor:i.pointStrokeColor,fillColor:i.pointColor,highlightFill:i.pointHighlightFill||i.pointColor,highlightStroke:i.pointHighlightStroke||i.pointStrokeColor}))},this),this.buildScale(t.labels),this.eachPoints(function(t,i){e.extend(t,{x:this.scale.calculateX(i),y:this.scale.endPoint}),t.save()},this)},this),this.render()},update:function(){this.scale.update(),e.each(this.activeElements,function(t){t.restore(["fillColor","strokeColor"])}),this.eachPoints(function(t){t.save()}),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.datasets,function(t){e.each(t.points,function(t){t.inRange(s.x,s.y)&&i.push(t)})},this),i},buildScale:function(t){var s=this,n=function(){var t=[];return s.eachPoints(function(i){t.push(i.value)}),t},o={templateString:this.options.scaleLabel,height:this.chart.height,width:this.chart.width,ctx:this.chart.ctx,textColor:this.options.scaleFontColor,fontSize:this.options.scaleFontSize,fontStyle:this.options.scaleFontStyle,fontFamily:this.options.scaleFontFamily,valuesCount:t.length,beginAtZero:this.options.scaleBeginAtZero,integersOnly:this.options.scaleIntegersOnly,calculateYRange:function(t){var i=e.calculateScaleRange(n(),t,this.fontSize,this.beginAtZero,this.integersOnly);e.extend(this,i)},xLabels:t,font:e.fontString(this.options.scaleFontSize,this.options.scaleFontStyle,this.options.scaleFontFamily),lineWidth:this.options.scaleLineWidth,lineColor:this.options.scaleLineColor,showHorizontalLines:this.options.scaleShowHorizontalLines,showVerticalLines:this.options.scaleShowVerticalLines,gridLineWidth:this.options.scaleShowGridLines?this.options.scaleGridLineWidth:0,gridLineColor:this.options.scaleShowGridLines?this.options.scaleGridLineColor:"rgba(0,0,0,0)",padding:this.options.showScale?0:this.options.pointDotRadius+this.options.pointDotStrokeWidth,showLabels:this.options.scaleShowLabels,display:this.options.showScale};this.options.scaleOverride&&e.extend(o,{calculateYRange:e.noop,steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}),this.scale=new i.Scale(o)},addData:function(t,i){e.each(t,function(t,e){this.datasets[e].points.push(new this.PointClass({value:t,label:i,x:this.scale.calculateX(this.scale.valuesCount+1),y:this.scale.endPoint,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.addXLabel(i),this.update()},removeData:function(){this.scale.removeXLabel(),e.each(this.datasets,function(t){t.points.shift()},this),this.update()},reflow:function(){var t=e.extend({height:this.chart.height,width:this.chart.width});this.scale.update(t)},draw:function(t){var i=t||1;this.clear();var s=this.chart.ctx,n=function(t){return null!==t.value},o=function(t,i,s){return e.findNextWhere(i,n,s)||t},a=function(t,i,s){return e.findPreviousWhere(i,n,s)||t};this.scale.draw(i),e.each(this.datasets,function(t){var h=e.where(t.points,n);e.each(t.points,function(t,e){t.hasValue()&&t.transition({y:this.scale.calculateY(t.value),x:this.scale.calculateX(e)},i)},this),this.options.bezierCurve&&e.each(h,function(t,i){var s=i>0&&i<h.length-1?this.options.bezierCurveTension:0;t.controlPoints=e.splineCurve(a(t,h,i),t,o(t,h,i),s),t.controlPoints.outer.y>this.scale.endPoint?t.controlPoints.outer.y=this.scale.endPoint:t.controlPoints.outer.y<this.scale.startPoint&&(t.controlPoints.outer.y=this.scale.startPoint),t.controlPoints.inner.y>this.scale.endPoint?t.controlPoints.inner.y=this.scale.endPoint:t.controlPoints.inner.y<this.scale.startPoint&&(t.controlPoints.inner.y=this.scale.startPoint)},this),s.lineWidth=this.options.datasetStrokeWidth,s.strokeStyle=t.strokeColor,s.beginPath(),e.each(h,function(t,i){if(0===i)s.moveTo(t.x,t.y);else if(this.options.bezierCurve){var e=a(t,h,i);s.bezierCurveTo(e.controlPoints.outer.x,e.controlPoints.outer.y,t.controlPoints.inner.x,t.controlPoints.inner.y,t.x,t.y)}else s.lineTo(t.x,t.y)},this),s.stroke(),this.options.datasetFill&&h.length>0&&(s.lineTo(h[h.length-1].x,this.scale.endPoint),s.lineTo(h[0].x,this.scale.endPoint),s.fillStyle=t.fillColor,s.closePath(),s.fill()),e.each(h,function(t){t.draw()})},this)}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBeginAtZero:!0,scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,scaleShowLine:!0,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<segments.length; i++){%><li><span style="background-color:<%=segments[i].fillColor%>"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>'};i.Type.extend({name:"PolarArea",defaults:s,initialize:function(t){this.segments=[],this.SegmentArc=i.Arc.extend({showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,ctx:this.chart.ctx,innerRadius:0,x:this.chart.width/2,y:this.chart.height/2}),this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,lineArc:!0,width:this.chart.width,height:this.chart.height,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,valuesCount:t.length}),this.updateScaleRange(t),this.scale.update(),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({fillColor:t.color,highlightColor:t.highlight||t.color,label:t.label,value:t.value,outerRadius:this.options.animateScale?0:this.scale.calculateCenterOffset(t.value),circumference:this.options.animateRotate?0:this.scale.getCircumference(),startAngle:1.5*Math.PI})),e||(this.reflow(),this.update())},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=t.value},this),this.scale.valuesCount=this.segments.length},updateScaleRange:function(t){var i=[];e.each(t,function(t){i.push(t.value)});var s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s,{size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2})},update:function(){this.calculateTotal(this.segments),e.each(this.segments,function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.updateScaleRange(this.segments),this.scale.update(),e.extend(this.scale,{xCenter:this.chart.width/2,yCenter:this.chart.height/2}),e.each(this.segments,function(t){t.update({outerRadius:this.scale.calculateCenterOffset(t.value)})},this)},draw:function(t){var i=t||1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.scale.getCircumference(),outerRadius:this.scale.calculateCenterOffset(t.value)},i),t.endAngle=t.startAngle+t.circumference,0===e&&(t.startAngle=1.5*Math.PI),e<this.segments.length-1&&(this.segments[e+1].startAngle=t.endAngle),t.draw()},this),this.scale.draw()}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers;i.Type.extend({name:"Radar",defaults:{scaleShowLine:!0,angleShowLineOut:!0,scaleShowLabels:!1,scaleBeginAtZero:!0,angleLineColor:"rgba(0,0,0,.1)",angleLineWidth:1,pointLabelFontFamily:"'Arial'",pointLabelFontStyle:"normal",pointLabelFontSize:10,pointLabelFontColor:"#666",pointDot:!0,pointDotRadius:3,pointDotStrokeWidth:1,pointHitDetectionRadius:20,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<datasets.length; i++){%><li><span style="background-color:<%=datasets[i].strokeColor%>"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>'},initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx}),this.datasets=[],this.buildScale(t),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,pointColor:i.pointColor,pointStrokeColor:i.pointStrokeColor,points:[]};this.datasets.push(s),e.each(i.data,function(e,n){var o;this.scale.animation||(o=this.scale.getPointPosition(n,this.scale.calculateCenterOffset(e))),s.points.push(new this.PointClass({value:e,label:t.labels[n],datasetLabel:i.label,x:this.options.animation?this.scale.xCenter:o.x,y:this.options.animation?this.scale.yCenter:o.y,strokeColor:i.pointStrokeColor,fillColor:i.pointColor,highlightFill:i.pointHighlightFill||i.pointColor,highlightStroke:i.pointHighlightStroke||i.pointStrokeColor}))},this)},this),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=e.getRelativePosition(t),s=e.getAngleFromPoint({x:this.scale.xCenter,y:this.scale.yCenter},i),n=2*Math.PI/this.scale.valuesCount,o=Math.round((s.angle-1.5*Math.PI)/n),a=[];return(o>=this.scale.valuesCount||0>o)&&(o=0),s.distance<=this.scale.drawingArea&&e.each(this.datasets,function(t){a.push(t.points[o])}),a},buildScale:function(t){this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,angleLineColor:this.options.angleLineColor,angleLineWidth:this.options.angleShowLineOut?this.options.angleLineWidth:0,pointLabelFontColor:this.options.pointLabelFontColor,pointLabelFontSize:this.options.pointLabelFontSize,pointLabelFontFamily:this.options.pointLabelFontFamily,pointLabelFontStyle:this.options.pointLabelFontStyle,height:this.chart.height,width:this.chart.width,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,labels:t.labels,valuesCount:t.datasets[0].data.length}),this.scale.setScaleSize(),this.updateScaleRange(t.datasets),this.scale.buildYLabels()},updateScaleRange:function(t){var i=function(){var i=[];return e.each(t,function(t){t.data?i=i.concat(t.data):e.each(t.points,function(t){i.push(t.value)})}),i}(),s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s)},addData:function(t,i){this.scale.valuesCount++,e.each(t,function(t,e){var s=this.scale.getPointPosition(this.scale.valuesCount,this.scale.calculateCenterOffset(t));this.datasets[e].points.push(new this.PointClass({value:t,label:i,x:s.x,y:s.y,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.labels.push(i),this.reflow(),this.update()},removeData:function(){this.scale.valuesCount--,this.scale.labels.shift(),e.each(this.datasets,function(t){t.points.shift()},this),this.reflow(),this.update()},update:function(){this.eachPoints(function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.scale,{width:this.chart.width,height:this.chart.height,size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2}),this.updateScaleRange(this.datasets),this.scale.setScaleSize(),this.scale.buildYLabels()},draw:function(t){var i=t||1,s=this.chart.ctx;this.clear(),this.scale.draw(),e.each(this.datasets,function(t){e.each(t.points,function(t,e){t.hasValue()&&t.transition(this.scale.getPointPosition(e,this.scale.calculateCenterOffset(t.value)),i)},this),s.lineWidth=this.options.datasetStrokeWidth,s.strokeStyle=t.strokeColor,s.beginPath(),e.each(t.points,function(t,i){0===i?s.moveTo(t.x,t.y):s.lineTo(t.x,t.y)},this),s.closePath(),s.stroke(),s.fillStyle=t.fillColor,s.fill(),e.each(t.points,function(t){t.hasValue()&&t.draw()})},this)}})}.call(this);
\ No newline at end of file
diff --git a/library/Chart.js-1.0.2/LICENSE.md b/library/Chart.js-1.0.2/LICENSE.md
new file mode 100644 (file)
index 0000000..e10bc0f
--- /dev/null
@@ -0,0 +1,7 @@
+Copyright (c) 2013-2015 Nick Downie
+
+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/library/Chart.js-1.0.2/README.md b/library/Chart.js-1.0.2/README.md
new file mode 100644 (file)
index 0000000..7c4fa8b
--- /dev/null
@@ -0,0 +1,20 @@
+# Chart.js 
+
+[![Build Status](https://travis-ci.org/nnnick/Chart.js.svg?branch=master)](https://travis-ci.org/nnnick/Chart.js) [![Code Climate](https://codeclimate.com/github/nnnick/Chart.js/badges/gpa.svg)](https://codeclimate.com/github/nnnick/Chart.js)
+
+
+*Simple HTML5 Charts using the canvas element* [chartjs.org](http://www.chartjs.org)
+
+## Documentation
+
+You can find documentation at [chartjs.org/docs](http://www.chartjs.org/docs/). The markdown files that build the site are available under `/docs`. Please note - in some of the json examples of configuration you might notice some liquid tags - this is just for the generating the site html, please disregard.
+
+## Bugs, issues and contributing
+
+Before submitting an issue or a pull request to the project, please take a moment to look over the [contributing guidelines](https://github.com/nnnick/Chart.js/blob/master/CONTRIBUTING.md) first.
+
+For support using Chart.js, please post questions with the [`chartjs` tag on Stack Overflow](http://stackoverflow.com/questions/tagged/chartjs).
+
+## License
+
+Chart.js is available under the [MIT license](http://opensource.org/licenses/MIT).
diff --git a/library/HTMLPurifier.auto.php b/library/HTMLPurifier.auto.php
deleted file mode 100644 (file)
index 1960c39..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?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/library/HTMLPurifier.autoload.php b/library/HTMLPurifier.autoload.php
deleted file mode 100644 (file)
index 8d40176..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-
-/**
- * @file
- * Convenience file that registers autoload handler for HTML Purifier.
- */
-
-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);
-    }
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier.func.php b/library/HTMLPurifier.func.php
deleted file mode 100644 (file)
index 56a55b2..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/library/HTMLPurifier.includes.php b/library/HTMLPurifier.includes.php
deleted file mode 100644 (file)
index 2ed0f0c..0000000
+++ /dev/null
@@ -1,210 +0,0 @@
-<?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.1.1
- *
- * @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/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/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/SafeEmbed.php';
-require 'HTMLPurifier/AttrTransform/SafeObject.php';
-require 'HTMLPurifier/AttrTransform/SafeParam.php';
-require 'HTMLPurifier/AttrTransform/ScriptRequired.php';
-require 'HTMLPurifier/AttrTransform/Textarea.php';
-require 'HTMLPurifier/ChildDef/Chameleon.php';
-require 'HTMLPurifier/ChildDef/Custom.php';
-require 'HTMLPurifier/ChildDef/Empty.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/Image.php';
-require 'HTMLPurifier/HTMLModule/Legacy.php';
-require 'HTMLPurifier/HTMLModule/List.php';
-require 'HTMLPurifier/HTMLModule/Name.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/Scripting.php';
-require 'HTMLPurifier/HTMLModule/StyleAttribute.php';
-require 'HTMLPurifier/HTMLModule/Tables.php';
-require 'HTMLPurifier/HTMLModule/Target.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/HostBlacklist.php';
-require 'HTMLPurifier/URIFilter/MakeAbsolute.php';
-require 'HTMLPurifier/URIFilter/Munge.php';
-require 'HTMLPurifier/URIScheme/data.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/library/HTMLPurifier.kses.php b/library/HTMLPurifier.kses.php
deleted file mode 100644 (file)
index 3143feb..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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/library/HTMLPurifier.path.php b/library/HTMLPurifier.path.php
deleted file mode 100644 (file)
index 39b1b65..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?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/library/HTMLPurifier.php b/library/HTMLPurifier.php
deleted file mode 100644 (file)
index ba2c7b3..0000000
+++ /dev/null
@@ -1,237 +0,0 @@
-<?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.1.1 - 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.1.1';
-
-    /** Constant with version of HTML Purifier */
-    const VERSION = '4.1.1';
-
-    /** 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/library/HTMLPurifier.safe-includes.php b/library/HTMLPurifier.safe-includes.php
deleted file mode 100644 (file)
index 6402de0..0000000
+++ /dev/null
@@ -1,204 +0,0 @@
-<?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/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/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/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/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/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/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/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/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/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/HostBlacklist.php';
-require_once $__dir . '/HTMLPurifier/URIFilter/MakeAbsolute.php';
-require_once $__dir . '/HTMLPurifier/URIFilter/Munge.php';
-require_once $__dir . '/HTMLPurifier/URIScheme/data.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/library/HTMLPurifier/AttrCollections.php b/library/HTMLPurifier/AttrCollections.php
deleted file mode 100644 (file)
index 555b86d..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef.php b/library/HTMLPurifier/AttrDef.php
deleted file mode 100644 (file)
index b2e4f36..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS.php b/library/HTMLPurifier/AttrDef/CSS.php
deleted file mode 100644 (file)
index 953e706..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php b/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php
deleted file mode 100644 (file)
index 292c040..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS/Background.php b/library/HTMLPurifier/AttrDef/CSS/Background.php
deleted file mode 100644 (file)
index 3a3d20c..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-<?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(' ', strtolower($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/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php b/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php
deleted file mode 100644 (file)
index fae82ea..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS/Border.php b/library/HTMLPurifier/AttrDef/CSS/Border.php
deleted file mode 100644 (file)
index 42a1d1b..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS/Color.php b/library/HTMLPurifier/AttrDef/CSS/Color.php
deleted file mode 100644 (file)
index 07f95a6..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS/Composite.php b/library/HTMLPurifier/AttrDef/CSS/Composite.php
deleted file mode 100644 (file)
index de1289c..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php b/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php
deleted file mode 100644 (file)
index 6599c5b..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS/Filter.php b/library/HTMLPurifier/AttrDef/CSS/Filter.php
deleted file mode 100644 (file)
index 147894b..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS/Font.php b/library/HTMLPurifier/AttrDef/CSS/Font.php
deleted file mode 100644 (file)
index 699ee0b..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS/FontFamily.php b/library/HTMLPurifier/AttrDef/CSS/FontFamily.php
deleted file mode 100644 (file)
index 42c2054..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-<?php
-
-/**
- * Validates a font family list according to CSS spec
- * @todo whitelisting allowed fonts would be nice
- */
-class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
-{
-
-    public function validate($string, $config, $context) {
-        static $generic_names = array(
-            'serif' => true,
-            'sans-serif' => true,
-            'monospace' => true,
-            'fantasy' => true,
-            'cursive' => true
-        );
-
-        // 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])) {
-                $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 (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);
-
-            // These ugly transforms don't pose a security
-            // risk (as \\ and \" might).  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.
-            // (warning: this code relies on the selection of quotation
-            // mark below)
-            $font = str_replace('\\', '\\5C ', $font);
-            $font = str_replace('"',  '\\22 ', $font);
-
-            // complicated font, requires quoting
-            $final .= "\"$font\", "; // note that this will later get turned into &quot;
-        }
-        $final = rtrim($final, ', ');
-        if ($final === '') return false;
-        return $final;
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php b/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php
deleted file mode 100644 (file)
index 4e6b35e..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS/Length.php b/library/HTMLPurifier/AttrDef/CSS/Length.php
deleted file mode 100644 (file)
index a07ec58..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS/ListStyle.php b/library/HTMLPurifier/AttrDef/CSS/ListStyle.php
deleted file mode 100644 (file)
index 4406868..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS/Multiple.php b/library/HTMLPurifier/AttrDef/CSS/Multiple.php
deleted file mode 100644 (file)
index 4d62a40..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS/Number.php b/library/HTMLPurifier/AttrDef/CSS/Number.php
deleted file mode 100644 (file)
index 3f99e12..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS/Percentage.php b/library/HTMLPurifier/AttrDef/CSS/Percentage.php
deleted file mode 100644 (file)
index c34b8fc..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php b/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php
deleted file mode 100644 (file)
index 772c922..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/CSS/URI.php b/library/HTMLPurifier/AttrDef/CSS/URI.php
deleted file mode 100644 (file)
index 1df17dc..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-<?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);
-
-        return "url(\"$result\")";
-
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/AttrDef/Enum.php b/library/HTMLPurifier/AttrDef/Enum.php
deleted file mode 100644 (file)
index 5d603eb..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/HTML/Bool.php b/library/HTMLPurifier/AttrDef/HTML/Bool.php
deleted file mode 100644 (file)
index e06987e..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/HTML/Class.php b/library/HTMLPurifier/AttrDef/HTML/Class.php
deleted file mode 100644 (file)
index 370068d..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/HTML/Color.php b/library/HTMLPurifier/AttrDef/HTML/Color.php
deleted file mode 100644 (file)
index d01e204..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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;
-        if (isset($colors[$string])) return $colors[$string];
-        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/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php b/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php
deleted file mode 100644 (file)
index ae6ea7c..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/HTML/ID.php b/library/HTMLPurifier/AttrDef/HTML/ID.php
deleted file mode 100644 (file)
index 81d0376..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-<?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
-{
-
-    // ref functionality disabled, since we also have to verify
-    // whether or not the ID it refers to exists
-
-    public function validate($id, $config, $context) {
-
-        if (!$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->ref) {
-            $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->ref && */$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/library/HTMLPurifier/AttrDef/HTML/Length.php b/library/HTMLPurifier/AttrDef/HTML/Length.php
deleted file mode 100644 (file)
index a242f9c..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php b/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php
deleted file mode 100644 (file)
index 76d25ed..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/HTML/MultiLength.php b/library/HTMLPurifier/AttrDef/HTML/MultiLength.php
deleted file mode 100644 (file)
index c72fc76..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php b/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php
deleted file mode 100644 (file)
index aa34120..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/HTML/Pixels.php b/library/HTMLPurifier/AttrDef/HTML/Pixels.php
deleted file mode 100644 (file)
index 4cb2c1b..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/Integer.php b/library/HTMLPurifier/AttrDef/Integer.php
deleted file mode 100644 (file)
index d59738d..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/Lang.php b/library/HTMLPurifier/AttrDef/Lang.php
deleted file mode 100644 (file)
index 10e6da5..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/Switch.php b/library/HTMLPurifier/AttrDef/Switch.php
deleted file mode 100644 (file)
index c9e3ed1..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/Text.php b/library/HTMLPurifier/AttrDef/Text.php
deleted file mode 100644 (file)
index c6216cc..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/URI.php b/library/HTMLPurifier/AttrDef/URI.php
deleted file mode 100644 (file)
index 01a6d83..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-<?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 = (bool) $string;
-        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/library/HTMLPurifier/AttrDef/URI/Email.php b/library/HTMLPurifier/AttrDef/URI/Email.php
deleted file mode 100644 (file)
index bfee9d1..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php b/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php
deleted file mode 100644 (file)
index 94c715a..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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@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/library/HTMLPurifier/AttrDef/URI/Host.php b/library/HTMLPurifier/AttrDef/URI/Host.php
deleted file mode 100644 (file)
index 2156c10..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-<?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);
-        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 breaks I18N domain names, but we don't have proper IRI support,
-        // so force users to insert Punycode. If there's complaining we'll
-        // try to fix things into an international friendly form.
-
-        // 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 [ "." ]
-        $match = preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string);
-        if (!$match) return false;
-
-        return $string;
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/AttrDef/URI/IPv4.php b/library/HTMLPurifier/AttrDef/URI/IPv4.php
deleted file mode 100644 (file)
index ec4cf59..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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/library/HTMLPurifier/AttrDef/URI/IPv6.php b/library/HTMLPurifier/AttrDef/URI/IPv6.php
deleted file mode 100644 (file)
index 9454e9b..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform.php b/library/HTMLPurifier/AttrTransform.php
deleted file mode 100644 (file)
index e61d3e0..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/Background.php b/library/HTMLPurifier/AttrTransform/Background.php
deleted file mode 100644 (file)
index 0e1ff24..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/BdoDir.php b/library/HTMLPurifier/AttrTransform/BdoDir.php
deleted file mode 100644 (file)
index 4d1a056..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/BgColor.php b/library/HTMLPurifier/AttrTransform/BgColor.php
deleted file mode 100644 (file)
index ad3916b..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/BoolToCSS.php b/library/HTMLPurifier/AttrTransform/BoolToCSS.php
deleted file mode 100644 (file)
index 51159b6..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/Border.php b/library/HTMLPurifier/AttrTransform/Border.php
deleted file mode 100644 (file)
index 476b0b0..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/EnumToCSS.php b/library/HTMLPurifier/AttrTransform/EnumToCSS.php
deleted file mode 100644 (file)
index 2a5b451..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/ImgRequired.php b/library/HTMLPurifier/AttrTransform/ImgRequired.php
deleted file mode 100644 (file)
index 7f0e4b7..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/ImgSpace.php b/library/HTMLPurifier/AttrTransform/ImgSpace.php
deleted file mode 100644 (file)
index fd84c10..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/Input.php b/library/HTMLPurifier/AttrTransform/Input.php
deleted file mode 100644 (file)
index 1682955..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/Lang.php b/library/HTMLPurifier/AttrTransform/Lang.php
deleted file mode 100644 (file)
index 5869e7f..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/Length.php b/library/HTMLPurifier/AttrTransform/Length.php
deleted file mode 100644 (file)
index ea2f304..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/Name.php b/library/HTMLPurifier/AttrTransform/Name.php
deleted file mode 100644 (file)
index 15315bc..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/NameSync.php b/library/HTMLPurifier/AttrTransform/NameSync.php
deleted file mode 100644 (file)
index a95638c..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/SafeEmbed.php b/library/HTMLPurifier/AttrTransform/SafeEmbed.php
deleted file mode 100644 (file)
index 4da4499..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/SafeObject.php b/library/HTMLPurifier/AttrTransform/SafeObject.php
deleted file mode 100644 (file)
index 1ed7489..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/SafeParam.php b/library/HTMLPurifier/AttrTransform/SafeParam.php
deleted file mode 100644 (file)
index 3f992ec..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-<?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
-    }
-
-    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 'wmode':
-                $attr['value'] = 'window';
-                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/library/HTMLPurifier/AttrTransform/ScriptRequired.php b/library/HTMLPurifier/AttrTransform/ScriptRequired.php
deleted file mode 100644 (file)
index 4499050..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?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/library/HTMLPurifier/AttrTransform/Textarea.php b/library/HTMLPurifier/AttrTransform/Textarea.php
deleted file mode 100644 (file)
index 81ac348..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<?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/library/HTMLPurifier/AttrTypes.php b/library/HTMLPurifier/AttrTypes.php
deleted file mode 100644 (file)
index fc2ea4e..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-<?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() {
-        // 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();
-
-        // 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);
-    }
-
-    /**
-     * 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/library/HTMLPurifier/AttrValidator.php b/library/HTMLPurifier/AttrValidator.php
deleted file mode 100644 (file)
index 829a0f8..0000000
+++ /dev/null
@@ -1,162 +0,0 @@
-<?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/library/HTMLPurifier/Bootstrap.php b/library/HTMLPurifier/Bootstrap.php
deleted file mode 100644 (file)
index 559f61a..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-<?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@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;
-        require 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')) {
-            $compat = version_compare(PHP_VERSION, '5.1.2', '<=') &&
-                      version_compare(PHP_VERSION, '5.1.0', '>=');
-            foreach ($funcs as $func) {
-                if (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/library/HTMLPurifier/CSSDefinition.php b/library/HTMLPurifier/CSSDefinition.php
deleted file mode 100644 (file)
index 6a2e6f5..0000000
+++ /dev/null
@@ -1,292 +0,0 @@
-<?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);
-
-        // partial support
-        $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(array('nowrap'));
-
-        if ($config->get('CSS.Proprietary')) {
-            $this->doSetupProprietary($config);
-        }
-
-        if ($config->get('CSS.AllowTricky')) {
-            $this->doSetupTricky($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();
-
-    }
-
-    protected function doSetupTricky($config) {
-        $this->info['display'] = new HTMLPurifier_AttrDef_Enum(array(
-            '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'
-        ));
-        $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(array(
-            'visible', 'hidden', 'collapse'
-        ));
-        $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll'));
-    }
-
-
-    /**
-     * 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_attributes = $config->get('CSS.AllowedProperties');
-        if ($allowed_attributes !== null) {
-            foreach ($this->info as $name => $d) {
-                if(!isset($allowed_attributes[$name])) unset($this->info[$name]);
-                unset($allowed_attributes[$name]);
-            }
-            // emit errors
-            foreach ($allowed_attributes 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);
-            }
-        }
-
-    }
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/ChildDef.php b/library/HTMLPurifier/ChildDef.php
deleted file mode 100644 (file)
index c5d5216..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-<?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/library/HTMLPurifier/ChildDef/Chameleon.php b/library/HTMLPurifier/ChildDef/Chameleon.php
deleted file mode 100644 (file)
index 15c364e..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-<?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/library/HTMLPurifier/ChildDef/Custom.php b/library/HTMLPurifier/ChildDef/Custom.php
deleted file mode 100644 (file)
index b68047b..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-<?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/library/HTMLPurifier/ChildDef/Empty.php b/library/HTMLPurifier/ChildDef/Empty.php
deleted file mode 100644 (file)
index 13171f6..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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/library/HTMLPurifier/ChildDef/Optional.php b/library/HTMLPurifier/ChildDef/Optional.php
deleted file mode 100644 (file)
index 32bcb98..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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/library/HTMLPurifier/ChildDef/Required.php b/library/HTMLPurifier/ChildDef/Required.php
deleted file mode 100644 (file)
index 4889f24..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-<?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/library/HTMLPurifier/ChildDef/StrictBlockquote.php b/library/HTMLPurifier/ChildDef/StrictBlockquote.php
deleted file mode 100644 (file)
index dfae8a6..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-<?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/library/HTMLPurifier/ChildDef/Table.php b/library/HTMLPurifier/ChildDef/Table.php
deleted file mode 100644 (file)
index 34f0227..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-<?php
-
-/**
- * Definition for tables
- */
-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
-
-        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 'tr':
-                        case 'tbody':
-                            $content[] = $collection;
-                            break;
-                        case 'caption':
-                            if ($caption !== false) break;
-                            $caption = $collection;
-                            break;
-                        case 'thead':
-                        case 'tfoot':
-                            // access the appropriate variable, $thead or $tfoot
-                            $var = $collection[$tag_index]->name;
-                            if ($$var === false) {
-                                $$var = $collection;
-                            } else {
-                                // transmutate the first and less entries into
-                                // tbody tags, and then put into content
-                                $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);
-        foreach ($content as $token_array) $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/library/HTMLPurifier/Config.php b/library/HTMLPurifier/Config.php
deleted file mode 100644 (file)
index 2a334b0..0000000
+++ /dev/null
@@ -1,580 +0,0 @@
-<?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.1.1';
-
-    /**
-     * 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;
-
-    /**
-     * 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)
-     */
-    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 md5 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] = md5(serialize($batch));
-        }
-        return $this->serials[$namespace];
-    }
-
-    /**
-     * Returns a md5 signature for the entire configuration object
-     * that uniquely identifies that particular configuration
-     */
-    public function getSerial() {
-        if (empty($this->serial)) {
-            $this->serial = md5(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.
-     */
-    public function getHTMLDefinition($raw = false) {
-        return $this->getDefinition('HTML', $raw);
-    }
-
-    /**
-     * 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.
-     */
-    public function getCSSDefinition($raw = false) {
-        return $this->getDefinition('CSS', $raw);
-    }
-
-    /**
-     * Retrieves a definition
-     * @param $type Type of definition: HTML, CSS, etc
-     * @param $raw  Whether or not definition should be returned raw
-     */
-    public function getDefinition($type, $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) {
-            // see if we can quickly supply a definition
-            if (!empty($this->definitions[$type])) {
-                if (!$this->definitions[$type]->setup) {
-                    $this->definitions[$type]->setup($this);
-                    $cache->set($this->definitions[$type], $this);
-                }
-                return $this->definitions[$type];
-            }
-            // memory check missed, try cache
-            $this->definitions[$type] = $cache->get($this);
-            if ($this->definitions[$type]) {
-                // definition in cache, return it
-                return $this->definitions[$type];
-            }
-        } elseif (
-            !empty($this->definitions[$type]) &&
-            !$this->definitions[$type]->setup
-        ) {
-            // raw requested, raw in memory, quick return
-            return $this->definitions[$type];
-        }
-        // quick checks failed, let's create the object
-        if ($type == 'HTML') {
-            $this->definitions[$type] = new HTMLPurifier_HTMLDefinition();
-        } elseif ($type == 'CSS') {
-            $this->definitions[$type] = new HTMLPurifier_CSSDefinition();
-        } elseif ($type == 'URI') {
-            $this->definitions[$type] = new HTMLPurifier_URIDefinition();
-        } else {
-            throw new HTMLPurifier_Exception("Definition of $type type not supported");
-        }
-        // quick abort if raw
-        if ($raw) {
-            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");
-            }
-            return $this->definitions[$type];
-        }
-        // set it up
-        $this->lock = $type;
-        $this->definitions[$type]->setup($this);
-        $this->lock = null;
-        // save in cache
-        $cache->set($this->definitions[$type], $this);
-        return $this->definitions[$type];
-    }
-
-    /**
-     * 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;
-        unset($this->parser);
-    }
-
-    /**
-     * Produces a nicely formatted error message by supplying the
-     * stack frame information from two levels up and OUTSIDE of
-     * HTMLPurifier_Config.
-     */
-    protected function triggerError($msg, $no) {
-        // determine previous stack frame
-        $backtrace = debug_backtrace();
-        if ($this->chatty && isset($backtrace[1])) {
-            $frame = $backtrace[1];
-            $extra = " on line {$frame['line']} in file {$frame['file']}";
-        } else {
-            $extra = '';
-        }
-        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/library/HTMLPurifier/ConfigSchema.php b/library/HTMLPurifier/ConfigSchema.php
deleted file mode 100644 (file)
index 67be5c7..0000000
+++ /dev/null
@@ -1,158 +0,0 @@
-<?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() {
-        return unserialize(file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser'));
-    }
-
-    /**
-     * 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/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php b/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php
deleted file mode 100644 (file)
index c05668a..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<?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/library/HTMLPurifier/ConfigSchema/Builder/Xml.php b/library/HTMLPurifier/ConfigSchema/Builder/Xml.php
deleted file mode 100644 (file)
index 244561a..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-<?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/library/HTMLPurifier/ConfigSchema/Exception.php b/library/HTMLPurifier/ConfigSchema/Exception.php
deleted file mode 100644 (file)
index 2671516..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-/**
- * Exceptions related to configuration schema
- */
-class HTMLPurifier_ConfigSchema_Exception extends HTMLPurifier_Exception
-{
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/ConfigSchema/Interchange.php b/library/HTMLPurifier/ConfigSchema/Interchange.php
deleted file mode 100644 (file)
index 91a5aa7..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-<?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/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php b/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php
deleted file mode 100644 (file)
index ac8be0d..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-<?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/library/HTMLPurifier/ConfigSchema/Interchange/Id.php b/library/HTMLPurifier/ConfigSchema/Interchange/Id.php
deleted file mode 100644 (file)
index b9b3c6f..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php b/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php
deleted file mode 100644 (file)
index 785b72c..0000000
+++ /dev/null
@@ -1,180 +0,0 @@
-<?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/library/HTMLPurifier/ConfigSchema/Validator.php b/library/HTMLPurifier/ConfigSchema/Validator.php
deleted file mode 100644 (file)
index f374f6a..0000000
+++ /dev/null
@@ -1,206 +0,0 @@
-<?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/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php b/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php
deleted file mode 100644 (file)
index b95aea1..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-<?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/library/HTMLPurifier/ConfigSchema/schema.ser b/library/HTMLPurifier/ConfigSchema/schema.ser
deleted file mode 100644 (file)
index 22b8d54..0000000
Binary files a/library/HTMLPurifier/ConfigSchema/schema.ser and /dev/null differ
diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt
deleted file mode 100644 (file)
index 0517fed..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt
deleted file mode 100644 (file)
index 249edd6..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt
deleted file mode 100644 (file)
index 9a8fa6a..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt
deleted file mode 100644 (file)
index b017883..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt
deleted file mode 100644 (file)
index e774b82..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt
deleted file mode 100644 (file)
index 533165e..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt
deleted file mode 100644 (file)
index 9eb7e38..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt
deleted file mode 100644 (file)
index 2f17bf4..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt
deleted file mode 100644 (file)
index 52654b5..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt
deleted file mode 100644 (file)
index 6440d21..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt
deleted file mode 100644 (file)
index f31d226..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt
deleted file mode 100644 (file)
index 5f2b5e3..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Attr.IDBlacklist
-TYPE: list
-DEFAULT: array()
-DESCRIPTION: Array of IDs not allowed in the document.
---# vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt
deleted file mode 100644 (file)
index 6f58245..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt
deleted file mode 100644 (file)
index cc49d43..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt b/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt
deleted file mode 100644 (file)
index 2c5924a..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt
deleted file mode 100644 (file)
index d5caa1b..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt
deleted file mode 100644 (file)
index 2a47648..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt
deleted file mode 100644 (file)
index 663064a..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-AutoFormat.DisplayLinkURI
-TYPE: bool
-VERSION: 3.2.0
-DEFAULT: false
---DESCRIPTION--
-<p>
-  This directive turns on the in-text display of URIs in &lt;a&gt; 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/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt
deleted file mode 100644 (file)
index 3a48ba9..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt
deleted file mode 100644 (file)
index db58b13..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt
deleted file mode 100644 (file)
index 7996488..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt
deleted file mode 100644 (file)
index 35c393b..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt
deleted file mode 100644 (file)
index ca17eb1..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt
deleted file mode 100644 (file)
index 34657ba..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-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>&lt;a&gt;&lt;/a&gt;</code> but not
-    <code>&lt;br /&gt;</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/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt b/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt
deleted file mode 100644 (file)
index dde990a..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt b/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt
deleted file mode 100644 (file)
index b324608..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt b/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt
deleted file mode 100644 (file)
index 748be0e..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt b/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt
deleted file mode 100644 (file)
index 460112e..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt b/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt
deleted file mode 100644 (file)
index 5cb7dda..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt b/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt
deleted file mode 100644 (file)
index 7a32914..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt b/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt
deleted file mode 100644 (file)
index 148eedb..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt b/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt
deleted file mode 100644 (file)
index c486724..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt b/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt
deleted file mode 100644 (file)
index 5403650..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt
deleted file mode 100644 (file)
index 568cbf3..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt
deleted file mode 100644 (file)
index d731791..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt
deleted file mode 100644 (file)
index 08b381d..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-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.
---# vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt
deleted file mode 100644 (file)
index 64b114f..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt
deleted file mode 100644 (file)
index 36f16e0..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt
deleted file mode 100644 (file)
index 8bfb47c..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt
deleted file mode 100644 (file)
index 4d5b505..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt
deleted file mode 100644 (file)
index a7a5b24..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt
deleted file mode 100644 (file)
index abb4999..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt
deleted file mode 100644 (file)
index 915391e..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt
deleted file mode 100644 (file)
index 233fca1..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt
deleted file mode 100644 (file)
index 8983e2c..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt
deleted file mode 100644 (file)
index eb841a7..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt
deleted file mode 100644 (file)
index 4070c2a..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt b/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt
deleted file mode 100644 (file)
index a4cd966..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt b/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt
deleted file mode 100644 (file)
index 3db50ef..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-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-&gt;addFilter()</code>
-  method. Specify an array of concrete implementations.
-</p>
---# vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt b/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt
deleted file mode 100644 (file)
index 16829bc..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-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 &lt;, &gt; and &amp;
-  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/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt b/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt
deleted file mode 100644 (file)
index 7f95f54..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt b/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt
deleted file mode 100644 (file)
index 6c231b2..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt b/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt
deleted file mode 100644 (file)
index 078d087..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt b/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt
deleted file mode 100644 (file)
index 7fa6536..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-Filter.YouTube
-TYPE: bool
-VERSION: 3.1.0
-DEFAULT: false
---DESCRIPTION--
-<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/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt
deleted file mode 100644 (file)
index 3e231d2..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-HTML.Allowed
-TYPE: itext/null
-VERSION: 2.0.0
-DEFAULT: NULL
---DESCRIPTION--
-
-<p>
-    This is a convenience directive that rolls the functionality of
-    %HTML.AllowedElements and %HTML.AllowedAttributes into one directive.
-    Specify elements and attributes that are allowed using:
-    <code>element1[attr1|attr2],element2...</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/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt
deleted file mode 100644 (file)
index fcf093f..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt
deleted file mode 100644 (file)
index 888d558..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-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.  Note that this
-    method is subtractive: it does its job by taking away from HTML Purifier
-    usual feature set, so you cannot add a tag that HTML Purifier never
-    supported in the first place (like embed, form or head).  If you
-    change this, you probably also want to change %HTML.AllowedAttributes.
-</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/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt
deleted file mode 100644 (file)
index 5a59a55..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt
deleted file mode 100644 (file)
index 151fb7b..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt
deleted file mode 100644 (file)
index 45ae469..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-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>&lt;blockquote&gt;Foo&lt;/blockquote&gt;</code> would become
-    <code>&lt;blockquote&gt;&lt;p&gt;Foo&lt;/p&gt;&lt;/blockquote&gt;</code>.
-    The <code>&lt;p&gt;</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/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt
deleted file mode 100644 (file)
index 5246188..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt
deleted file mode 100644 (file)
index a64e3d7..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt
deleted file mode 100644 (file)
index 103db75..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt
deleted file mode 100644 (file)
index 229ae02..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt
deleted file mode 100644 (file)
index 9dab497..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt
deleted file mode 100644 (file)
index 57358f9..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-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@attr</code>. To disallow <code>href</code>
-    attributes in <code>a</code> tags, set this directive to
-    <code>a@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/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt
deleted file mode 100644 (file)
index 93a53e1..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt
deleted file mode 100644 (file)
index e424c38..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt
deleted file mode 100644 (file)
index 62e8e16..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt
deleted file mode 100644 (file)
index dfb7204..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt
deleted file mode 100644 (file)
index cdda09a..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt
deleted file mode 100644 (file)
index ceb342e..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt
deleted file mode 100644 (file)
index a8b1de5..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt
deleted file mode 100644 (file)
index b4c271b..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt
deleted file mode 100644 (file)
index 4186ccd..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt
deleted file mode 100644 (file)
index 996762b..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt
deleted file mode 100644 (file)
index 89133b1..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-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.
---# vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt
deleted file mode 100644 (file)
index 2a47e38..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt b/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt
deleted file mode 100644 (file)
index 08921fd..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt b/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt
deleted file mode 100644 (file)
index 93398e8..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt b/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt
deleted file mode 100644 (file)
index 79f8ad8..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt b/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt
deleted file mode 100644 (file)
index 232b023..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-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>&lt;el b="" a="" c="" /&gt;</code>
-  to <code>&lt;el a="" b="" c="" /&gt;</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/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt b/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt
deleted file mode 100644 (file)
index 06bab00..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt b/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt
deleted file mode 100644 (file)
index 071bc02..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt
deleted file mode 100644 (file)
index ae3a913..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-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> URI scheme, but it is not
-enabled by default.
---# vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt
deleted file mode 100644 (file)
index 876f068..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt
deleted file mode 100644 (file)
index 728e378..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt
deleted file mode 100644 (file)
index f05312b..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt
deleted file mode 100644 (file)
index 80cfea9..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt
deleted file mode 100644 (file)
index 71ce025..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt
deleted file mode 100644 (file)
index 13c122c..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt
deleted file mode 100644 (file)
index abcc1ef..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt
deleted file mode 100644 (file)
index 51e6ea9..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-URI.DisableResources
-TYPE: bool
-VERSION: 1.3.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>
---# vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt
deleted file mode 100644 (file)
index ee83b12..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt
deleted file mode 100644 (file)
index 0b6df76..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt
deleted file mode 100644 (file)
index 4214900..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt
deleted file mode 100644 (file)
index 58c81dc..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-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>&lt;a href=""&gt;</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/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt
deleted file mode 100644 (file)
index 6fce0fd..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-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>&lt;img src=""&gt;</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/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt
deleted file mode 100644 (file)
index 0d00f62..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt b/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt
deleted file mode 100644 (file)
index 23331a4..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-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/library/HTMLPurifier/ConfigSchema/schema/info.ini b/library/HTMLPurifier/ConfigSchema/schema/info.ini
deleted file mode 100644 (file)
index 5de4505..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-name = "HTML Purifier"
-
-; vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/ContentSets.php b/library/HTMLPurifier/ContentSets.php
deleted file mode 100644 (file)
index 3b6e96f..0000000
+++ /dev/null
@@ -1,155 +0,0 @@
-<?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/library/HTMLPurifier/Context.php b/library/HTMLPurifier/Context.php
deleted file mode 100644 (file)
index 9ddf0c5..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-<?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/library/HTMLPurifier/Definition.php b/library/HTMLPurifier/Definition.php
deleted file mode 100644 (file)
index a7408c9..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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;
-
-    /**
-     * 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/library/HTMLPurifier/DefinitionCache.php b/library/HTMLPurifier/DefinitionCache.php
deleted file mode 100644 (file)
index c6e1e38..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-<?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/library/HTMLPurifier/DefinitionCache/Decorator.php b/library/HTMLPurifier/DefinitionCache/Decorator.php
deleted file mode 100644 (file)
index b0fb6d0..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-<?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/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php b/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php
deleted file mode 100644 (file)
index d4cc35c..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?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/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php b/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php
deleted file mode 100644 (file)
index 18f16d3..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-<?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/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in b/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in
deleted file mode 100644 (file)
index 21a8fcf..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-<?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/library/HTMLPurifier/DefinitionCache/Null.php b/library/HTMLPurifier/DefinitionCache/Null.php
deleted file mode 100644 (file)
index 41d97e7..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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/library/HTMLPurifier/DefinitionCache/Serializer.php b/library/HTMLPurifier/DefinitionCache/Serializer.php
deleted file mode 100644 (file)
index 7a6aa93..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-<?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));
-    }
-
-    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));
-    }
-
-    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));
-    }
-
-    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
-     * @return Number of bytes written if success, or false if failure.
-     */
-    private function _write($file, $data) {
-        return file_put_contents($file, $data);
-    }
-
-    /**
-     * Prepares the directory that this type stores the serials in
-     * @return True if successful
-     */
-    private function _prepareDir($config) {
-        $directory = $this->generateDirectoryPath($config);
-        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)) {
-                return false;
-            }
-            $old = umask(0022); // disable group and world writes
-            mkdir($directory);
-            umask($old);
-        } elseif (!$this->_testPermissions($directory)) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Tests permissions on a directory and throws out friendly
-     * error messages and attempts to chmod it itself if possible
-     */
-    private function _testPermissions($dir) {
-        // 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($dir, 0755);
-                return true;
-            } elseif (filegroup($dir) === posix_getgid()) {
-                $chmod = '775';
-            } else {
-                // PHP's probably running as nobody, so we'll
-                // need to give global permissions
-                $chmod = '777';
-            }
-            trigger_error('Directory '.$dir.' not writable, '.
-                'please chmod to ' . $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/library/HTMLPurifier/DefinitionCache/Serializer/README b/library/HTMLPurifier/DefinitionCache/Serializer/README
deleted file mode 100644 (file)
index 2e35c1c..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-This is a dummy file to prevent Git from ignoring this empty directory.
-
-    vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/DefinitionCacheFactory.php b/library/HTMLPurifier/DefinitionCacheFactory.php
deleted file mode 100644 (file)
index a6ead62..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-<?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/library/HTMLPurifier/Doctype.php b/library/HTMLPurifier/Doctype.php
deleted file mode 100644 (file)
index 1e3c574..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-<?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/library/HTMLPurifier/DoctypeRegistry.php b/library/HTMLPurifier/DoctypeRegistry.php
deleted file mode 100644 (file)
index 86049e9..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-<?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/library/HTMLPurifier/ElementDef.php b/library/HTMLPurifier/ElementDef.php
deleted file mode 100644 (file)
index 5498d95..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-<?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();
-
-    /**
-     * Indexed list of tag's HTMLPurifier_AttrTransform to be done before validation
-     */
-    public $attr_transform_pre = array();
-
-    /**
-     * Indexed list of tag's 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->attr_transform_pre, $def->attr_transform_pre);
-        $this->_mergeAssocArray($this->attr_transform_post, $def->attr_transform_post);
-        $this->_mergeAssocArray($this->excludes, $def->excludes);
-
-        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/library/HTMLPurifier/Encoder.php b/library/HTMLPurifier/Encoder.php
deleted file mode 100644 (file)
index 2b3140c..0000000
+++ /dev/null
@@ -1,426 +0,0 @@
-<?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() {}
-
-    /**
-     * 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@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;
-    }
-
-    /**
-     * 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 = function_exists('iconv');
-        set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
-        if ($iconv && !$config->get('Test.ForceNoIconv')) {
-            $str = iconv($encoding, 'utf-8//IGNORE', $str);
-            if ($str === false) {
-                // $encoding is not a valid encoding
-                restore_error_handler();
-                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, HTMLPurifier_Encoder::testEncodingSupportsASCII($encoding));
-            restore_error_handler();
-            return $str;
-        } elseif ($encoding === 'iso-8859-1') {
-            $str = utf8_encode($str);
-            restore_error_handler();
-            return $str;
-        }
-        trigger_error('Encoding not supported, please install iconv', 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 ($encoding === 'utf-8') return $str;
-        static $iconv = null;
-        if ($iconv === null) $iconv = function_exists('iconv');
-        if ($escape = $config->get('Core.EscapeNonASCIICharacters')) {
-            $str = HTMLPurifier_Encoder::convertToASCIIDumbLossless($str);
-        }
-        set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
-        if ($iconv && !$config->get('Test.ForceNoIconv')) {
-            // Undo our previous fix in convertToUTF8, otherwise iconv will barf
-            $ascii_fix = HTMLPurifier_Encoder::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 = iconv('utf-8', $encoding . '//IGNORE', $str);
-            restore_error_handler();
-            return $str;
-        } elseif ($encoding === 'iso-8859-1') {
-            $str = utf8_decode($str);
-            restore_error_handler();
-            return $str;
-        }
-        trigger_error('Encoding not supported', E_USER_ERROR);
-    }
-
-    /**
-     * 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;
-    }
-
-    /**
-     * 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) {
-        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();
-        set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
-        if (iconv('UTF-8', $encoding, 'a') === false) return false;
-        for ($i = 0x20; $i <= 0x7E; $i++) { // all printable ASCII chars
-            $c = chr($i); // UTF-8 char
-            $r = iconv('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 && iconv($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[iconv($encoding, 'UTF-8//IGNORE', $c)] = $c;
-            }
-        }
-        restore_error_handler();
-        $encodings[$encoding] = $ret;
-        return $ret;
-    }
-
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/EntityLookup.php b/library/HTMLPurifier/EntityLookup.php
deleted file mode 100644 (file)
index b4dfce9..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<?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/library/HTMLPurifier/EntityLookup/entities.ser b/library/HTMLPurifier/EntityLookup/entities.ser
deleted file mode 100644 (file)
index f2b8b8f..0000000
+++ /dev/null
@@ -1 +0,0 @@
-a:246:{s:4:"nbsp";s:2:" ";s:5:"iexcl";s:2:"¡";s:4:"cent";s:2:"¢";s:5:"pound";s:2:"£";s:6:"curren";s:2:"¤";s:3:"yen";s:2:"¥";s:6:"brvbar";s:2:"¦";s:4:"sect";s:2:"§";s:3:"uml";s:2:"¨";s:4:"copy";s:2:"©";s:4:"ordf";s:2:"ª";s:5:"laquo";s:2:"«";s:3:"not";s:2:"¬";s:3:"shy";s:2:"­";s:3:"reg";s:2:"®";s:4:"macr";s:2:"¯";s:3:"deg";s:2:"°";s:6:"plusmn";s:2:"±";s:5:"acute";s:2:"´";s:5:"micro";s:2:"µ";s:4:"para";s:2:"¶";s:6:"middot";s:2:"·";s:5:"cedil";s:2:"¸";s:4:"ordm";s:2:"º";s:5:"raquo";s:2:"»";s:6:"iquest";s:2:"¿";s:6:"Agrave";s:2:"À";s:6:"Aacute";s:2:"Á";s:5:"Acirc";s:2:"Â";s:6:"Atilde";s:2:"Ã";s:4:"Auml";s:2:"Ä";s:5:"Aring";s:2:"Å";s:5:"AElig";s:2:"Æ";s:6:"Ccedil";s:2:"Ç";s:6:"Egrave";s:2:"È";s:6:"Eacute";s:2:"É";s:5:"Ecirc";s:2:"Ê";s:4:"Euml";s:2:"Ë";s:6:"Igrave";s:2:"Ì";s:6:"Iacute";s:2:"Í";s:5:"Icirc";s:2:"Î";s:4:"Iuml";s:2:"Ï";s:3:"ETH";s:2:"Ð";s:6:"Ntilde";s:2:"Ñ";s:6:"Ograve";s:2:"Ò";s:6:"Oacute";s:2:"Ó";s:5:"Ocirc";s:2:"Ô";s:6:"Otilde";s:2:"Õ";s:4:"Ouml";s:2:"Ö";s:5:"times";s:2:"×";s:6:"Oslash";s:2:"Ø";s:6:"Ugrave";s:2:"Ù";s:6:"Uacute";s:2:"Ú";s:5:"Ucirc";s:2:"Û";s:4:"Uuml";s:2:"Ü";s:6:"Yacute";s:2:"Ý";s:5:"THORN";s:2:"Þ";s:5:"szlig";s:2:"ß";s:6:"agrave";s:2:"à";s:6:"aacute";s:2:"á";s:5:"acirc";s:2:"â";s:6:"atilde";s:2:"ã";s:4:"auml";s:2:"ä";s:5:"aring";s:2:"å";s:5:"aelig";s:2:"æ";s:6:"ccedil";s:2:"ç";s:6:"egrave";s:2:"è";s:6:"eacute";s:2:"é";s:5:"ecirc";s:2:"ê";s:4:"euml";s:2:"ë";s:6:"igrave";s:2:"ì";s:6:"iacute";s:2:"í";s:5:"icirc";s:2:"î";s:4:"iuml";s:2:"ï";s:3:"eth";s:2:"ð";s:6:"ntilde";s:2:"ñ";s:6:"ograve";s:2:"ò";s:6:"oacute";s:2:"ó";s:5:"ocirc";s:2:"ô";s:6:"otilde";s:2:"õ";s:4:"ouml";s:2:"ö";s:6:"divide";s:2:"÷";s:6:"oslash";s:2:"ø";s:6:"ugrave";s:2:"ù";s:6:"uacute";s:2:"ú";s:5:"ucirc";s:2:"û";s:4:"uuml";s:2:"ü";s:6:"yacute";s:2:"ý";s:5:"thorn";s:2:"þ";s:4:"yuml";s:2:"ÿ";s:4:"quot";s:1:""";s:3:"amp";s:1:"&";s:2:"lt";s:1:"<";s:2:"gt";s:1:">";s:4:"apos";s:1:"'";s:5:"OElig";s:2:"Œ";s:5:"oelig";s:2:"œ";s:6:"Scaron";s:2:"Š";s:6:"scaron";s:2:"š";s:4:"Yuml";s:2:"Ÿ";s:4:"circ";s:2:"ˆ";s:5:"tilde";s:2:"˜";s:4:"ensp";s:3:" ";s:4:"emsp";s:3:" ";s:6:"thinsp";s:3:" ";s:4:"zwnj";s:3:"‌";s:3:"zwj";s:3:"‍";s:3:"lrm";s:3:"‎";s:3:"rlm";s:3:"‏";s:5:"ndash";s:3:"–";s:5:"mdash";s:3:"—";s:5:"lsquo";s:3:"‘";s:5:"rsquo";s:3:"’";s:5:"sbquo";s:3:"‚";s:5:"ldquo";s:3:"“";s:5:"rdquo";s:3:"”";s:5:"bdquo";s:3:"„";s:6:"dagger";s:3:"†";s:6:"Dagger";s:3:"‡";s:6:"permil";s:3:"‰";s:6:"lsaquo";s:3:"‹";s:6:"rsaquo";s:3:"›";s:4:"euro";s:3:"€";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:"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:6:"sigmaf";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:8:"thetasym";s:2:"ϑ";s:5:"upsih";s:2:"ϒ";s:3:"piv";s:2:"ϖ";s:4:"bull";s:3:"•";s:6:"hellip";s:3:"…";s:5:"prime";s:3:"′";s:5:"Prime";s:3:"″";s:5:"oline";s:3:"‾";s:5:"frasl";s:3:"⁄";s:6:"weierp";s:3:"℘";s:5:"image";s:3:"ℑ";s:4:"real";s:3:"ℜ";s:5:"trade";s:3:"™";s:7:"alefsym";s:3:"ℵ";s:4:"larr";s:3:"←";s:4:"uarr";s:3:"↑";s:4:"rarr";s:3:"→";s:4:"darr";s:3:"↓";s:4:"harr";s:3:"↔";s:5:"crarr";s:3:"↵";s:4:"lArr";s:3:"⇐";s:4:"uArr";s:3:"⇑";s:4:"rArr";s:3:"⇒";s:4:"dArr";s:3:"⇓";s:4:"hArr";s:3:"⇔";s:6:"forall";s:3:"∀";s:4:"part";s:3:"∂";s:5:"exist";s:3:"∃";s:5:"empty";s:3:"∅";s:5:"nabla";s:3:"∇";s:4:"isin";s:3:"∈";s:5:"notin";s:3:"∉";s:2:"ni";s:3:"∋";s:4:"prod";s:3:"∏";s:3:"sum";s:3:"∑";s:5:"minus";s:3:"−";s:6:"lowast";s:3:"∗";s:5:"radic";s:3:"√";s:4:"prop";s:3:"∝";s:5:"infin";s:3:"∞";s:3:"ang";s:3:"∠";s:3:"and";s:3:"∧";s:2:"or";s:3:"∨";s:3:"cap";s:3:"∩";s:3:"cup";s:3:"∪";s:3:"int";s:3:"∫";s:3:"sim";s:3:"∼";s:4:"cong";s:3:"≅";s:5:"asymp";s:3:"≈";s:2:"ne";s:3:"≠";s:5:"equiv";s:3:"≡";s:2:"le";s:3:"≤";s:2:"ge";s:3:"≥";s:3:"sub";s:3:"⊂";s:3:"sup";s:3:"⊃";s:4:"nsub";s:3:"⊄";s:4:"sube";s:3:"⊆";s:4:"supe";s:3:"⊇";s:5:"oplus";s:3:"⊕";s:6:"otimes";s:3:"⊗";s:4:"perp";s:3:"⊥";s:4:"sdot";s:3:"⋅";s:5:"lceil";s:3:"⌈";s:5:"rceil";s:3:"⌉";s:6:"lfloor";s:3:"⌊";s:6:"rfloor";s:3:"⌋";s:4:"lang";s:3:"〈";s:4:"rang";s:3:"〉";s:3:"loz";s:3:"◊";s:6:"spades";s:3:"♠";s:5:"clubs";s:3:"♣";s:6:"hearts";s:3:"♥";s:5:"diams";s:3:"♦";}
\ No newline at end of file
diff --git a/library/HTMLPurifier/EntityParser.php b/library/HTMLPurifier/EntityParser.php
deleted file mode 100644 (file)
index 8c38447..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-<?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/library/HTMLPurifier/ErrorCollector.php b/library/HTMLPurifier/ErrorCollector.php
deleted file mode 100644 (file)
index 6713eaf..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-<?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/library/HTMLPurifier/ErrorStruct.php b/library/HTMLPurifier/ErrorStruct.php
deleted file mode 100644 (file)
index 9bc8996..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-<?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/library/HTMLPurifier/Exception.php b/library/HTMLPurifier/Exception.php
deleted file mode 100644 (file)
index be85b4c..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?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/library/HTMLPurifier/Filter.php b/library/HTMLPurifier/Filter.php
deleted file mode 100644 (file)
index 9a0e7b0..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-<?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/library/HTMLPurifier/Filter/ExtractStyleBlocks.php b/library/HTMLPurifier/Filter/ExtractStyleBlocks.php
deleted file mode 100644 (file)
index bbf78a6..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-<?php
-
-/**
- * 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;
-
-    public function __construct() {
-        $this->_tidy = new csstidy();
-    }
-
-    /**
-     * 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);
-        $this->_tidy->parse($css);
-        $css_definition = $config->getDefinition('CSS');
-        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
-                if ($selector[0] === '+') {
-                    if ($selector !== '' && $selector[0] === '+') continue;
-                }
-                if (!empty($scopes)) {
-                    $new_selector = array(); // because multiple ones are possible
-                    $selectors = array_map('trim', explode(',', $selector));
-                    foreach ($scopes as $s1) {
-                        foreach ($selectors as $s2) {
-                            $new_selector[] = "$s1 $s2";
-                        }
-                    }
-                    $selector = implode(', ', $new_selector); // now it's a string
-                }
-                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;
-            }
-            $this->_tidy->css[$k] = $new_decls;
-        }
-        // remove stuff that shouldn't be used, could be reenabled
-        // after security risks are analyzed
-        $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/library/HTMLPurifier/Filter/YouTube.php b/library/HTMLPurifier/Filter/YouTube.php
deleted file mode 100644 (file)
index 23df221..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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('--', '-&#45;', $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/library/HTMLPurifier/Generator.php b/library/HTMLPurifier/Generator.php
deleted file mode 100644 (file)
index 4a62417..0000000
+++ /dev/null
@@ -1,224 +0,0 @@
-<?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;
-
-    /**
-     * 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->_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
-        $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)) {
-                    $flash = array_pop($this->_flashStack);
-                    $compat_token = new HTMLPurifier_Token_Empty("embed");
-                    foreach ($flash->attr as $name => $val) {
-                        if ($name == "classid") continue;
-                        if ($name == "type") continue;
-                        if ($name == "data") $name = "src";
-                        $compat_token->attr[$name] = $val;
-                    }
-                    foreach ($flash->param as $name => $val) {
-                        if ($name == "movie") $name = "src";
-                        $compat_token->attr[$name] = $val;
-                    }
-                    $_extra = "<!--[if IE]>".$this->generateFromToken($compat_token)."<![endif]-->";
-                }
-            }
-            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;
-                }
-            }
-            $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 = ENT_COMPAT) {
-        return htmlspecialchars($string, $quote, 'UTF-8');
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/HTMLDefinition.php b/library/HTMLPurifier/HTMLDefinition.php
deleted file mode 100644 (file)
index c99ac11..0000000
+++ /dev/null
@@ -1,420 +0,0 @@
-<?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;
-
-
-    // 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) 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@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/library/HTMLPurifier/HTMLModule.php b/library/HTMLPurifier/HTMLModule.php
deleted file mode 100644 (file)
index 072cf68..0000000
+++ /dev/null
@@ -1,244 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/Bdo.php b/library/HTMLPurifier/HTMLModule/Bdo.php
deleted file mode 100644 (file)
index 3d66f1b..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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['required-dir'] = new HTMLPurifier_AttrTransform_BdoDir();
-
-        $this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl';
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/HTMLModule/CommonAttributes.php b/library/HTMLPurifier/HTMLModule/CommonAttributes.php
deleted file mode 100644 (file)
index 7c15da8..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/Edit.php b/library/HTMLPurifier/HTMLModule/Edit.php
deleted file mode 100644 (file)
index ff93690..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/Forms.php b/library/HTMLPurifier/HTMLModule/Forms.php
deleted file mode 100644 (file)
index 44c22f6..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-<?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#embeds',
-            '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
-        );
-
-        // 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/library/HTMLPurifier/HTMLModule/Hypertext.php b/library/HTMLPurifier/HTMLModule/Hypertext.php
deleted file mode 100644 (file)
index d7e9bdd..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/Image.php b/library/HTMLPurifier/HTMLModule/Image.php
deleted file mode 100644 (file)
index 948d435..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/Legacy.php b/library/HTMLPurifier/HTMLModule/Legacy.php
deleted file mode 100644 (file)
index df33927..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-<?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'] = 'Enum#top,middle,bottom,left,right';
-        $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';
-
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/HTMLModule/List.php b/library/HTMLPurifier/HTMLModule/List.php
deleted file mode 100644 (file)
index 74d4522..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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', 'Required: li', 'Common');
-        $ol->wrap = "li";
-        $ul = $this->addElement('ul', 'List', 'Required: li', 'Common');
-        $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/library/HTMLPurifier/HTMLModule/Name.php b/library/HTMLPurifier/HTMLModule/Name.php
deleted file mode 100644 (file)
index 05694b4..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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['NameSync'] = new HTMLPurifier_AttrTransform_NameSync();
-            }
-        }
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php b/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php
deleted file mode 100644 (file)
index 5f1b14a..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/Object.php b/library/HTMLPurifier/HTMLModule/Object.php
deleted file mode 100644 (file)
index 193c101..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/Presentation.php b/library/HTMLPurifier/HTMLModule/Presentation.php
deleted file mode 100644 (file)
index 8ff0b5e..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/Proprietary.php b/library/HTMLPurifier/HTMLModule/Proprietary.php
deleted file mode 100644 (file)
index dd36a3d..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/Ruby.php b/library/HTMLPurifier/HTMLModule/Ruby.php
deleted file mode 100644 (file)
index b26a0a3..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/SafeEmbed.php b/library/HTMLPurifier/HTMLModule/SafeEmbed.php
deleted file mode 100644 (file)
index ea25671..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-<?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',
-                'name' => 'ID',
-            )
-        );
-        $embed->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeEmbed();
-
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/HTMLModule/SafeObject.php b/library/HTMLPurifier/HTMLModule/SafeObject.php
deleted file mode 100644 (file)
index 64ab8c0..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-<?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',
-                'classid' => 'Enum#clsid:d27cdb6e-ae6d-11cf-96b8-444553540000',
-                '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/library/HTMLPurifier/HTMLModule/Scripting.php b/library/HTMLPurifier/HTMLModule/Scripting.php
deleted file mode 100644 (file)
index cecdea6..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-<?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['type'] =
-        $this->info['script']->attr_transform_post['type'] =
-            new HTMLPurifier_AttrTransform_ScriptRequired();
-    }
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/HTMLModule/StyleAttribute.php b/library/HTMLPurifier/HTMLModule/StyleAttribute.php
deleted file mode 100644 (file)
index eb78464..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/Tables.php b/library/HTMLPurifier/HTMLModule/Tables.php
deleted file mode 100644 (file)
index f314ced..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-<?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',
-            ),
-            $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/library/HTMLPurifier/HTMLModule/Target.php b/library/HTMLPurifier/HTMLModule/Target.php
deleted file mode 100644 (file)
index 2b844ec..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/Text.php b/library/HTMLPurifier/HTMLModule/Text.php
deleted file mode 100644 (file)
index ae77c71..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/Tidy.php b/library/HTMLPurifier/HTMLModule/Tidy.php
deleted file mode 100644 (file)
index 21783f1..0000000
+++ /dev/null
@@ -1,207 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/Tidy/Name.php b/library/HTMLPurifier/HTMLModule/Tidy/Name.php
deleted file mode 100644 (file)
index 61ff85c..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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@name'] =
-        $r['a@name'] = new HTMLPurifier_AttrTransform_Name();
-
-        return $r;
-    }
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php b/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php
deleted file mode 100644 (file)
index 14c15c4..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-class HTMLPurifier_HTMLModule_Tidy_Proprietary extends HTMLPurifier_HTMLModule_Tidy
-{
-
-    public $name = 'Tidy_Proprietary';
-    public $defaultLevel = 'light';
-
-    public function makeFixes() {
-        $r = array();
-        $r['table@background'] = new HTMLPurifier_AttrTransform_Background();
-        $r['td@background']    = new HTMLPurifier_AttrTransform_Background();
-        $r['th@background']    = new HTMLPurifier_AttrTransform_Background();
-        $r['tr@background']    = new HTMLPurifier_AttrTransform_Background();
-        $r['thead@background'] = new HTMLPurifier_AttrTransform_Background();
-        $r['tfoot@background'] = new HTMLPurifier_AttrTransform_Background();
-        $r['tbody@background'] = new HTMLPurifier_AttrTransform_Background();
-        $r['table@height']     = new HTMLPurifier_AttrTransform_Length('height');
-        return $r;
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/HTMLModule/Tidy/Strict.php b/library/HTMLPurifier/HTMLModule/Tidy/Strict.php
deleted file mode 100644 (file)
index c73dc3c..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php b/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php
deleted file mode 100644 (file)
index 9960b1d..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php b/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php
deleted file mode 100644 (file)
index db5a378..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php b/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php
deleted file mode 100644 (file)
index 02e9438..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-<?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@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@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@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@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@align'] =
-        $r['h2@align'] =
-        $r['h3@align'] =
-        $r['h4@align'] =
-        $r['h5@align'] =
-        $r['h6@align'] =
-        $r['p@align']  =
-        $r['div@align'] =
-            new HTMLPurifier_AttrTransform_EnumToCSS('align', $align_lookup);
-
-        // @bgcolor for table, tr, td, th ---------------------------------
-        $r['table@bgcolor'] =
-        $r['td@bgcolor'] =
-        $r['th@bgcolor'] =
-            new HTMLPurifier_AttrTransform_BgColor();
-
-        // @border for img ------------------------------------------------
-        $r['img@border'] = new HTMLPurifier_AttrTransform_Border();
-
-        // @clear for br --------------------------------------------------
-        $r['br@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@height'] =
-        $r['th@height'] =
-            new HTMLPurifier_AttrTransform_Length('height');
-
-        // @hspace for img ------------------------------------------------
-        $r['img@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@noshade'] =
-            new HTMLPurifier_AttrTransform_BoolToCSS(
-                'noshade',
-                'color:#808080;background-color:#808080;border:0;'
-            );
-
-        // @nowrap for td, th ---------------------------------------------
-        $r['td@nowrap'] =
-        $r['th@nowrap'] =
-            new HTMLPurifier_AttrTransform_BoolToCSS(
-                'nowrap',
-                'white-space:nowrap;'
-            );
-
-        // @size for hr  --------------------------------------------------
-        $r['hr@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@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ul_types);
-        $r['ol@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ol_types, true);
-        $r['li@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $li_types, true);
-
-        // @vspace for img ------------------------------------------------
-        $r['img@vspace'] = new HTMLPurifier_AttrTransform_ImgSpace('vspace');
-
-        // @width for hr, td, th ------------------------------------------
-        $r['td@width'] =
-        $r['th@width'] =
-        $r['hr@width'] = new HTMLPurifier_AttrTransform_Length('width');
-
-        return $r;
-
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php b/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php
deleted file mode 100644 (file)
index 9c0e031..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<?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/library/HTMLPurifier/HTMLModuleManager.php b/library/HTMLPurifier/HTMLModuleManager.php
deleted file mode 100644 (file)
index f5c4a1d..0000000
+++ /dev/null
@@ -1,403 +0,0 @@
-<?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');
-        $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,
-            array_merge($common, $xml, array('Ruby')),
-            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]);
-            }
-        }
-
-        // add proprietary module (this gets special treatment because
-        // it is completely removed from doctypes, etc.)
-        if ($config->get('HTML.Proprietary')) {
-            $modules[] = 'Proprietary';
-        }
-
-        // add SafeObject/Safeembed modules
-        if ($config->get('HTML.SafeObject')) {
-            $modules[] = 'SafeObject';
-        }
-        if ($config->get('HTML.SafeEmbed')) {
-            $modules[] = 'SafeEmbed';
-        }
-
-        // 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
-                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/library/HTMLPurifier/IDAccumulator.php b/library/HTMLPurifier/IDAccumulator.php
deleted file mode 100644 (file)
index 7321529..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-<?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/library/HTMLPurifier/Injector.php b/library/HTMLPurifier/Injector.php
deleted file mode 100644 (file)
index 5922f81..0000000
+++ /dev/null
@@ -1,239 +0,0 @@
-<?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/library/HTMLPurifier/Injector/AutoParagraph.php b/library/HTMLPurifier/Injector/AutoParagraph.php
deleted file mode 100644 (file)
index afa7608..0000000
+++ /dev/null
@@ -1,345 +0,0 @@
-<?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/library/HTMLPurifier/Injector/DisplayLinkURI.php b/library/HTMLPurifier/Injector/DisplayLinkURI.php
deleted file mode 100644 (file)
index 9dce9bd..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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/library/HTMLPurifier/Injector/Linkify.php b/library/HTMLPurifier/Injector/Linkify.php
deleted file mode 100644 (file)
index 296dac2..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-<?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/library/HTMLPurifier/Injector/PurifierLinkify.php b/library/HTMLPurifier/Injector/PurifierLinkify.php
deleted file mode 100644 (file)
index ad2455a..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-<?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/library/HTMLPurifier/Injector/RemoveEmpty.php b/library/HTMLPurifier/Injector/RemoveEmpty.php
deleted file mode 100644 (file)
index 638bfca..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector
-{
-
-    private $context, $config, $attrValidator, $removeNbsp, $removeNbspExceptions;
-
-    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 ($token->name == 'colgroup') 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/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php b/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php
deleted file mode 100644 (file)
index b213134..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-<?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/library/HTMLPurifier/Injector/SafeObject.php b/library/HTMLPurifier/Injector/SafeObject.php
deleted file mode 100644 (file)
index 9e178ce..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-<?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,
-    );
-
-    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/library/HTMLPurifier/Language.php b/library/HTMLPurifier/Language.php
deleted file mode 100644 (file)
index 3e2be03..0000000
+++ /dev/null
@@ -1,163 +0,0 @@
-<?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/library/HTMLPurifier/Language/classes/en-x-test.php b/library/HTMLPurifier/Language/classes/en-x-test.php
deleted file mode 100644 (file)
index d52fcb7..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-// private class for unit testing
-
-class HTMLPurifier_Language_en_x_test extends HTMLPurifier_Language
-{
-
-
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/Language/messages/en-x-test.php b/library/HTMLPurifier/Language/messages/en-x-test.php
deleted file mode 100644 (file)
index 1c046f3..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?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/library/HTMLPurifier/Language/messages/en-x-testmini.php b/library/HTMLPurifier/Language/messages/en-x-testmini.php
deleted file mode 100644 (file)
index 806c83f..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?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/library/HTMLPurifier/Language/messages/en.php b/library/HTMLPurifier/Language/messages/en.php
deleted file mode 100644 (file)
index 8d7b573..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-<?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 &lt;',
-'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/library/HTMLPurifier/LanguageFactory.php b/library/HTMLPurifier/LanguageFactory.php
deleted file mode 100644 (file)
index 134ef8c..0000000
+++ /dev/null
@@ -1,198 +0,0 @@
-<?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/library/HTMLPurifier/Length.php b/library/HTMLPurifier/Length.php
deleted file mode 100644 (file)
index 8d2a46b..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-<?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/library/HTMLPurifier/Lexer.php b/library/HTMLPurifier/Lexer.php
deleted file mode 100644 (file)
index b05e115..0000000
+++ /dev/null
@@ -1,306 +0,0 @@
-<?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(
-                    '&quot;' => '"',
-                    '&amp;'  => '&',
-                    '&lt;'   => '<',
-                    '&gt;'   => '>',
-                    '&#39;'  => "'",
-                    '&#039;' => "'",
-                    '&#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, '&amp;');
-        $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
-        );
-    }
-
-    /**
-     * 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
-        $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);
-
-        // 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);
-
-        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/library/HTMLPurifier/Lexer/DOMLex.php b/library/HTMLPurifier/Lexer/DOMLex.php
deleted file mode 100644 (file)
index 20dc2ed..0000000
+++ /dev/null
@@ -1,213 +0,0 @@
-<?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", '&lt;\\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;
-    }
-
-    /**
-     * Recursive function that tokenizes a node, putting it into an accumulator.
-     *
-     * @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 Tokens of node appended to previously passed tokens.
-     */
-    protected function tokenizeDOM($node, &$tokens, $collect = false) {
-
-        // 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;
-        } 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;
-        } 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;
-        } elseif (
-            // not-well tested: there may be other nodes we have to grab
-            $node->nodeType !== XML_ELEMENT_NODE
-        ) {
-            return;
-        }
-
-        $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);
-            }
-        } else {
-            if ($collect) { // don't wrap on first iteration
-                $tokens[] = $this->factory->createStart(
-                    $tag_name = $node->tagName, // somehow, it get's dropped
-                    $attr
-                );
-            }
-            foreach ($node->childNodes as $node) {
-                // remember, it's an accumulator. Otherwise, we'd have
-                // to use array_merge
-                $this->tokenizeDOM($node, $tokens, true);
-            }
-            if ($collect) {
-                $tokens[] = $this->factory->createEnd($tag_name);
-            }
-        }
-
-    }
-
-    /**
-     * 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('&amp;'=>'&','&lt;'=>'<')) . $matches[2];
-    }
-
-    /**
-     * Callback function that entity-izes ampersands in comments so that
-     * callbackUndoCommentSubst doesn't clobber them
-     */
-    public function callbackArmorCommentEntities($matches) {
-        return '<!--' . str_replace('&', '&amp;', $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/library/HTMLPurifier/Lexer/DirectLex.php b/library/HTMLPurifier/Lexer/DirectLex.php
deleted file mode 100644 (file)
index 456e6e1..0000000
+++ /dev/null
@@ -1,490 +0,0 @@
-<?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/library/HTMLPurifier/Lexer/PEARSax3.php b/library/HTMLPurifier/Lexer/PEARSax3.php
deleted file mode 100644 (file)
index 1d358c7..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-<?php
-
-/**
- * Proof-of-concept lexer that uses the PEAR package XML_HTMLSax3 to parse HTML.
- *
- * PEAR, not suprisingly, also has a SAX parser for HTML.  I don't know
- * very much about implementation, but it's fairly well written.  However, that
- * abstraction comes at a price: performance. You need to have it installed,
- * and if the API changes, it might break our adapter. Not sure whether or not
- * it's UTF-8 aware, but it has some entity parsing trouble (in all areas,
- * text and attributes).
- *
- * Quite personally, I don't recommend using the PEAR class, and the defaults
- * don't use it. The unit tests do perform the tests on the SAX parser too, but
- * whatever it does for poorly formed HTML is up to it.
- *
- * @todo Generalize so that XML_HTMLSax is also supported.
- *
- * @warning Entity-resolution inside attributes is broken.
- */
-
-class HTMLPurifier_Lexer_PEARSax3 extends HTMLPurifier_Lexer
-{
-
-    /**
-     * Internal accumulator array for SAX parsers.
-     */
-    protected $tokens = array();
-    protected $last_token_was_empty;
-
-    private $parent_handler;
-    private $stack = array();
-
-    public function tokenizeHTML($string, $config, $context) {
-
-        $this->tokens = array();
-        $this->last_token_was_empty = false;
-
-        $string = $this->normalize($string, $config, $context);
-
-        $this->parent_handler = set_error_handler(array($this, 'muteStrictErrorHandler'));
-
-        $parser = new XML_HTMLSax3();
-        $parser->set_object($this);
-        $parser->set_element_handler('openHandler','closeHandler');
-        $parser->set_data_handler('dataHandler');
-        $parser->set_escape_handler('escapeHandler');
-
-        // doesn't seem to work correctly for attributes
-        $parser->set_option('XML_OPTION_ENTITIES_PARSED', 1);
-
-        $parser->parse($string);
-
-        restore_error_handler();
-
-        return $this->tokens;
-
-    }
-
-    /**
-     * Open tag event handler, interface is defined by PEAR package.
-     */
-    public function openHandler(&$parser, $name, $attrs, $closed) {
-        // entities are not resolved in attrs
-        foreach ($attrs as $key => $attr) {
-            $attrs[$key] = $this->parseData($attr);
-        }
-        if ($closed) {
-            $this->tokens[] = new HTMLPurifier_Token_Empty($name, $attrs);
-            $this->last_token_was_empty = true;
-        } else {
-            $this->tokens[] = new HTMLPurifier_Token_Start($name, $attrs);
-        }
-        $this->stack[] = $name;
-        return true;
-    }
-
-    /**
-     * Close tag event handler, interface is defined by PEAR package.
-     */
-    public function closeHandler(&$parser, $name) {
-        // HTMLSax3 seems to always send empty tags an extra close tag
-        // check and ignore if you see it:
-        // [TESTME] to make sure it doesn't overreach
-        if ($this->last_token_was_empty) {
-            $this->last_token_was_empty = false;
-            return true;
-        }
-        $this->tokens[] = new HTMLPurifier_Token_End($name);
-        if (!empty($this->stack)) array_pop($this->stack);
-        return true;
-    }
-
-    /**
-     * Data event handler, interface is defined by PEAR package.
-     */
-    public function dataHandler(&$parser, $data) {
-        $this->last_token_was_empty = false;
-        $this->tokens[] = new HTMLPurifier_Token_Text($data);
-        return true;
-    }
-
-    /**
-     * Escaped text handler, interface is defined by PEAR package.
-     */
-    public function escapeHandler(&$parser, $data) {
-        if (strpos($data, '--') === 0) {
-            // remove trailing and leading double-dashes
-            $data = substr($data, 2);
-            if (strlen($data) >= 2 && substr($data, -2) == "--") {
-                $data = substr($data, 0, -2);
-            }
-            if (isset($this->stack[sizeof($this->stack) - 1]) &&
-                $this->stack[sizeof($this->stack) - 1] == "style") {
-                $this->tokens[] = new HTMLPurifier_Token_Text($data);
-            } else {
-                $this->tokens[] = new HTMLPurifier_Token_Comment($data);
-            }
-            $this->last_token_was_empty = false;
-        }
-        // CDATA is handled elsewhere, but if it was handled here:
-        //if (strpos($data, '[CDATA[') === 0) {
-        //    $this->tokens[] = new HTMLPurifier_Token_Text(
-        //        substr($data, 7, strlen($data) - 9) );
-        //}
-        return true;
-    }
-
-    /**
-     * An error handler that mutes strict errors
-     */
-    public function muteStrictErrorHandler($errno, $errstr, $errfile=null, $errline=null, $errcontext=null) {
-        if ($errno == E_STRICT) return;
-        return call_user_func($this->parent_handler, $errno, $errstr, $errfile, $errline, $errcontext);
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/Lexer/PH5P.php b/library/HTMLPurifier/Lexer/PH5P.php
deleted file mode 100644 (file)
index fa1bf97..0000000
+++ /dev/null
@@ -1,3906 +0,0 @@
-<?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) {
-        $data = str_replace("\r\n", "\n", $data);
-        $data = str_replace("\r", null, $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/library/HTMLPurifier/PercentEncoder.php b/library/HTMLPurifier/PercentEncoder.php
deleted file mode 100644 (file)
index a43c44f..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-<?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/library/HTMLPurifier/Printer.php b/library/HTMLPurifier/Printer.php
deleted file mode 100644 (file)
index e7eb82e..0000000
+++ /dev/null
@@ -1,176 +0,0 @@
-<?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/library/HTMLPurifier/Printer/CSSDefinition.php b/library/HTMLPurifier/Printer/CSSDefinition.php
deleted file mode 100644 (file)
index 81f9865..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-<?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/library/HTMLPurifier/Printer/ConfigForm.css b/library/HTMLPurifier/Printer/ConfigForm.css
deleted file mode 100644 (file)
index 3ff1a88..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-
-.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/library/HTMLPurifier/Printer/ConfigForm.js b/library/HTMLPurifier/Printer/ConfigForm.js
deleted file mode 100644 (file)
index cba00c9..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-function toggleWriteability(id_of_patient, checked) {
-    document.getElementById(id_of_patient).disabled = checked;
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/Printer/ConfigForm.php b/library/HTMLPurifier/Printer/ConfigForm.php
deleted file mode 100644 (file)
index 02aa656..0000000
+++ /dev/null
@@ -1,368 +0,0 @@
-<?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/library/HTMLPurifier/Printer/HTMLDefinition.php b/library/HTMLPurifier/Printer/HTMLDefinition.php
deleted file mode 100644 (file)
index 8a8f126..0000000
+++ /dev/null
@@ -1,272 +0,0 @@
-<?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&nbsp;=&nbsp;<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/library/HTMLPurifier/PropertyList.php b/library/HTMLPurifier/PropertyList.php
deleted file mode 100644 (file)
index 2b99fb7..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-<?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/library/HTMLPurifier/PropertyListIterator.php b/library/HTMLPurifier/PropertyListIterator.php
deleted file mode 100644 (file)
index 8f25044..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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/library/HTMLPurifier/Strategy.php b/library/HTMLPurifier/Strategy.php
deleted file mode 100644 (file)
index 2462865..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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/library/HTMLPurifier/Strategy/Composite.php b/library/HTMLPurifier/Strategy/Composite.php
deleted file mode 100644 (file)
index 816490b..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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();
-
-    abstract public function __construct();
-
-    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/library/HTMLPurifier/Strategy/Core.php b/library/HTMLPurifier/Strategy/Core.php
deleted file mode 100644 (file)
index d90e158..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<?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/library/HTMLPurifier/Strategy/FixNesting.php b/library/HTMLPurifier/Strategy/FixNesting.php
deleted file mode 100644 (file)
index f818023..0000000
+++ /dev/null
@@ -1,328 +0,0 @@
-<?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.
- */
-
-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();
-
-        // 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)) {
-                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
-                    $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/library/HTMLPurifier/Strategy/MakeWellFormed.php b/library/HTMLPurifier/Strategy/MakeWellFormed.php
deleted file mode 100644 (file)
index c736584..0000000
+++ /dev/null
@@ -1,475 +0,0 @@
-<?php
-
-/**
- * Takes tokens makes them well-formed (balance end tags, etc.)
- */
-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');
-        $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 punting:
-        //      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
-                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);
-                $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));
-                // 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)) {
-
-                    $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) {
-                        // errors need to be updated
-                        $new_token = new HTMLPurifier_Token_End($parent->name);
-                        $new_token->start = $parent;
-                        if ($carryover) {
-                            $element = clone $parent;
-                            $element->armor['MakeWellFormed_TagClosedError'] = true;
-                            $element->carryover = true;
-                            $this->processToken(array($new_token, $token, $element));
-                        } else {
-                            $this->insertBefore($new_token);
-                        }
-                        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);
-                            }
-                        }
-                        $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...
-                    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) {
-                    $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
-     */
-    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.
-     */
-    private function remove() {
-        array_splice($this->tokens, $this->t, 1);
-    }
-
-    /**
-     * Swap current token with new token. Cursor points to new token (no change).
-     */
-    private function swap($token) {
-        $this->tokens[$this->t] = $token;
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/Strategy/RemoveForeignElements.php b/library/HTMLPurifier/Strategy/RemoveForeignElements.php
deleted file mode 100644 (file)
index cf3a33e..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-<?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');
-
-        $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) {
-                    // keep, but perform comment cleaning
-                    if ($e) {
-                        // perform check whether or not there's a trailing hyphen
-                        if (substr($token->data, -1) == '-') {
-                            $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed');
-                        }
-                    }
-                    $token->data = rtrim($token->data, '-');
-                    $found_double_hyphen = false;
-                    while (strpos($token->data, '--') !== false) {
-                        if ($e && !$found_double_hyphen) {
-                            $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed');
-                        }
-                        $found_double_hyphen = true; // prevent double-erroring
-                        $token->data = str_replace('--', '-', $token->data);
-                    }
-                } 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/library/HTMLPurifier/Strategy/ValidateAttributes.php b/library/HTMLPurifier/Strategy/ValidateAttributes.php
deleted file mode 100644 (file)
index c3328a9..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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/library/HTMLPurifier/StringHash.php b/library/HTMLPurifier/StringHash.php
deleted file mode 100644 (file)
index 62085c5..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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/library/HTMLPurifier/StringHashParser.php b/library/HTMLPurifier/StringHashParser.php
deleted file mode 100644 (file)
index f3e70c7..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-<?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/library/HTMLPurifier/TagTransform.php b/library/HTMLPurifier/TagTransform.php
deleted file mode 100644 (file)
index 210a447..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-<?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/library/HTMLPurifier/TagTransform/Font.php b/library/HTMLPurifier/TagTransform/Font.php
deleted file mode 100644 (file)
index ed24637..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-<?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']{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/library/HTMLPurifier/TagTransform/Simple.php b/library/HTMLPurifier/TagTransform/Simple.php
deleted file mode 100644 (file)
index 0e36130..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-<?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/library/HTMLPurifier/Token.php b/library/HTMLPurifier/Token.php
deleted file mode 100644 (file)
index 7900e6c..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-<?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/library/HTMLPurifier/Token/Comment.php b/library/HTMLPurifier/Token/Comment.php
deleted file mode 100644 (file)
index dc6bdca..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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/library/HTMLPurifier/Token/Empty.php b/library/HTMLPurifier/Token/Empty.php
deleted file mode 100644 (file)
index 2a82b47..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-/**
- * Concrete empty token class.
- */
-class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag
-{
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/Token/End.php b/library/HTMLPurifier/Token/End.php
deleted file mode 100644 (file)
index 353e79d..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<?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/library/HTMLPurifier/Token/Start.php b/library/HTMLPurifier/Token/Start.php
deleted file mode 100644 (file)
index e0e14fc..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-/**
- * Concrete start token class.
- */
-class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag
-{
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/Token/Tag.php b/library/HTMLPurifier/Token/Tag.php
deleted file mode 100644 (file)
index 798be02..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-<?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) {
-        $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;
-    }
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/Token/Text.php b/library/HTMLPurifier/Token/Text.php
deleted file mode 100644 (file)
index 82efd82..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?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/library/HTMLPurifier/TokenFactory.php b/library/HTMLPurifier/TokenFactory.php
deleted file mode 100644 (file)
index 7cf48fb..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-<?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/library/HTMLPurifier/URI.php b/library/HTMLPurifier/URI.php
deleted file mode 100644 (file)
index 8b50d0d..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-<?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 = $registry->getScheme($def->defaultScheme, $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 scheme (MUST BE FIRST!)
-        if (!is_null($this->scheme) && is_null($this->host)) {
-            $def = $config->getDefinition('URI');
-            if ($def->defaultScheme === $this->scheme) {
-                $this->scheme = null;
-            }
-        }
-
-        // 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 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)) {
-            // path-abempty (hier and relative)
-            $this->path = $segments_encoder->encode($this->path);
-        } elseif ($this->path !== '' && $this->path[0] === '/') {
-            // path-absolute (hier and relative)
-            if (strlen($this->path) >= 2 && $this->path[1] === '/') {
-                // This shouldn't ever happen!
-                $this->path = '';
-            } else {
-                $this->path = $segments_encoder->encode($this->path);
-            }
-        } elseif (!is_null($this->scheme) && $this->path !== '') {
-            // path-rootless (hier)
-            // Short circuit evaluation means we don't need to check nz
-            $this->path = $segments_encoder->encode($this->path);
-        } elseif (is_null($this->scheme) && $this->path !== '') {
-            // path-noscheme (relative)
-            // (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;
-        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
-        $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;
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/URIDefinition.php b/library/HTMLPurifier/URIDefinition.php
deleted file mode 100644 (file)
index ea2b8fe..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-<?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_HostBlacklist());
-        $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) {
-            $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 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/library/HTMLPurifier/URIFilter.php b/library/HTMLPurifier/URIFilter.php
deleted file mode 100644 (file)
index c116f93..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-
-/**
- * Chainable filters for custom URI processing.
- *
- * These filters can perform custom actions on a URI filter object,
- * including transformation or blacklisting.
- *
- * @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;
-
-    /**
-     * Performs initialization for the filter
-     */
-    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/library/HTMLPurifier/URIFilter/DisableExternal.php b/library/HTMLPurifier/URIFilter/DisableExternal.php
deleted file mode 100644 (file)
index d8a39a5..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/library/HTMLPurifier/URIFilter/DisableExternalResources.php b/library/HTMLPurifier/URIFilter/DisableExternalResources.php
deleted file mode 100644 (file)
index 881abc4..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?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/library/HTMLPurifier/URIFilter/HostBlacklist.php b/library/HTMLPurifier/URIFilter/HostBlacklist.php
deleted file mode 100644 (file)
index 045aa09..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-
-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/library/HTMLPurifier/URIFilter/MakeAbsolute.php b/library/HTMLPurifier/URIFilter/MakeAbsolute.php
deleted file mode 100644 (file)
index f46ab26..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-<?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/library/HTMLPurifier/URIFilter/Munge.php b/library/HTMLPurifier/URIFilter/Munge.php
deleted file mode 100644 (file)
index efa10a6..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-<?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 (is_null($uri->host) || empty($scheme_obj->browsable)) {
-            return true;
-        }
-        // don't redirect if target host is our host
-        if ($uri->host === $config->getDefinition('URI')->host) {
-            return true;
-        }
-
-        $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/library/HTMLPurifier/URIParser.php b/library/HTMLPurifier/URIParser.php
deleted file mode 100644 (file)
index 7179e4a..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-<?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/library/HTMLPurifier/URIScheme.php b/library/HTMLPurifier/URIScheme.php
deleted file mode 100644 (file)
index 039710f..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-
-/**
- * Validator for the components of a URI for a specific scheme
- */
-class HTMLPurifier_URIScheme
-{
-
-    /**
-     * Scheme's default port (integer)
-     */
-    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 the URI always uses <hier_part>, resolves edge cases
-     * with making relative URIs absolute
-     */
-    public $hierarchical = false;
-
-    /**
-     * Validates the components of a URI
-     * @note This implementation should be called by children if they define
-     *       a default port, as it does port processing.
-     * @param $uri Instance of HTMLPurifier_URI
-     * @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;
-        return true;
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/URIScheme/data.php b/library/HTMLPurifier/URIScheme/data.php
deleted file mode 100644 (file)
index b7f1989..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-<?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,
-        );
-
-    public function validate(&$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);
-        } elseif (function_exists('getimagesize')) {
-            set_error_handler(array($this, 'muteErrorHandler'));
-            $info = getimagesize($file);
-            restore_error_handler();
-            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/library/HTMLPurifier/URIScheme/ftp.php b/library/HTMLPurifier/URIScheme/ftp.php
deleted file mode 100644 (file)
index 5849bf7..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?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 validate(&$uri, $config, $context) {
-        parent::validate($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/library/HTMLPurifier/URIScheme/http.php b/library/HTMLPurifier/URIScheme/http.php
deleted file mode 100644 (file)
index b097a31..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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 validate(&$uri, $config, $context) {
-        parent::validate($uri, $config, $context);
-        $uri->userinfo = null;
-        return true;
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/URIScheme/https.php b/library/HTMLPurifier/URIScheme/https.php
deleted file mode 100644 (file)
index 29e3809..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-/**
- * Validates https (Secure HTTP) according to http scheme.
- */
-class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http {
-
-    public $default_port = 443;
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/URIScheme/mailto.php b/library/HTMLPurifier/URIScheme/mailto.php
deleted file mode 100644 (file)
index c1e2cd5..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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 function validate(&$uri, $config, $context) {
-        parent::validate($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/library/HTMLPurifier/URIScheme/news.php b/library/HTMLPurifier/URIScheme/news.php
deleted file mode 100644 (file)
index f5f54f4..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-/**
- * Validates news (Usenet) as defined by generic RFC 1738
- */
-class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme {
-
-    public $browsable = false;
-
-    public function validate(&$uri, $config, $context) {
-        parent::validate($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/library/HTMLPurifier/URIScheme/nntp.php b/library/HTMLPurifier/URIScheme/nntp.php
deleted file mode 100644 (file)
index 5bf93ea..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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 validate(&$uri, $config, $context) {
-        parent::validate($uri, $config, $context);
-        $uri->userinfo = null;
-        $uri->query    = null;
-        return true;
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/URISchemeRegistry.php b/library/HTMLPurifier/URISchemeRegistry.php
deleted file mode 100644 (file)
index 576bf7b..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-<?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/library/HTMLPurifier/UnitConverter.php b/library/HTMLPurifier/UnitConverter.php
deleted file mode 100644 (file)
index 545d426..0000000
+++ /dev/null
@@ -1,254 +0,0 @@
-<?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/library/HTMLPurifier/VarParser.php b/library/HTMLPurifier/VarParser.php
deleted file mode 100644 (file)
index 68e72ae..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-<?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/library/HTMLPurifier/VarParser/Flexible.php b/library/HTMLPurifier/VarParser/Flexible.php
deleted file mode 100644 (file)
index c954250..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-<?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[$c[0]] = $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::LOOKUP) {
-                    foreach ($var as $key => $value) {
-                        $var[$key] = true;
-                    }
-                }
-                return $var;
-            default:
-                $this->errorInconsistent(__CLASS__, $type);
-        }
-        $this->errorGeneric($var, $type);
-    }
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/HTMLPurifier/VarParser/Native.php b/library/HTMLPurifier/VarParser/Native.php
deleted file mode 100644 (file)
index b02a6de..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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/library/HTMLPurifier/VarParserException.php b/library/HTMLPurifier/VarParserException.php
deleted file mode 100644 (file)
index 5df3414..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-/**
- * Exception type for HTMLPurifier_VarParser
- */
-class HTMLPurifier_VarParserException extends HTMLPurifier_Exception
-{
-
-}
-
-// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/CREDITS b/library/ezyang/htmlpurifier/CREDITS
new file mode 100644 (file)
index 0000000..7921b45
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/INSTALL b/library/ezyang/htmlpurifier/INSTALL
new file mode 100644 (file)
index 0000000..677c04a
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/INSTALL.fr.utf8 b/library/ezyang/htmlpurifier/INSTALL.fr.utf8
new file mode 100644 (file)
index 0000000..06e628c
--- /dev/null
@@ -0,0 +1,60 @@
+
+Installation
+    Comment installer HTML Purifier
+
+Attention : Ce document est encodé en UTF-8, si les lettres avec des accents
+ne s'affichent pas, prenez un meilleur éditeur de texte.
+
+L'installation de HTML Purifier est très simple, parce qu'il n'a pas besoin
+de configuration. Pour les utilisateurs impatients, le code se trouve dans le
+pied de page, mais je recommande de lire le document.
+
+1.  Compatibilité
+
+HTML Purifier fonctionne avec PHP 5. PHP 5.0.5 est la dernière version testée.
+Il ne dépend pas d'autres librairies.
+
+Les extensions optionnelles sont iconv (généralement déjà installée) et tidy
+(répendue aussi). Si vous utilisez UTF-8 et que vous ne voulez pas l'indentation,
+vous pouvez utiliser HTML Purifier sans ces extensions.
+
+
+2.  Inclure la librairie
+
+Quand vous devez l'utilisez, incluez le :
+
+    require_once('/path/to/library/HTMLPurifier.auto.php');
+
+Ne pas l'inclure si ce n'est pas nécessaire, car HTML Purifier est lourd.
+
+HTML Purifier utilise "autoload". Si vous avez défini la fonction __autoload,
+vous devez ajouter cette fonction :
+
+    spl_autoload_register('__autoload')
+
+Plus d'informations dans le document "INSTALL".
+
+3.  Installation rapide
+
+Si votre site Web est en UTF-8 et XHTML Transitional, utilisez :
+
+<?php
+    require_once('/path/to/htmlpurifier/library/HTMLPurifier.auto.php');
+    $purificateur = new HTMLPurifier();
+    $html_propre = $purificateur->purify($html_a_purifier);
+?>
+
+Sinon, utilisez :
+
+<?php
+    require_once('/path/to/html/purifier/library/HTMLPurifier.auto.load');
+    $config = $HTMLPurifier_Config::createDefault();
+    $config->set('Core', 'Encoding', 'ISO-8859-1'); //Remplacez par votre
+    encodage
+    $config->set('Core', 'XHTML', true); //Remplacer par false si HTML 4.01
+    $purificateur = new HTMLPurifier($config);
+    $html_propre = $purificateur->purify($html_a_purifier);
+?>
+
+
+    vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/LICENSE b/library/ezyang/htmlpurifier/LICENSE
new file mode 100644 (file)
index 0000000..8c88a20
--- /dev/null
@@ -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.
+\f
+  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.
+\f
+                 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.
+\f
+  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.
+\f
+  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.
+\f
+  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.
+\f
+  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.
+\f
+  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.
+\f
+  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
+\f
+           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/library/ezyang/htmlpurifier/NEWS b/library/ezyang/htmlpurifier/NEWS
new file mode 100644 (file)
index 0000000..a9124af
--- /dev/null
@@ -0,0 +1,1094 @@
+NEWS ( CHANGELOG and HISTORY )                                     HTMLPurifier
+|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+= KEY ====================
+    # Breaks back-compat
+    ! Feature
+    - Bugfix
+      + Sub-comment
+    . Internal change
+==========================
+
+4.7.0, released 2015-08-04
+# opacity is now considered a "tricky" CSS property rather than a
+  proprietary one.
+! %AutoFormat.RemoveEmpty.Predicate for specifying exactly when
+  an element should be considered "empty" (maybe preserve if it
+  has attributes), and modify iframe support so that the iframe
+  is removed if it is missing a src attribute.  Thanks meeva for
+  reporting.
+- Don't truncate upon encountering </div> when using DOMLex.  Thanks
+  Myrto Christina for finally convincing me to fix this.
+- Update YouTube filter for new code.
+- Fix parsing of rgb() values with spaces in them for 'border'
+  attribute.
+- Don't remove foo="" attributes if foo is a boolean attribute.  Thanks
+  valME for reporting.
+
+4.6.0, released 2013-11-30
+# Secure URI munge hashing algorithm has changed to hash_hmac("sha256", $url, $secret).
+  Please update any verification scripts you may have.
+# URI parsing algorithm was made more strict, so only prefixes which
+  looks like schemes will actually be schemes.  Thanks
+  Michael Gusev <mgusev@sugarcrm.com> for fixing.
+# %Core.EscapeInvalidChildren is no longer supported, and no longer does
+  anything.
+! New directive %Core.AllowHostnameUnderscore which allows underscores
+  in hostnames.
+- Eliminate quadratic behavior in DOMLex by using a proper queue.
+  Thanks Ole Laursen for noticing this.
+- Rewritten MakeWellFormed/FixNesting implementation eliminates quadratic
+  behavior in the rest of the purificaiton pipeline.  Thanks Chedburn
+  Networks for sponsoring this work.
+- Made Linkify URL parser a bit less permissive, so that non-breaking
+  spaces and commas are not included as part of URL.  Thanks nAS for fixing.
+- Fix some bad interactions with %HTML.Allowed and injectors.  Thanks
+  David Hirtz for reporting.
+- Fix infinite loop in DirectLex. Thanks Ashar Javed (@soaj1664ashar)
+  for reporting.
+
+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.DisableExcludes; can be set to 'true' 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
+  'true'.
+- 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@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@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@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@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@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@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@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@gmail.com for reporting and
+  Neike Taika-Tessaro <pinkgothic@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@googlemail.com> for reporting and Takeshi
+  Terada <t-terada@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@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 &nbsp; 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@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/library/ezyang/htmlpurifier/README b/library/ezyang/htmlpurifier/README
new file mode 100644 (file)
index 0000000..53f26f1
--- /dev/null
@@ -0,0 +1,24 @@
+
+README
+    All about HTML Purifier
+
+HTML Purifier is an HTML filtering solution that uses a unique combination
+of robust whitelists and agressive parsing to ensure that not only are
+XSS attacks thwarted, but the resulting HTML is standards compliant.
+
+HTML Purifier is oriented towards richly formatted documents from
+untrusted sources that require CSS and a full tag-set.  This library can
+be configured to accept a more restrictive set of tags, but it won't be
+as efficient as more bare-bones parsers. It will, however, do the job
+right, which may be more important.
+
+Places to go:
+
+* See INSTALL for a quick installation guide
+* See docs/ for developer-oriented documentation, code examples and
+  an in-depth installation guide.
+* See WYSIWYG for information on editors like TinyMCE and FCKeditor
+
+HTML Purifier can be found on the web at: http://htmlpurifier.org/
+
+    vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/TODO b/library/ezyang/htmlpurifier/TODO
new file mode 100644 (file)
index 0000000..a92abf2
--- /dev/null
@@ -0,0 +1,150 @@
+
+TODO List
+
+= KEY ====================
+    # Flagship
+    - Regular
+    ? Maybe I'll Do It
+==========================
+
+If no interest is expressed for a feature that may require a considerable
+amount of effort to implement, it may get endlessly delayed. Do not be
+afraid to cast your vote for the next feature to be implemented!
+
+Things to do as soon as possible:
+
+ - http://htmlpurifier.org/phorum/read.php?3,5560,6307#msg-6307
+ - Think about allowing explicit order of operations hooks for transforms
+ - Fix "<.<" bug (trailing < is removed if not EOD)
+ - Build in better internal state dumps and debugging tools for remote
+   debugging
+ - Allowed/Allowed* have strange interactions when both set
+ ? Transform lone embeds into object tags
+ - Deprecated config options that emit warnings when you set them (with'
+   a way of muting the warning if you really want to)
+ - Make HTML.Trusted work with Output.FlashCompat
+ - HTML.Trusted and HTML.SafeObject have funny interaction; general
+   problem is what to do when a module "supersedes" another
+   (see also tables and basic tables.)  This is a little dicier
+   because HTML.SafeObject has some extra functionality that
+   trusted might find useful.  See http://htmlpurifier.org/phorum/read.php?3,5762,6100
+
+FUTURE VERSIONS
+---------------
+
+4.8 release [OMG CONFIG PONIES]
+ ! Fix Printer. It's from the old days when we didn't have decent XML classes
+ ! Factor demo.php into a set of Printer classes, and then create a stub
+   file for users here (inside the actual HTML Purifier library)
+ - Fix error handling with form construction
+ - Do encoding validation in Printers, or at least, where user data comes in
+ - Config: Add examples to everything (make built-in which also automatically
+   gives output)
+ - Add "register" field to config schemas to eliminate dependence on
+   naming conventions (try to remember why we ultimately decided on tihs)
+
+5.0 release [HTML 5]
+ # Swap out code to use html5lib tokenizer and tree-builder
+ ! Allow turning off of FixNesting and required attribute insertion
+
+5.1 release [It's All About Trust] (floating)
+ # Implement untrusted, dangerous elements/attributes
+ # Implement IDREF support (harder than it seems, since you cannot have
+   IDREFs to non-existent IDs)
+     - Implement <area> (client and server side image maps are blocking
+       on IDREF support)
+ # Frameset XHTML 1.0 and HTML 4.01 doctypes
+ - Figure out how to simultaneously set %CSS.Trusted and %HTML.Trusted (?)
+
+5.2 release [Error'ed]
+ # Error logging for filtering/cleanup procedures
+ # Additional support for poorly written HTML
+    - Microsoft Word HTML cleaning (i.e. MsoNormal, but research essential!)
+    - Friendly strict handling of <address> (block -> <br>)
+ - XSS-attempt detection--certain errors are flagged XSS-like
+ - Append something to duplicate IDs so they're still usable (impl. note: the
+   dupe detector would also need to detect the suffix as well)
+
+6.0 release [Beyond HTML]
+ # Legit token based CSS parsing (will require revamping almost every
+   AttrDef class). Probably will use CSSTidy
+ # More control over allowed CSS properties using a modularization
+ # IRI support (this includes IDN)
+ - Standardize token armor for all areas of processing
+
+7.0 release [To XML and Beyond]
+ - Extended HTML capabilities based on namespacing and tag transforms (COMPLEX)
+    - Hooks for adding custom processors to custom namespaced tags and
+      attributes, offer default implementation
+    - Lots of documentation and samples
+
+Ongoing
+ - More refactoring to take advantage of PHP5's facilities
+ - Refactor unit tests into lots of test methods
+ - Plugins for major CMSes (COMPLEX)
+    - phpBB
+    - Also, a FAQ for extension writers with HTML Purifier
+
+AutoFormat
+ - Smileys
+ - Syntax highlighting (with GeSHi) with <pre> and possibly <?php
+ - Look at http://drupal.org/project/Modules/category/63 for ideas
+
+Neat feature related
+ ! Support exporting configuration, so users can easily tweak settings
+   in the demo, and then copy-paste into their own setup
+ - Advanced URI filtering schemes (see docs/proposal-new-directives.txt)
+ - Allow scoped="scoped" attribute in <style> tags; may be troublesome
+   because regular CSS has no way of uniquely identifying nodes, so we'd
+   have to generate IDs
+ - Explain how to use HTML Purifier in non-PHP languages / create
+   a simple command line stub (or complicated?)
+ - Fixes for Firefox's inability to handle COL alignment props (Bug 915)
+ - Automatically add non-breaking spaces to empty table cells when
+   empty-cells:show is applied to have compatibility with Internet Explorer
+ - Table of Contents generation (XHTML Compiler might be reusable). May also
+   be out-of-band information.
+ - Full set of color keywords. Also, a way to add onto them without
+   finalizing the configuration object.
+ - Write a var_export and memcached DefinitionCache - Denis
+ - Built-in support for target="_blank" on all external links
+ - Convert RTL/LTR override characters to <bdo> tags, or vice versa on demand.
+   Also, enable disabling of directionality
+ ? Externalize inline CSS to promote clean HTML, proposed by Sander Tekelenburg
+ ? Remove redundant tags, ex. <u><u>Underlined</u></u>. Implementation notes:
+    1. Analyzing which tags to remove duplicants
+    2. Ensure attributes are merged into the parent tag
+    3. Extend the tag exclusion system to specify whether or not the
+    contents should be dropped or not (currently, there's code that could do
+    something like this if it didn't drop the inner text too.)
+ ? Make AutoParagraph also support paragraph-izing double <br> tags, and not
+   just double newlines.  This is kind of tough to do in the current framework,
+   though, and might be reasonably approximated by search replacing double <br>s
+   with newlines before running it through HTML Purifier.
+
+Maintenance related (slightly boring)
+ # CHMOD install script for PEAR installs
+ ! Factor out command line parser into its own class, and unit test it
+ - Reduce size of internal data-structures (esp. HTMLDefinition)
+ - Allow merging configurations.  Thus,
+        a -> b -> default
+        c -> d -> default
+   becomes
+        a -> b -> c -> d -> default
+   Maybe allow more fine-grained tuning of this behavior. Alternatively,
+   encourage people to use short plist depths before building them up.
+ - Time PHPT tests
+
+ChildDef related (very boring)
+ - Abstract ChildDef_BlockQuote to work with all elements that only
+   allow blocks in them, required or optional
+ - Implement lenient <ruby> child validation
+
+Wontfix
+ - Non-lossy smart alternate character encoding transformations (unless
+   patch provided)
+ - Pretty-printing HTML: users can use Tidy on the output on entire page
+ - Native content compression, whitespace stripping: use gzip if this is
+   really important
+
+    vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/VERSION b/library/ezyang/htmlpurifier/VERSION
new file mode 100644 (file)
index 0000000..1163055
--- /dev/null
@@ -0,0 +1 @@
+4.7.0
\ No newline at end of file
diff --git a/library/ezyang/htmlpurifier/WHATSNEW b/library/ezyang/htmlpurifier/WHATSNEW
new file mode 100644 (file)
index 0000000..4e5eb2b
--- /dev/null
@@ -0,0 +1,4 @@
+HTML Purifier 4.7.0 is a bugfix release, collecting two years
+worth of accumulated bug fixes.  Highlighted bugfixes are updated
+YouTube filter code, corrected rgb() CSS parsing, and one new
+configuration option, %AutoFormat.RemoveEmpty.Predicate.
diff --git a/library/ezyang/htmlpurifier/WYSIWYG b/library/ezyang/htmlpurifier/WYSIWYG
new file mode 100644 (file)
index 0000000..c518aac
--- /dev/null
@@ -0,0 +1,20 @@
+
+WYSIWYG - What You See Is What You Get
+    HTML Purifier: A Pretty Good Fit for TinyMCE and FCKeditor
+
+Javascript-based WYSIWYG editors, simply stated, are quite amazing.  But I've
+always been wary about using them due to security issues: they handle the
+client-side magic, but once you've been served a piping hot load of unfiltered
+HTML, what should be done then?  In some situations, you can serve it uncleaned,
+since you only offer these facilities to trusted(?) authors.
+
+Unfortunantely, for blog comments and anonymous input, BBCode, Textile and
+other markup languages still reign supreme.  Put simply: filtering HTML is
+hard work, and these WYSIWYG authors don't offer anything to alleviate that
+trouble.  Therein lies the solution:
+
+HTML Purifier is perfect for filtering pure-HTML input from WYSIWYG editors.
+
+Enough said.
+
+    vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/composer.json b/library/ezyang/htmlpurifier/composer.json
new file mode 100644 (file)
index 0000000..2f59d0f
--- /dev/null
@@ -0,0 +1,22 @@
+{
+    "name": "ezyang/htmlpurifier",
+    "description": "Standards compliant HTML filter written in PHP",
+    "type": "library",
+    "keywords": ["html"],
+    "homepage": "http://htmlpurifier.org/",
+    "license": "LGPL",
+    "authors": [
+        {
+            "name": "Edward Z. Yang",
+            "email": "admin@htmlpurifier.org",
+            "homepage": "http://ezyang.com"
+        }
+    ],
+    "require": {
+        "php": ">=5.2"
+    },
+    "autoload": {
+        "psr-0": { "HTMLPurifier": "library/" },
+        "files": ["library/HTMLPurifier.composer.php"]
+    }
+}
diff --git a/library/ezyang/htmlpurifier/extras/ConfigDoc/HTMLXSLTProcessor.php b/library/ezyang/htmlpurifier/extras/ConfigDoc/HTMLXSLTProcessor.php
new file mode 100644 (file)
index 0000000..1cfec5d
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * Decorator/extender XSLT processor specifically for HTML documents.
+ */
+class ConfigDoc_HTMLXSLTProcessor
+{
+
+    /**
+     * Instance of XSLTProcessor
+     */
+    protected $xsltProcessor;
+
+    public function __construct($proc = false)
+    {
+        if ($proc === false) $proc = new XSLTProcessor();
+        $this->xsltProcessor = $proc;
+    }
+
+    /**
+     * @note Allows a string $xsl filename to be passed
+     */
+    public function importStylesheet($xsl)
+    {
+        if (is_string($xsl)) {
+            $xsl_file = $xsl;
+            $xsl = new DOMDocument();
+            $xsl->load($xsl_file);
+        }
+        return $this->xsltProcessor->importStylesheet($xsl);
+    }
+
+    /**
+     * Transforms an XML file into compatible XHTML based on the stylesheet
+     * @param $xml XML DOM tree, or string filename
+     * @return string HTML output
+     * @todo Rename to transformToXHTML, as transformToHTML is misleading
+     */
+    public function transformToHTML($xml)
+    {
+        if (is_string($xml)) {
+            $dom = new DOMDocument();
+            $dom->load($xml);
+        } else {
+            $dom = $xml;
+        }
+        $out = $this->xsltProcessor->transformToXML($dom);
+
+        // fudges for HTML backwards compatibility
+        // assumes that document is XHTML
+        $out = str_replace('/>', ' />', $out); // <br /> not <br/>
+        $out = str_replace(' xmlns=""', '', $out); // rm unnecessary xmlns
+
+        if (class_exists('Tidy')) {
+            // cleanup output
+            $config = array(
+                'indent'        => true,
+                'output-xhtml'  => true,
+                'wrap'          => 80
+            );
+            $tidy = new Tidy;
+            $tidy->parseString($out, $config, 'utf8');
+            $tidy->cleanRepair();
+            $out = (string) $tidy;
+        }
+
+        return $out;
+    }
+
+    /**
+     * Bulk sets parameters for the XSL stylesheet
+     * @param array $options Associative array of options to set
+     */
+    public function setParameters($options)
+    {
+        foreach ($options as $name => $value) {
+            $this->xsltProcessor->setParameter('', $name, $value);
+        }
+    }
+
+    /**
+     * Forward any other calls to the XSLT processor
+     */
+    public function __call($name, $arguments)
+    {
+        call_user_func_array(array($this->xsltProcessor, $name), $arguments);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/extras/FSTools.php b/library/ezyang/htmlpurifier/extras/FSTools.php
new file mode 100644 (file)
index 0000000..ce00763
--- /dev/null
@@ -0,0 +1,164 @@
+<?php
+
+/**
+ * Filesystem tools not provided by default; can recursively create, copy
+ * and delete folders. Some template methods are provided for extensibility.
+ *
+ * @note This class must be instantiated to be used, although it does
+ *       not maintain state.
+ */
+class FSTools
+{
+
+    private static $singleton;
+
+    /**
+     * Returns a global instance of FSTools
+     */
+    public static function singleton()
+    {
+        if (empty(FSTools::$singleton)) FSTools::$singleton = new FSTools();
+        return FSTools::$singleton;
+    }
+
+    /**
+     * Sets our global singleton to something else; useful for overloading
+     * functions.
+     */
+    public static function setSingleton($singleton)
+    {
+        FSTools::$singleton = $singleton;
+    }
+
+    /**
+     * Recursively creates a directory
+     * @param string $folder Name of folder to create
+     * @note Adapted from the PHP manual comment 76612
+     */
+    public function mkdirr($folder)
+    {
+        $folders = preg_split("#[\\\\/]#", $folder);
+        $base = '';
+        for($i = 0, $c = count($folders); $i < $c; $i++) {
+            if(empty($folders[$i])) {
+                if (!$i) {
+                    // special case for root level
+                    $base .= DIRECTORY_SEPARATOR;
+                }
+                continue;
+            }
+            $base .= $folders[$i];
+            if(!is_dir($base)){
+                $this->mkdir($base);
+            }
+            $base .= DIRECTORY_SEPARATOR;
+        }
+    }
+
+    /**
+     * Copy a file, or recursively copy a folder and its contents; modified
+     * so that copied files, if PHP, have includes removed
+     * @note Adapted from http://aidanlister.com/repos/v/function.copyr.php
+     */
+    public function copyr($source, $dest)
+    {
+        // Simple copy for a file
+        if (is_file($source)) {
+            return $this->copy($source, $dest);
+        }
+        // Make destination directory
+        if (!is_dir($dest)) {
+            $this->mkdir($dest);
+        }
+        // Loop through the folder
+        $dir = $this->dir($source);
+        while ( false !== ($entry = $dir->read()) ) {
+            // Skip pointers
+            if ($entry == '.' || $entry == '..') {
+                continue;
+            }
+            if (!$this->copyable($entry)) {
+                continue;
+            }
+            // Deep copy directories
+            if ($dest !== "$source/$entry") {
+                $this->copyr("$source/$entry", "$dest/$entry");
+            }
+        }
+        // Clean up
+        $dir->close();
+        return true;
+    }
+
+    /**
+     * Overloadable function that tests a filename for copyability. By
+     * default, everything should be copied; you can restrict things to
+     * ignore hidden files, unreadable files, etc. This function
+     * applies to copyr().
+     */
+    public function copyable($file)
+    {
+        return true;
+    }
+
+    /**
+     * Delete a file, or a folder and its contents
+     * @note Adapted from http://aidanlister.com/repos/v/function.rmdirr.php
+     */
+    public function rmdirr($dirname)
+    {
+        // Sanity check
+        if (!$this->file_exists($dirname)) {
+            return false;
+        }
+
+        // Simple delete for a file
+        if ($this->is_file($dirname) || $this->is_link($dirname)) {
+            return $this->unlink($dirname);
+        }
+
+        // Loop through the folder
+        $dir = $this->dir($dirname);
+        while (false !== $entry = $dir->read()) {
+            // Skip pointers
+            if ($entry == '.' || $entry == '..') {
+                continue;
+            }
+            // Recurse
+            $this->rmdirr($dirname . DIRECTORY_SEPARATOR . $entry);
+        }
+
+        // Clean up
+        $dir->close();
+        return $this->rmdir($dirname);
+    }
+
+    /**
+     * Recursively globs a directory.
+     */
+    public function globr($dir, $pattern, $flags = NULL)
+    {
+        $files = $this->glob("$dir/$pattern", $flags);
+        if ($files === false) $files = array();
+        $sub_dirs = $this->glob("$dir/*", GLOB_ONLYDIR);
+        if ($sub_dirs === false) $sub_dirs = array();
+        foreach ($sub_dirs as $sub_dir) {
+            $sub_files = $this->globr($sub_dir, $pattern, $flags);
+            $files = array_merge($files, $sub_files);
+        }
+        return $files;
+    }
+
+    /**
+     * Allows for PHP functions to be called and be stubbed.
+     * @warning This function will not work for functions that need
+     *      to pass references; manually define a stub function for those.
+     */
+    public function __call($name, $args)
+    {
+        return call_user_func_array($name, $args);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/extras/FSTools/File.php b/library/ezyang/htmlpurifier/extras/FSTools/File.php
new file mode 100644 (file)
index 0000000..6453a7a
--- /dev/null
@@ -0,0 +1,141 @@
+<?php
+
+/**
+ * Represents a file in the filesystem
+ *
+ * @warning Be sure to distinguish between get() and write() versus
+ *      read() and put(), the former operates on the entire file, while
+ *      the latter operates on a handle.
+ */
+class FSTools_File
+{
+
+    /** Filename of file this object represents */
+    protected $name;
+
+    /** Handle for the file */
+    protected $handle = false;
+
+    /** Instance of FSTools for interfacing with filesystem */
+    protected $fs;
+
+    /**
+     * Filename of file you wish to instantiate.
+     * @note This file need not exist
+     */
+    public function __construct($name, $fs = false)
+    {
+        $this->name = $name;
+        $this->fs = $fs ? $fs : FSTools::singleton();
+    }
+
+    /** Returns the filename of the file. */
+    public function getName() {return $this->name;}
+
+    /** Returns directory of the file without trailing slash */
+    public function getDirectory() {return $this->fs->dirname($this->name);}
+
+    /**
+     * Retrieves the contents of a file
+     * @todo Throw an exception if file doesn't exist
+     */
+    public function get()
+    {
+        return $this->fs->file_get_contents($this->name);
+    }
+
+    /** Writes contents to a file, creates new file if necessary */
+    public function write($contents)
+    {
+        return $this->fs->file_put_contents($this->name, $contents);
+    }
+
+    /** Deletes the file */
+    public function delete()
+    {
+        return $this->fs->unlink($this->name);
+    }
+
+    /** Returns true if file exists and is a file. */
+    public function exists()
+    {
+        return $this->fs->is_file($this->name);
+    }
+
+    /** Returns last file modification time */
+    public function getMTime()
+    {
+        return $this->fs->filemtime($this->name);
+    }
+
+    /**
+     * Chmod a file
+     * @note We ignore errors because of some weird owner trickery due
+     *       to SVN duality
+     */
+    public function chmod($octal_code)
+    {
+        return @$this->fs->chmod($this->name, $octal_code);
+    }
+
+    /** Opens file's handle */
+    public function open($mode)
+    {
+        if ($this->handle) $this->close();
+        $this->handle = $this->fs->fopen($this->name, $mode);
+        return true;
+    }
+
+    /** Closes file's handle */
+    public function close()
+    {
+        if (!$this->handle) return false;
+        $status = $this->fs->fclose($this->handle);
+        $this->handle = false;
+        return $status;
+    }
+
+    /** Retrieves a line from an open file, with optional max length $length */
+    public function getLine($length = null)
+    {
+        if (!$this->handle) $this->open('r');
+        if ($length === null) return $this->fs->fgets($this->handle);
+        else return $this->fs->fgets($this->handle, $length);
+    }
+
+    /** Retrieves a character from an open file */
+    public function getChar()
+    {
+        if (!$this->handle) $this->open('r');
+        return $this->fs->fgetc($this->handle);
+    }
+
+    /** Retrieves an $length bytes of data from an open data */
+    public function read($length)
+    {
+        if (!$this->handle) $this->open('r');
+        return $this->fs->fread($this->handle, $length);
+    }
+
+    /** Writes to an open file */
+    public function put($string)
+    {
+        if (!$this->handle) $this->open('a');
+        return $this->fs->fwrite($this->handle, $string);
+    }
+
+    /** Returns TRUE if the end of the file has been reached */
+    public function eof()
+    {
+        if (!$this->handle) return true;
+        return $this->fs->feof($this->handle);
+    }
+
+    public function __destruct()
+    {
+        if ($this->handle) $this->close();
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.auto.php b/library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.auto.php
new file mode 100644 (file)
index 0000000..4016d8a
--- /dev/null
@@ -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 'HTMLPurifierExtras.php';
+require_once 'HTMLPurifierExtras.autoload.php';
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload.php b/library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload.php
new file mode 100644 (file)
index 0000000..de4a8aa
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Convenience file that registers autoload handler for HTML Purifier.
+ *
+ * @warning
+ *      This autoloader does not contain the compatibility code seen in
+ *      HTMLPurifier_Bootstrap; the user is expected to make any necessary
+ *      changes to use this library.
+ */
+
+if (function_exists('spl_autoload_register')) {
+    spl_autoload_register(array('HTMLPurifierExtras', 'autoload'));
+    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 HTMLPurifierExtras::autoload($class);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.php b/library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.php
new file mode 100644 (file)
index 0000000..35c2ca7
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * Meta-class for HTML Purifier's extra class hierarchies, similar to
+ * HTMLPurifier_Bootstrap.
+ */
+class HTMLPurifierExtras
+{
+
+    public static function autoload($class)
+    {
+        $path = HTMLPurifierExtras::getPath($class);
+        if (!$path) return false;
+        require $path;
+        return true;
+    }
+
+    public static function getPath($class)
+    {
+        if (
+            strncmp('FSTools', $class, 7) !== 0 &&
+            strncmp('ConfigDoc', $class, 9) !== 0
+        ) return false;
+        // Custom implementations can go here
+        // Standard implementation:
+        return str_replace('_', '/', $class) . '.php';
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/extras/README b/library/ezyang/htmlpurifier/extras/README
new file mode 100644 (file)
index 0000000..4bfece7
--- /dev/null
@@ -0,0 +1,32 @@
+
+HTML Purifier Extras
+    The Method Behind The Madness!
+
+The extras/ folder in HTML Purifier contains--you guessed it--extra things
+for HTML Purifier.  Specifically, these are two extra libraries called
+FSTools and ConfigSchema.  They're extra for a reason: you don't need them
+if you're using HTML Purifier for normal usage: filtering HTML.  However,
+if you're a developer, and would like to test HTML Purifier, or need to
+use one of HTML Purifier's maintenance scripts, chances are they'll need
+these libraries. Who knows: maybe you'll find them useful too!
+
+Here are the libraries:
+
+
+FSTools
+-------
+
+Short for File System Tools, this is a poor-man's object-oriented wrapper for
+the filesystem. It currently consists of two classes:
+
+- FSTools: This is a singleton that contains a manner of useful functions
+  such as recursive glob, directory removal, etc, as well as the ability
+  to call arbitrary native PHP functions through it like $FS->fopen(...).
+  This makes it a lot simpler to mock these filesystem calls for unit testing.
+
+- FSTools_File: This object represents a single file, and has almost any
+  method imaginable one would need.
+
+Check the files themselves for more information.
+
+    vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier.auto.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.auto.php
new file mode 100644 (file)
index 0000000..1960c39
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier.autoload.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.autoload.php
new file mode 100644 (file)
index 0000000..c3ea67e
--- /dev/null
@@ -0,0 +1,27 @@
+<?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/library/ezyang/htmlpurifier/library/HTMLPurifier.composer.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.composer.php
new file mode 100644 (file)
index 0000000..52acc56
--- /dev/null
@@ -0,0 +1,4 @@
+<?php
+if (!defined('HTMLPURIFIER_PREFIX')) {
+    define('HTMLPURIFIER_PREFIX', dirname(__FILE__));
+}
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier.func.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.func.php
new file mode 100644 (file)
index 0000000..64b140b
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Defines a function wrapper for HTML Purifier for quick use.
+ * @note ''HTMLPurifier()'' is NOT the same as ''new HTMLPurifier()''
+ */
+
+/**
+ * Purify HTML.
+ * @param string $html String HTML to purify
+ * @param mixed $config Configuration to use, can be any value accepted by
+ *        HTMLPurifier_Config::create()
+ * @return string
+ */
+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/library/ezyang/htmlpurifier/library/HTMLPurifier.includes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.includes.php
new file mode 100644 (file)
index 0000000..fdb58c2
--- /dev/null
@@ -0,0 +1,229 @@
+<?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.7.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/Arborize.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/Node.php';
+require 'HTMLPurifier/PercentEncoder.php';
+require 'HTMLPurifier/PropertyList.php';
+require 'HTMLPurifier/PropertyListIterator.php';
+require 'HTMLPurifier/Queue.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/Zipper.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/Node/Comment.php';
+require 'HTMLPurifier/Node/Element.php';
+require 'HTMLPurifier/Node/Text.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/library/ezyang/htmlpurifier/library/HTMLPurifier.kses.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.kses.php
new file mode 100644 (file)
index 0000000..7522900
--- /dev/null
@@ -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);
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier.path.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.path.php
new file mode 100644 (file)
index 0000000..39b1b65
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.php
new file mode 100644 (file)
index 0000000..c6041bc
--- /dev/null
@@ -0,0 +1,292 @@
+<?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.7.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.
+     * @type string
+     */
+    public $version = '4.7.0';
+
+    /**
+     * Constant with version of HTML Purifier.
+     */
+    const VERSION = '4.7.0';
+
+    /**
+     * Global configuration object.
+     * @type HTMLPurifier_Config
+     */
+    public $config;
+
+    /**
+     * Array of extra filter objects to run on HTML,
+     * for backwards compatibility.
+     * @type HTMLPurifier_Filter[]
+     */
+    private $filters = array();
+
+    /**
+     * Single instance of HTML Purifier.
+     * @type HTMLPurifier
+     */
+    private static $instance;
+
+    /**
+     * @type HTMLPurifier_Strategy_Core
+     */
+    protected $strategy;
+
+    /**
+     * @type HTMLPurifier_Generator
+     */
+    protected $generator;
+
+    /**
+     * Resultant context of last run purification.
+     * Is an array of contexts if the last called method was purifyArray().
+     * @type HTMLPurifier_Context
+     */
+    public $context;
+
+    /**
+     * Initializes the purifier.
+     *
+     * @param HTMLPurifier_Config $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 HTMLPurifier_Filter $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 string $html String of HTML to purify
+     * @param HTMLPurifier_Config $config 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 string 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 string[] $array_of_html Array of html snippets
+     * @param HTMLPurifier_Config $config Optional config object for this operation.
+     *                See HTMLPurifier::purify() for more details.
+     *
+     * @return string[] 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 HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype
+     *                   HTMLPurifier instance to overload singleton with,
+     *                   or HTMLPurifier_Config instance to configure the
+     *                   generated version with.
+     *
+     * @return HTMLPurifier
+     */
+    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;
+    }
+
+    /**
+     * Singleton for enforcing just one HTML Purifier in your system
+     *
+     * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype
+     *                   HTMLPurifier instance to overload singleton with,
+     *                   or HTMLPurifier_Config instance to configure the
+     *                   generated version with.
+     *
+     * @return HTMLPurifier
+     * @note Backwards compatibility, see instance()
+     */
+    public static function getInstance($prototype = null)
+    {
+        return HTMLPurifier::instance($prototype);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php
new file mode 100644 (file)
index 0000000..9dea6d1
--- /dev/null
@@ -0,0 +1,223 @@
+<?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/Arborize.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/Node.php';
+require_once $__dir . '/HTMLPurifier/PercentEncoder.php';
+require_once $__dir . '/HTMLPurifier/PropertyList.php';
+require_once $__dir . '/HTMLPurifier/PropertyListIterator.php';
+require_once $__dir . '/HTMLPurifier/Queue.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/Zipper.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/Node/Comment.php';
+require_once $__dir . '/HTMLPurifier/Node/Element.php';
+require_once $__dir . '/HTMLPurifier/Node/Text.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/library/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.php
new file mode 100644 (file)
index 0000000..9e6617b
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * Converts a stream of HTMLPurifier_Token into an HTMLPurifier_Node,
+ * and back again.
+ *
+ * @note This transformation is not an equivalence.  We mutate the input
+ * token stream to make it so; see all [MUT] markers in code.
+ */
+class HTMLPurifier_Arborize
+{
+    public static function arborize($tokens, $config, $context) {
+        $definition = $config->getHTMLDefinition();
+        $parent = new HTMLPurifier_Token_Start($definition->info_parent);
+        $stack = array($parent->toNode());
+        foreach ($tokens as $token) {
+            $token->skip = null; // [MUT]
+            $token->carryover = null; // [MUT]
+            if ($token instanceof HTMLPurifier_Token_End) {
+                $token->start = null; // [MUT]
+                $r = array_pop($stack);
+                assert($r->name === $token->name);
+                assert(empty($token->attr));
+                $r->endCol = $token->col;
+                $r->endLine = $token->line;
+                $r->endArmor = $token->armor;
+                continue;
+            }
+            $node = $token->toNode();
+            $stack[count($stack)-1]->children[] = $node;
+            if ($token instanceof HTMLPurifier_Token_Start) {
+                $stack[] = $node;
+            }
+        }
+        assert(count($stack) == 1);
+        return $stack[0];
+    }
+
+    public static function flatten($node, $config, $context) {
+        $level = 0;
+        $nodes = array($level => new HTMLPurifier_Queue(array($node)));
+        $closingTokens = array();
+        $tokens = array();
+        do {
+            while (!$nodes[$level]->isEmpty()) {
+                $node = $nodes[$level]->shift(); // FIFO
+                list($start, $end) = $node->toTokenPair();
+                if ($level > 0) {
+                    $tokens[] = $start;
+                }
+                if ($end !== NULL) {
+                    $closingTokens[$level][] = $end;
+                }
+                if ($node instanceof HTMLPurifier_Node_Element) {
+                    $level++;
+                    $nodes[$level] = new HTMLPurifier_Queue();
+                    foreach ($node->children as $childNode) {
+                        $nodes[$level]->push($childNode);
+                    }
+                }
+            }
+            $level--;
+            if ($level && isset($closingTokens[$level])) {
+                while ($token = array_pop($closingTokens[$level])) {
+                    $tokens[] = $token;
+                }
+            }
+        } while ($level > 0);
+        return $tokens;
+    }
+}
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php
new file mode 100644 (file)
index 0000000..4f6c2e3
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * Defines common attribute collections that modules reference
+ */
+
+class HTMLPurifier_AttrCollections
+{
+
+    /**
+     * Associative array of attribute collections, indexed by name.
+     * @type array
+     */
+    public $info = array();
+
+    /**
+     * Performs all expansions on internal data for use by other inclusions
+     * It also collects all attribute collection extensions from
+     * modules
+     * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
+     * @param HTMLPurifier_HTMLModule[] $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 array &$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 array &$attr Reference to attribute array
+     * @param HTMLPurifier_AttrTypes $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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php
new file mode 100644 (file)
index 0000000..5ac0652
--- /dev/null
@@ -0,0 +1,138 @@
+<?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.
+     * @type bool
+     */
+    public $minimized = false;
+
+    /**
+     * Tells us whether or not an HTML attribute is required.
+     * Has no meaning in other contexts
+     * @type bool
+     */
+    public $required = false;
+
+    /**
+     * Validates and cleans passed string according to a definition.
+     *
+     * @param string $string String to be validated and cleaned.
+     * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
+     * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context 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 String construction info
+     * @return HTMLPurifier_AttrDef 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!
+     * @param string $string a CSS colour definition
+     * @return string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php
new file mode 100644 (file)
index 0000000..02c1641
--- /dev/null
@@ -0,0 +1,106 @@
+<?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
+{
+
+    /**
+     * @param string $css
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php
new file mode 100644 (file)
index 0000000..af2b83d
--- /dev/null
@@ -0,0 +1,34 @@
+<?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
+    }
+
+    /**
+     * @param string $number
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php
new file mode 100644 (file)
index 0000000..7f1ea3b
--- /dev/null
@@ -0,0 +1,111 @@
+<?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.
+     * @type HTMLPurifier_AttrDef[]
+     * @note See HTMLPurifier_AttrDef_Font::$info for a similar impl.
+     */
+    protected $info;
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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'];
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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
+
+        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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php
new file mode 100644 (file)
index 0000000..4580ef5
--- /dev/null
@@ -0,0 +1,157 @@
+<?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
+{
+
+    /**
+     * @type HTMLPurifier_AttrDef_CSS_Length
+     */
+    protected $length;
+
+    /**
+     * @type HTMLPurifier_AttrDef_CSS_Percentage
+     */
+    protected $percentage;
+
+    public function __construct()
+    {
+        $this->length = new HTMLPurifier_AttrDef_CSS_Length();
+        $this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage();
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php
new file mode 100644 (file)
index 0000000..16243ba
--- /dev/null
@@ -0,0 +1,56 @@
+<?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.
+     * @type HTMLPurifier_AttrDef[]
+     */
+    protected $info = array();
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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'];
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php
new file mode 100644 (file)
index 0000000..16d2a6b
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * Validates Color as defined by CSS.
+ */
+class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * @param string $color
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php
new file mode 100644 (file)
index 0000000..9c17505
--- /dev/null
@@ -0,0 +1,48 @@
+<?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 objects that may process strings.
+     * @type HTMLPurifier_AttrDef[]
+     * @todo Make protected
+     */
+    public $defs;
+
+    /**
+     * @param HTMLPurifier_AttrDef[] $defs List of HTMLPurifier_AttrDef objects
+     */
+    public function __construct($defs)
+    {
+        $this->defs = $defs;
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php
new file mode 100644 (file)
index 0000000..9d77cc9
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Decorator which enables CSS properties to be disabled for specific elements.
+ */
+class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef
+{
+    /**
+     * @type HTMLPurifier_AttrDef
+     */
+    public $def;
+    /**
+     * @type string
+     */
+    public $element;
+
+    /**
+     * @param HTMLPurifier_AttrDef $def Definition to wrap
+     * @param string $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
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php
new file mode 100644 (file)
index 0000000..bde4c33
--- /dev/null
@@ -0,0 +1,77 @@
+<?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
+{
+    /**
+     * @type HTMLPurifier_AttrDef_Integer
+     */
+    protected $intValidator;
+
+    public function __construct()
+    {
+        $this->intValidator = new HTMLPurifier_AttrDef_Integer();
+    }
+
+    /**
+     * @param string $value
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php
new file mode 100644 (file)
index 0000000..579b97e
--- /dev/null
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * Validates shorthand CSS property font.
+ */
+class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * Local copy of validators
+     * @type HTMLPurifier_AttrDef[]
+     * @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();
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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'];
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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) {
+                case 0: // attempting to catch font-style, font-variant or font-weight
+                    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;
+                    }
+                case 1: // attempting to catch font-size and perhaps line-height
+                    $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;
+                case 2: // attempting to catch font-family
+                    $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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php
new file mode 100644 (file)
index 0000000..74e24c8
--- /dev/null
@@ -0,0 +1,219 @@
+<?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.
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php
new file mode 100644 (file)
index 0000000..973002c
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Validates based on {ident} CSS grammar production
+ */
+class HTMLPurifier_AttrDef_CSS_Ident extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php
new file mode 100644 (file)
index 0000000..ffc989f
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * Decorator which enables !important to be used in CSS values.
+ */
+class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef
+{
+    /**
+     * @type HTMLPurifier_AttrDef
+     */
+    public $def;
+    /**
+     * @type bool
+     */
+    public $allow;
+
+    /**
+     * @param HTMLPurifier_AttrDef $def Definition to wrap
+     * @param bool $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
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php
new file mode 100644 (file)
index 0000000..f12453a
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * Represents a Length as defined by CSS.
+ */
+class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * @type HTMLPurifier_Length|string
+     */
+    protected $min;
+
+    /**
+     * @type HTMLPurifier_Length|string
+     */
+    protected $max;
+
+    /**
+     * @param HTMLPurifier_Length|string $min Minimum length, or null for no bound. String is also acceptable.
+     * @param HTMLPurifier_Length|string $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;
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php
new file mode 100644 (file)
index 0000000..e74d426
--- /dev/null
@@ -0,0 +1,112 @@
+<?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 validators.
+     * @type HTMLPurifier_AttrDef[]
+     * @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl.
+     */
+    protected $info;
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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'];
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php
new file mode 100644 (file)
index 0000000..e707f87
--- /dev/null
@@ -0,0 +1,71 @@
+<?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.
+     * @type HTMLPurifier_AttrDef
+     * @todo Make protected
+     */
+    public $single;
+
+    /**
+     * Max number of values allowed.
+     * @todo Make protected
+     */
+    public $max;
+
+    /**
+     * @param HTMLPurifier_AttrDef $single HTMLPurifier_AttrDef to multiply
+     * @param int $max Max number of values allowed (usually four)
+     */
+    public function __construct($single, $max = 4)
+    {
+        $this->single = $single;
+        $this->max = $max;
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    public function validate($string, $config, $context)
+    {
+        $string = $this->mungeRgb($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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php
new file mode 100644 (file)
index 0000000..8edc159
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * Validates a number as defined by the CSS spec.
+ */
+class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * Indicates whether or not only positive values are allowed.
+     * @type bool
+     */
+    protected $non_negative = false;
+
+    /**
+     * @param bool $non_negative indicates whether negatives are forbidden
+     */
+    public function __construct($non_negative = false)
+    {
+        $this->non_negative = $non_negative;
+    }
+
+    /**
+     * @param string $number
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return string|bool
+     * @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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php
new file mode 100644 (file)
index 0000000..f0f25c5
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * Validates a Percentage as defined by the CSS spec.
+ */
+class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * Instance to defer number validation to.
+     * @type HTMLPurifier_AttrDef_CSS_Number
+     */
+    protected $number_def;
+
+    /**
+     * @param bool $non_negative Whether to forbid negative values
+     */
+    public function __construct($non_negative = false)
+    {
+        $this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php
new file mode 100644 (file)
index 0000000..5fd4b7f
--- /dev/null
@@ -0,0 +1,46 @@
+<?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
+{
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php
new file mode 100644 (file)
index 0000000..f943423
--- /dev/null
@@ -0,0 +1,74 @@
+<?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
+    }
+
+    /**
+     * @param string $uri_string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php
new file mode 100644 (file)
index 0000000..6698a00
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Dummy AttrDef that mimics another AttrDef, BUT it generates clones
+ * with make.
+ */
+class HTMLPurifier_AttrDef_Clone extends HTMLPurifier_AttrDef
+{
+    /**
+     * What we're cloning.
+     * @type HTMLPurifier_AttrDef
+     */
+    protected $clone;
+
+    /**
+     * @param HTMLPurifier_AttrDef $clone
+     */
+    public function __construct($clone)
+    {
+        $this->clone = $clone;
+    }
+
+    /**
+     * @param string $v
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    public function validate($v, $config, $context)
+    {
+        return $this->clone->validate($v, $config, $context);
+    }
+
+    /**
+     * @param string $string
+     * @return HTMLPurifier_AttrDef
+     */
+    public function make($string)
+    {
+        return clone $this->clone;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php
new file mode 100644 (file)
index 0000000..8abda7f
--- /dev/null
@@ -0,0 +1,73 @@
+<?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.
+     * @type array
+     * @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 array $valid_values List of valid values
+     * @param bool $case_sensitive 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;
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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 $string In form of comma-delimited list of case-insensitive
+     *      valid values. Example: "foo,bar,baz". Prepend "s:" to make
+     *      case sensitive
+     * @return HTMLPurifier_AttrDef_Enum
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php
new file mode 100644 (file)
index 0000000..dea15d2
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Validates a boolean attribute
+ */
+class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * @type bool
+     */
+    protected $name;
+
+    /**
+     * @type bool
+     */
+    public $minimized = true;
+
+    /**
+     * @param bool $name
+     */
+    public function __construct($name = false)
+    {
+        $this->name = $name;
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    public function validate($string, $config, $context)
+    {
+        return $this->name;
+    }
+
+    /**
+     * @param string $string Name of attribute
+     * @return HTMLPurifier_AttrDef_HTML_Bool
+     */
+    public function make($string)
+    {
+        return new HTMLPurifier_AttrDef_HTML_Bool($string);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php
new file mode 100644 (file)
index 0000000..d501348
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Implements special behavior for class attribute (normally NMTOKENS)
+ */
+class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens
+{
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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);
+        }
+    }
+
+    /**
+     * @param array $tokens
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php
new file mode 100644 (file)
index 0000000..946ebb7
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * Validates a color according to the HTML spec.
+ */
+class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php
new file mode 100644 (file)
index 0000000..d79ba12
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * Special-case enum attribute definition that lazy loads allowed frame targets
+ */
+class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum
+{
+
+    /**
+     * @type array
+     */
+    public $valid_values = false; // uninitialized value
+
+    /**
+     * @type bool
+     */
+    protected $case_sensitive = false;
+
+    public function __construct()
+    {
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php
new file mode 100644 (file)
index 0000000..3d86efb
--- /dev/null
@@ -0,0 +1,105 @@
+<?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.
+     * @type bool
+     */
+    protected $selector;
+
+    /**
+     * @param bool $selector
+     */
+    public function __construct($selector = false)
+    {
+        $this->selector = $selector;
+    }
+
+    /**
+     * @param string $id
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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;
+            }
+            // primitive style of regexps, I suppose
+            $trim = trim(
+                $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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php
new file mode 100644 (file)
index 0000000..1c4006f
--- /dev/null
@@ -0,0 +1,56 @@
+<?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
+{
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php
new file mode 100644 (file)
index 0000000..63fa04c
--- /dev/null
@@ -0,0 +1,72 @@
+<?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.
+     * @type string
+     */
+    protected $name;
+
+    /**
+     * @param string $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];
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php
new file mode 100644 (file)
index 0000000..bbb20f2
--- /dev/null
@@ -0,0 +1,60 @@
+<?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
+{
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php
new file mode 100644 (file)
index 0000000..f79683b
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * Validates contents based on NMTOKENS attribute type.
+ */
+class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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.
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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.
+     * @param array $tokens
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    protected function filter($tokens, $config, $context)
+    {
+        return $tokens;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php
new file mode 100644 (file)
index 0000000..a1d019e
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * Validates an integer representation of pixels according to the HTML spec.
+ */
+class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * @type int
+     */
+    protected $max;
+
+    /**
+     * @param int $max
+     */
+    public function __construct($max = null)
+    {
+        $this->max = $max;
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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;
+    }
+
+    /**
+     * @param string $string
+     * @return HTMLPurifier_AttrDef
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php
new file mode 100644 (file)
index 0000000..400e707
--- /dev/null
@@ -0,0 +1,91 @@
+<?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
+{
+
+    /**
+     * Whether or not negative values are allowed.
+     * @type bool
+     */
+    protected $negative = true;
+
+    /**
+     * Whether or not zero is allowed.
+     * @type bool
+     */
+    protected $zero = true;
+
+    /**
+     * Whether or not positive values are allowed.
+     * @type bool
+     */
+    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;
+    }
+
+    /**
+     * @param string $integer
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php
new file mode 100644 (file)
index 0000000..2a55cea
--- /dev/null
@@ -0,0 +1,86 @@
+<?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
+{
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    public function validate($string, $config, $context)
+    {
+        $string = trim($string);
+        if (!$string) {
+            return false;
+        }
+
+        $subtags = explode('-', $string);
+        $num_subtags = count($subtags);
+
+        if ($num_subtags == 0) { // sanity check
+            return false;
+        }
+
+        // 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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php
new file mode 100644 (file)
index 0000000..c7eb319
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * Decorator that, depending on a token, switches between two definitions.
+ */
+class HTMLPurifier_AttrDef_Switch
+{
+
+    /**
+     * @type string
+     */
+    protected $tag;
+
+    /**
+     * @type HTMLPurifier_AttrDef
+     */
+    protected $withTag;
+
+    /**
+     * @type HTMLPurifier_AttrDef
+     */
+    protected $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;
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php
new file mode 100644 (file)
index 0000000..4553a4e
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * Validates arbitrary text according to the HTML spec.
+ */
+class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    public function validate($string, $config, $context)
+    {
+        return $this->parseCDATA($string);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php
new file mode 100644 (file)
index 0000000..c1cd897
--- /dev/null
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * Validates a URI as defined by RFC 3986.
+ * @note Scheme-specific mechanics deferred to HTMLPurifier_URIScheme
+ */
+class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * @type HTMLPurifier_URIParser
+     */
+    protected $parser;
+
+    /**
+     * @type bool
+     */
+    protected $embedsResource;
+
+    /**
+     * @param bool $embeds_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;
+    }
+
+    /**
+     * @param string $string
+     * @return HTMLPurifier_AttrDef_URI
+     */
+    public function make($string)
+    {
+        $embeds = ($string === 'embedded');
+        return new HTMLPurifier_AttrDef_URI($embeds);
+    }
+
+    /**
+     * @param string $uri
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php
new file mode 100644 (file)
index 0000000..daf32b7
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+abstract class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * Unpacks a mailbox into its display-name and address
+     * @param string $string
+     * @return mixed
+     */
+    public function unpack($string)
+    {
+        // needs to be implemented
+    }
+
+}
+
+// sub-implementations
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php
new file mode 100644 (file)
index 0000000..52c0d59
--- /dev/null
@@ -0,0 +1,29 @@
+<?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
+{
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    public function validate($string, $config, $context)
+    {
+        // no support for named mailboxes i.e. "Bob <bob@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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php
new file mode 100644 (file)
index 0000000..e7df800
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * Validates a host according to the IPv4, IPv6 and DNS (future) specifications.
+ */
+class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * IPv4 sub-validator.
+     * @type HTMLPurifier_AttrDef_URI_IPv4
+     */
+    protected $ipv4;
+
+    /**
+     * IPv6 sub-validator.
+     * @type HTMLPurifier_AttrDef_URI_IPv6
+     */
+    protected $ipv6;
+
+    public function __construct()
+    {
+        $this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4();
+        $this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6();
+    }
+
+    /**
+     * @param string $string
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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.
+
+        // There is not a good sense in which underscores should be
+        // allowed, since it's technically not! (And if you go as
+        // far to allow everything as specified by the DNS spec...
+        // well, that's literally everything, modulo some space limits
+        // for the components and the overall name (which, by the way,
+        // we are NOT checking!).  So we (arbitrarily) decide this:
+        // let's allow underscores wherever we would have allowed
+        // hyphens, if they are enabled.  This is a pretty good match
+        // for browser behavior, for example, a large number of browsers
+        // cannot handle foo_.example.com, but foo_bar.example.com is
+        // fairly well supported.
+        $underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : '';
+
+        // The productions describing this are:
+        $a   = '[a-z]';     // alpha
+        $an  = '[a-z0-9]';  // alphanum
+        $and = "[a-z0-9-$underscore]"; // 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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php
new file mode 100644 (file)
index 0000000..30ac16c
--- /dev/null
@@ -0,0 +1,45 @@
+<?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.
+     * @type string
+     */
+    protected $ip4;
+
+    /**
+     * @param string $aIP
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php
new file mode 100644 (file)
index 0000000..f243793
--- /dev/null
@@ -0,0 +1,89 @@
+<?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
+{
+
+    /**
+     * @param string $aIP
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php
new file mode 100644 (file)
index 0000000..b428331
--- /dev/null
@@ -0,0 +1,60 @@
+<?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 array $attr Assoc array of attributes, usually from
+     *              HTMLPurifier_Token_Tag::$attr
+     * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
+     * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object
+     * @return array 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 array &$attr Attribute array to process (passed by reference)
+     * @param string $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 array &$attr Attribute array to process (passed by reference)
+     * @param mixed $key Key of attribute to confiscate
+     * @return mixed
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Background.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Background.php
new file mode 100644 (file)
index 0000000..2f72869
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Pre-transform that changes proprietary background attribute to CSS.
+ */
+class HTMLPurifier_AttrTransform_Background extends HTMLPurifier_AttrTransform
+{
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php
new file mode 100644 (file)
index 0000000..d66c04a
--- /dev/null
@@ -0,0 +1,27 @@
+<?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
+{
+
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php
new file mode 100644 (file)
index 0000000..0f51fd2
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Pre-transform that changes deprecated bgcolor attribute to CSS.
+ */
+class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform
+{
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php
new file mode 100644 (file)
index 0000000..f25cd01
--- /dev/null
@@ -0,0 +1,47 @@
+<?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.
+     * @type string
+     */
+    protected $attr;
+
+    /**
+     * CSS declarations to add to style, needs trailing semicolon.
+     * @type string
+     */
+    protected $css;
+
+    /**
+     * @param string $attr attribute name to convert from
+     * @param string $css CSS declarations to add to style (needs semicolon)
+     */
+    public function __construct($attr, $css)
+    {
+        $this->attr = $attr;
+        $this->css = $css;
+    }
+
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php
new file mode 100644 (file)
index 0000000..057dc01
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * Pre-transform that changes deprecated border attribute to CSS.
+ */
+class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform
+{
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php
new file mode 100644 (file)
index 0000000..7ccd0e3
--- /dev/null
@@ -0,0 +1,68 @@
+<?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.
+     * @type string
+     */
+    protected $attr;
+
+    /**
+     * Lookup array of attribute values to CSS.
+     * @type array
+     */
+    protected $enumToCSS = array();
+
+    /**
+     * Case sensitivity of the matching.
+     * @type bool
+     * @warning Currently can only be guaranteed to work with ASCII
+     *          values.
+     */
+    protected $caseSensitive = false;
+
+    /**
+     * @param string $attr Attribute name to transform from
+     * @param array $enum_to_css Lookup array of attribute values to CSS
+     * @param bool $case_sensitive 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;
+    }
+
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php
new file mode 100644 (file)
index 0000000..7df6cb3
--- /dev/null
@@ -0,0 +1,48 @@
+<?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
+{
+
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php
new file mode 100644 (file)
index 0000000..350b335
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * Pre-transform that changes deprecated hspace and vspace attributes to CSS
+ */
+class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform
+{
+    /**
+     * @type string
+     */
+    protected $attr;
+
+    /**
+     * @type array
+     */
+    protected $css = array(
+        'hspace' => array('left', 'right'),
+        'vspace' => array('top', 'bottom')
+    );
+
+    /**
+     * @param string $attr
+     */
+    public function __construct($attr)
+    {
+        $this->attr = $attr;
+        if (!isset($this->css[$attr])) {
+            trigger_error(htmlspecialchars($attr) . ' is not valid space attribute');
+        }
+    }
+
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php
new file mode 100644 (file)
index 0000000..3ab47ed
--- /dev/null
@@ -0,0 +1,56 @@
+<?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
+{
+    /**
+     * @type HTMLPurifier_AttrDef_HTML_Pixels
+     */
+    protected $pixels;
+
+    public function __construct()
+    {
+        $this->pixels = new HTMLPurifier_AttrDef_HTML_Pixels();
+    }
+
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php
new file mode 100644 (file)
index 0000000..5b0aff0
--- /dev/null
@@ -0,0 +1,31 @@
+<?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
+{
+
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Length.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Length.php
new file mode 100644 (file)
index 0000000..853f335
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * Class for handling width/height length attribute transformations to CSS
+ */
+class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform
+{
+
+    /**
+     * @type string
+     */
+    protected $name;
+
+    /**
+     * @type string
+     */
+    protected $cssName;
+
+    public function __construct($name, $css_name = null)
+    {
+        $this->name = $name;
+        $this->cssName = $css_name ? $css_name : $name;
+    }
+
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php
new file mode 100644 (file)
index 0000000..63cce68
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * Pre-transform that changes deprecated name attribute to ID if necessary
+ */
+class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform
+{
+
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php
new file mode 100644 (file)
index 0000000..36079b7
--- /dev/null
@@ -0,0 +1,41 @@
+<?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();
+    }
+
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php
new file mode 100644 (file)
index 0000000..1057ebe
--- /dev/null
@@ -0,0 +1,52 @@
+<?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
+{
+    /**
+     * @type HTMLPurifier_URIParser
+     */
+    private $parser;
+
+    public function __construct()
+    {
+        $this->parser = new HTMLPurifier_URIParser();
+    }
+
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php
new file mode 100644 (file)
index 0000000..231c81a
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+class HTMLPurifier_AttrTransform_SafeEmbed extends HTMLPurifier_AttrTransform
+{
+    /**
+     * @type string
+     */
+    public $name = "SafeEmbed";
+
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeObject.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeObject.php
new file mode 100644 (file)
index 0000000..d1f3a4d
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Writes default type for all objects. Currently only supports flash.
+ */
+class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform
+{
+    /**
+     * @type string
+     */
+    public $name = "SafeObject";
+
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    public 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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeParam.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeParam.php
new file mode 100644 (file)
index 0000000..1143b4b
--- /dev/null
@@ -0,0 +1,79 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $name = "SafeParam";
+
+    /**
+     * @type HTMLPurifier_AttrDef_URI
+     */
+    private $uri;
+
+    public function __construct()
+    {
+        $this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded
+        $this->wmode = new HTMLPurifier_AttrDef_Enum(array('window', 'opaque', 'transparent'));
+    }
+
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php
new file mode 100644 (file)
index 0000000..b7057bb
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Implements required attribute stipulation for <script>
+ */
+class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform
+{
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php
new file mode 100644 (file)
index 0000000..dd63ea8
--- /dev/null
@@ -0,0 +1,45 @@
+<?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
+{
+    /**
+     * @type HTMLPurifier_URIParser
+     */
+    private $parser;
+
+    public function __construct()
+    {
+        $this->parser = new HTMLPurifier_URIParser();
+    }
+
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php
new file mode 100644 (file)
index 0000000..6a9f33a
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * Sets height/width defaults for <textarea>
+ */
+class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform
+{
+    /**
+     * @param array $attr
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php
new file mode 100644 (file)
index 0000000..3b70520
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * Provides lookup array of attribute types to HTMLPurifier_AttrDef objects
+ */
+class HTMLPurifier_AttrTypes
+{
+    /**
+     * Lookup array of attribute string identifiers to concrete implementations.
+     * @type HTMLPurifier_AttrDef[]
+     */
+    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 string $type String type name
+     * @return HTMLPurifier_AttrDef 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 string $type String type name
+     * @param HTMLPurifier_AttrDef $impl Object AttrDef for type
+     */
+    public function set($type, $impl)
+    {
+        $this->info[$type] = $impl;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php
new file mode 100644 (file)
index 0000000..f97dc93
--- /dev/null
@@ -0,0 +1,178 @@
+<?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, mutating it as necessary.
+     * that has valid tokens
+     * @param HTMLPurifier_Token $token Token to validate.
+     * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
+     * @param HTMLPurifier_Context $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;
+        }
+
+        // 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/library/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php
new file mode 100644 (file)
index 0000000..707122b
--- /dev/null
@@ -0,0 +1,124 @@
+<?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@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 string $class Class to load
+     * @return bool
+     */
+    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.
+     * @param string $class Class path to get
+     * @return string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php
new file mode 100644 (file)
index 0000000..07cc941
--- /dev/null
@@ -0,0 +1,474 @@
+<?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.
+     * @type HTMLPurifier_AttrDef[]
+     */
+    public $info = array();
+
+    /**
+     * Constructs the info array.  The meat of this class.
+     * @param HTMLPurifier_Config $config
+     */
+    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);
+    }
+
+    /**
+     * @param HTMLPurifier_Config $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();
+
+        // vendor specific prefixes of opacity
+        $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'));
+
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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'));
+        $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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.
+     * @param HTMLPurifier_Config $config
+     * @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/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php
new file mode 100644 (file)
index 0000000..8eb17b8
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * Defines allowed child nodes and validates nodes against it.
+ */
+abstract class HTMLPurifier_ChildDef
+{
+    /**
+     * Type of child definition, usually right-most part of class name lowercase.
+     * Used occasionally in terms of context.
+     * @type string
+     */
+    public $type;
+
+    /**
+     * 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.
+     * @type bool
+     */
+    public $allow_empty;
+
+    /**
+     * Lookup array of all elements that this definition could possibly allow.
+     * @type array
+     */
+    public $elements = array();
+
+    /**
+     * Get lookup of tag names that should not close this element automatically.
+     * All other elements will do so.
+     * @param HTMLPurifier_Config $config HTMLPurifier_Config object
+     * @return array
+     */
+    public function getAllowedElements($config)
+    {
+        return $this->elements;
+    }
+
+    /**
+     * Validates nodes according to definition and returns modification.
+     *
+     * @param HTMLPurifier_Node[] $children Array of HTMLPurifier_Node
+     * @param HTMLPurifier_Config $config HTMLPurifier_Config object
+     * @param HTMLPurifier_Context $context HTMLPurifier_Context object
+     * @return bool|array true to leave nodes as is, false to remove parent node, array of replacement children
+     */
+    abstract public function validateChildren($children, $config, $context);
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php
new file mode 100644 (file)
index 0000000..7439be2
--- /dev/null
@@ -0,0 +1,67 @@
+<?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.
+     * @type HTMLPurifier_ChildDef_Optional
+     */
+    public $inline;
+
+    /**
+     * Instance of the definition object to use when block.
+     * @type HTMLPurifier_ChildDef_Optional
+     */
+    public $block;
+
+    /**
+     * @type string
+     */
+    public $type = 'chameleon';
+
+    /**
+     * @param array $inline List of elements to allow when inline.
+     * @param array $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;
+    }
+
+    /**
+     * @param HTMLPurifier_Node[] $children
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    public function validateChildren($children, $config, $context)
+    {
+        if ($context->get('IsInline') === false) {
+            return $this->block->validateChildren(
+                $children,
+                $config,
+                $context
+            );
+        } else {
+            return $this->inline->validateChildren(
+                $children,
+                $config,
+                $context
+            );
+        }
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php
new file mode 100644 (file)
index 0000000..128132e
--- /dev/null
@@ -0,0 +1,102 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $type = 'custom';
+
+    /**
+     * @type bool
+     */
+    public $allow_empty = false;
+
+    /**
+     * Allowed child pattern as defined by the DTD.
+     * @type string
+     */
+    public $dtd_regex;
+
+    /**
+     * PCRE regex derived from $dtd_regex.
+     * @type string
+     */
+    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;
+    }
+
+    /**
+     * @param HTMLPurifier_Node[] $children
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    public function validateChildren($children, $config, $context)
+    {
+        $list_of_children = '';
+        $nesting = 0; // depth into the nest
+        foreach ($children as $node) {
+            if (!empty($node->is_whitespace)) {
+                continue;
+            }
+            $list_of_children .= $node->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/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php
new file mode 100644 (file)
index 0000000..a8a6cbd
--- /dev/null
@@ -0,0 +1,38 @@
+<?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
+{
+    /**
+     * @type bool
+     */
+    public $allow_empty = true;
+
+    /**
+     * @type string
+     */
+    public $type = 'empty';
+
+    public function __construct()
+    {
+    }
+
+    /**
+     * @param HTMLPurifier_Node[] $children
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    public function validateChildren($children, $config, $context)
+    {
+        return array();
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php
new file mode 100644 (file)
index 0000000..891b9f6
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * Definition for list containers ul and ol.
+ *
+ * What does this do?  The big thing is to handle ol/ul at the top
+ * level of list nodes, which should be handled specially by /folding/
+ * them into the previous list node.  We generally shouldn't ever
+ * see other disallowed elements, because the autoclose behavior
+ * in MakeWellFormed handles it.
+ */
+class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
+{
+    /**
+     * @type string
+     */
+    public $type = 'list';
+    /**
+     * @type array
+     */
+    // 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);
+
+    /**
+     * @param array $children
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    public function validateChildren($children, $config, $context)
+    {
+        // Flag for subclasses
+        $this->whitespace = false;
+
+        // if there are no tokens, delete parent node
+        if (empty($children)) {
+            return false;
+        }
+
+        // the new set of children
+        $result = array();
+
+        // a little sanity check to make sure it's not ALL whitespace
+        $all_whitespace = true;
+
+        $current_li = false;
+
+        foreach ($children as $node) {
+            if (!empty($node->is_whitespace)) {
+                $result[] = $node;
+                continue;
+            }
+            $all_whitespace = false; // phew, we're not talking about whitespace
+
+            if ($node->name === 'li') {
+                // good
+                $current_li = $node;
+                $result[] = $node;
+            } else {
+                // we want to tuck this into the previous li
+                // Invariant: we expect the node to be ol/ul
+                // ToDo: Make this more robust in the case of not ol/ul
+                // by distinguishing between existing li and li created
+                // to handle non-list elements; non-list elements should
+                // not be appended to an existing li; only li created
+                // for non-list. This distinction is not currently made.
+                if ($current_li === false) {
+                    $current_li = new HTMLPurifier_Node_Element('li');
+                    $result[] = $current_li;
+                }
+                $current_li->children[] = $node;
+                $current_li->empty = false; // XXX fascinating! Check for this error elsewhere ToDo
+            }
+        }
+        if (empty($result)) {
+            return false;
+        }
+        if ($all_whitespace) {
+            return false;
+        }
+        return $result;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php
new file mode 100644 (file)
index 0000000..b946806
--- /dev/null
@@ -0,0 +1,45 @@
+<?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
+{
+    /**
+     * @type bool
+     */
+    public $allow_empty = true;
+
+    /**
+     * @type string
+     */
+    public $type = 'optional';
+
+    /**
+     * @param array $children
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    public function validateChildren($children, $config, $context)
+    {
+        $result = parent::validateChildren($children, $config, $context);
+        // we assume that $children is not modified
+        if ($result === false) {
+            if (empty($children)) {
+                return true;
+            } elseif ($this->whitespace) {
+                return $children;
+            } else {
+                return array();
+            }
+        }
+        return $result;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php
new file mode 100644 (file)
index 0000000..0d1c8f5
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+
+/**
+ * Definition that allows a set of elements, but disallows empty children.
+ */
+class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
+{
+    /**
+     * Lookup table of allowed elements.
+     * @type array
+     */
+    public $elements = array();
+
+    /**
+     * Whether or not the last passed node was all whitespace.
+     * @type bool
+     */
+    protected $whitespace = false;
+
+    /**
+     * @param array|string $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;
+    }
+
+    /**
+     * @type bool
+     */
+    public $allow_empty = false;
+
+    /**
+     * @type string
+     */
+    public $type = 'required';
+
+    /**
+     * @param array $children
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    public function validateChildren($children, $config, $context)
+    {
+        // Flag for subclasses
+        $this->whitespace = false;
+
+        // if there are no tokens, delete parent node
+        if (empty($children)) {
+            return false;
+        }
+
+        // the new set of children
+        $result = array();
+
+        // 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;
+
+        $stack = array_reverse($children);
+        while (!empty($stack)) {
+            $node = array_pop($stack);
+            if (!empty($node->is_whitespace)) {
+                $result[] = $node;
+                continue;
+            }
+            $all_whitespace = false; // phew, we're not talking about whitespace
+
+            if (!isset($this->elements[$node->name])) {
+                // special case text
+                // XXX One of these ought to be redundant or something
+                if ($pcdata_allowed && $node instanceof HTMLPurifier_Node_Text) {
+                    $result[] = $node;
+                    continue;
+                }
+                // spill the child contents in
+                // ToDo: Make configurable
+                if ($node instanceof HTMLPurifier_Node_Element) {
+                    for ($i = count($node->children) - 1; $i >= 0; $i--) {
+                        $stack[] = $node->children[$i];
+                    }
+                    continue;
+                }
+                continue;
+            }
+            $result[] = $node;
+        }
+        if (empty($result)) {
+            return false;
+        }
+        if ($all_whitespace) {
+            $this->whitespace = true;
+            return false;
+        }
+        return $result;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php
new file mode 100644 (file)
index 0000000..3270a46
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * Takes the contents of blockquote when in strict and reformats for validation.
+ */
+class HTMLPurifier_ChildDef_StrictBlockquote extends HTMLPurifier_ChildDef_Required
+{
+    /**
+     * @type array
+     */
+    protected $real_elements;
+
+    /**
+     * @type array
+     */
+    protected $fake_elements;
+
+    /**
+     * @type bool
+     */
+    public $allow_empty = true;
+
+    /**
+     * @type string
+     */
+    public $type = 'strictblockquote';
+
+    /**
+     * @type bool
+     */
+    protected $init = false;
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return array
+     * @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;
+    }
+
+    /**
+     * @param array $children
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    public function validateChildren($children, $config, $context)
+    {
+        $this->init($config);
+
+        // trick the parent class into thinking it allows more
+        $this->elements = $this->fake_elements;
+        $result = parent::validateChildren($children, $config, $context);
+        $this->elements = $this->real_elements;
+
+        if ($result === false) {
+            return array();
+        }
+        if ($result === true) {
+            $result = $children;
+        }
+
+        $def = $config->getHTMLDefinition();
+        $block_wrap_name = $def->info_block_wrapper;
+        $block_wrap = false;
+        $ret = array();
+
+        foreach ($result as $node) {
+            if ($block_wrap === false) {
+                if (($node instanceof HTMLPurifier_Node_Text && !$node->is_whitespace) ||
+                    ($node instanceof HTMLPurifier_Node_Element && !isset($this->elements[$node->name]))) {
+                        $block_wrap = new HTMLPurifier_Node_Element($def->info_block_wrapper);
+                        $ret[] = $block_wrap;
+                }
+            } else {
+                if ($node instanceof HTMLPurifier_Node_Element && isset($this->elements[$node->name])) {
+                    $block_wrap = false;
+
+                }
+            }
+            if ($block_wrap) {
+                $block_wrap->children[] = $node;
+            } else {
+                $ret[] = $node;
+            }
+        }
+        return $ret;
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php
new file mode 100644 (file)
index 0000000..3e4a0f2
--- /dev/null
@@ -0,0 +1,224 @@
+<?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
+{
+    /**
+     * @type bool
+     */
+    public $allow_empty = false;
+
+    /**
+     * @type string
+     */
+    public $type = 'table';
+
+    /**
+     * @type array
+     */
+    public $elements = array(
+        'tr' => true,
+        'tbody' => true,
+        'thead' => true,
+        'tfoot' => true,
+        'caption' => true,
+        'colgroup' => true,
+        'col' => true
+    );
+
+    public function __construct()
+    {
+    }
+
+    /**
+     * @param array $children
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array
+     */
+    public function validateChildren($children, $config, $context)
+    {
+        if (empty($children)) {
+            return false;
+        }
+
+        // only one of these elements is allowed in a table
+        $caption = false;
+        $thead = false;
+        $tfoot = false;
+
+        // whitespace
+        $initial_ws = array();
+        $after_caption_ws = array();
+        $after_thead_ws = array();
+        $after_tfoot_ws = array();
+
+        // as many of these as you want
+        $cols = array();
+        $content = array();
+
+        $tbody_mode = false; // if true, then we need to wrap any stray
+                             // <tr>s with a <tbody>.
+
+        $ws_accum =& $initial_ws;
+
+        foreach ($children as $node) {
+            if ($node instanceof HTMLPurifier_Node_Comment) {
+                $ws_accum[] = $node;
+                continue;
+            }
+            switch ($node->name) {
+            case 'tbody':
+                $tbody_mode = true;
+                // fall through
+            case 'tr':
+                $content[] = $node;
+                $ws_accum =& $content;
+                break;
+            case 'caption':
+                // there can only be one caption!
+                if ($caption !== false)  break;
+                $caption = $node;
+                $ws_accum =& $after_caption_ws;
+                break;
+            case 'thead':
+                $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.
+                if ($thead === false) {
+                    $thead = $node;
+                    $ws_accum =& $after_thead_ws;
+                } 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.
+                    $node->name = 'tbody';
+                    $content[] = $node;
+                    $ws_accum =& $content;
+                }
+                break;
+            case 'tfoot':
+                // see above for some aveats
+                $tbody_mode = true;
+                if ($tfoot === false) {
+                    $tfoot = $node;
+                    $ws_accum =& $after_tfoot_ws;
+                } else {
+                    $node->name = 'tbody';
+                    $content[] = $node;
+                    $ws_accum =& $content;
+                }
+                break;
+            case 'colgroup':
+            case 'col':
+                $cols[] = $node;
+                $ws_accum =& $cols;
+                break;
+            case '#PCDATA':
+                // How is whitespace handled? We treat is as sticky to
+                // the *end* of the previous element. So all of the
+                // nonsense we have worked on is to keep things
+                // together.
+                if (!empty($node->is_whitespace)) {
+                    $ws_accum[] = $node;
+                }
+                break;
+            }
+        }
+
+        if (empty($content)) {
+            return false;
+        }
+
+        $ret = $initial_ws;
+        if ($caption !== false) {
+            $ret[] = $caption;
+            $ret = array_merge($ret, $after_caption_ws);
+        }
+        if ($cols !== false) {
+            $ret = array_merge($ret, $cols);
+        }
+        if ($thead !== false) {
+            $ret[] = $thead;
+            $ret = array_merge($ret, $after_thead_ws);
+        }
+        if ($tfoot !== false) {
+            $ret[] = $tfoot;
+            $ret = array_merge($ret, $after_tfoot_ws);
+        }
+
+        if ($tbody_mode) {
+            // we have to shuffle tr into tbody
+            $current_tr_tbody = null;
+
+            foreach($content as $node) {
+                switch ($node->name) {
+                case 'tbody':
+                    $current_tr_tbody = null;
+                    $ret[] = $node;
+                    break;
+                case 'tr':
+                    if ($current_tr_tbody === null) {
+                        $current_tr_tbody = new HTMLPurifier_Node_Element('tbody');
+                        $ret[] = $current_tr_tbody;
+                    }
+                    $current_tr_tbody->children[] = $node;
+                    break;
+                case '#PCDATA':
+                    assert($node->is_whitespace);
+                    if ($current_tr_tbody === null) {
+                        $ret[] = $node;
+                    } else {
+                        $current_tr_tbody->children[] = $node;
+                    }
+                    break;
+                }
+            }
+        } else {
+            $ret = array_merge($ret, $content);
+        }
+
+        return $ret;
+
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Config.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Config.php
new file mode 100644 (file)
index 0000000..2b2db0c
--- /dev/null
@@ -0,0 +1,920 @@
+<?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
+     * @type string
+     */
+    public $version = '4.7.0';
+
+    /**
+     * Whether or not to automatically finalize
+     * the object if a read operation is done.
+     * @type bool
+     */
+    public $autoFinalize = true;
+
+    // protected member variables
+
+    /**
+     * Namespace indexed array of serials for specific namespaces.
+     * @see getSerial() for more info.
+     * @type string[]
+     */
+    protected $serials = array();
+
+    /**
+     * Serial for entire configuration object.
+     * @type string
+     */
+    protected $serial;
+
+    /**
+     * Parser for variables.
+     * @type HTMLPurifier_VarParser_Flexible
+     */
+    protected $parser = null;
+
+    /**
+     * Reference HTMLPurifier_ConfigSchema for value checking.
+     * @type HTMLPurifier_ConfigSchema
+     * @note This is public for introspective purposes. Please don't
+     *       abuse!
+     */
+    public $def;
+
+    /**
+     * Indexed array of definitions.
+     * @type HTMLPurifier_Definition[]
+     */
+    protected $definitions;
+
+    /**
+     * Whether or not config is finalized.
+     * @type bool
+     */
+    protected $finalized = false;
+
+    /**
+     * Property list containing configuration directives.
+     * @type array
+     */
+    protected $plist;
+
+    /**
+     * Whether or not a set is taking place due to an alias lookup.
+     * @type bool
+     */
+    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.
+     * @type bool
+     */
+    public $chatty = true;
+
+    /**
+     * Current lock; only gets to this namespace are allowed.
+     * @type string
+     */
+    private $lock;
+
+    /**
+     * Constructor
+     * @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines
+     * what directives are allowed.
+     * @param HTMLPurifier_PropertyList $parent
+     */
+    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 Schema object
+     * @return HTMLPurifier_Config Configured 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 HTMLPurifier_Config default object.
+     */
+    public static function createDefault()
+    {
+        $definition = HTMLPurifier_ConfigSchema::instance();
+        $config = new HTMLPurifier_Config($definition);
+        return $config;
+    }
+
+    /**
+     * Retrieves a value from the configuration.
+     *
+     * @param string $key String key
+     * @param mixed $a
+     *
+     * @return mixed
+     */
+    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);
+    }
+
+    /**
+     * Retrieves an array of directives to values from a given namespace
+     *
+     * @param string $namespace String namespace
+     *
+     * @return array
+     */
+    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
+     *
+     * @param string $namespace Namespace to get serial for
+     *
+     * @return string
+     * @note Revision is handled specially and is removed from the batch
+     *       before processing!
+     */
+    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
+     *
+     * @return string
+     */
+    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 string $key key
+     * @param mixed $value value
+     * @param mixed $a
+     */
+    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
+     *
+     * @param array $lookup
+     *
+     * @return string
+     */
+    private function _listify($lookup)
+    {
+        $list = array();
+        foreach ($lookup as $name => $b) {
+            $list[] = $name;
+        }
+        return implode(', ', $list);
+    }
+
+    /**
+     * Retrieves object reference to the HTML definition.
+     *
+     * @param bool $raw Return a copy that has not been setup yet. Must be
+     *             called before it's been setup, otherwise won't work.
+     * @param bool $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.
+     *
+     * @return HTMLPurifier_HTMLDefinition
+     */
+    public function getHTMLDefinition($raw = false, $optimized = false)
+    {
+        return $this->getDefinition('HTML', $raw, $optimized);
+    }
+
+    /**
+     * Retrieves object reference to the CSS definition
+     *
+     * @param bool $raw Return a copy that has not been setup yet. Must be
+     *             called before it's been setup, otherwise won't work.
+     * @param bool $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.
+     *
+     * @return HTMLPurifier_CSSDefinition
+     */
+    public function getCSSDefinition($raw = false, $optimized = false)
+    {
+        return $this->getDefinition('CSS', $raw, $optimized);
+    }
+
+    /**
+     * Retrieves object reference to the URI definition
+     *
+     * @param bool $raw Return a copy that has not been setup yet. Must be
+     *             called before it's been setup, otherwise won't work.
+     * @param bool $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.
+     *
+     * @return HTMLPurifier_URIDefinition
+     */
+    public function getURIDefinition($raw = false, $optimized = false)
+    {
+        return $this->getDefinition('URI', $raw, $optimized);
+    }
+
+    /**
+     * Retrieves a definition
+     *
+     * @param string $type Type of definition: HTML, CSS, etc
+     * @param bool $raw Whether or not definition should be returned raw
+     * @param bool $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."
+     *
+     * @throws HTMLPurifier_Exception
+     * @return HTMLPurifier_Definition
+     */
+    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://htmlpurifier.org/docs/enduser-customize.html#optimized">' .
+                            'Customize</a> for more details',
+                            E_USER_WARNING
+                        );
+                    } 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!");
+    }
+
+    /**
+     * Initialise definition
+     *
+     * @param string $type What type of definition to create
+     *
+     * @return HTMLPurifier_CSSDefinition|HTMLPurifier_HTMLDefinition|HTMLPurifier_URIDefinition
+     * @throws HTMLPurifier_Exception
+     */
+    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);
+    }
+
+    /**
+     * @return HTMLPurifier_HTMLDefinition
+     */
+    public function maybeGetRawHTMLDefinition()
+    {
+        return $this->getDefinition('HTML', true, true);
+    }
+    
+    /**
+     * @return HTMLPurifier_CSSDefinition
+     */
+    public function maybeGetRawCSSDefinition()
+    {
+        return $this->getDefinition('CSS', true, true);
+    }
+    
+    /**
+     * @return HTMLPurifier_URIDefinition
+     */
+    public function maybeGetRawURIDefinition()
+    {
+        return $this->getDefinition('URI', true, true);
+    }
+
+    /**
+     * Loads configuration values from an array with the following structure:
+     * Namespace.Directive => Value
+     *
+     * @param array $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 => $value2) {
+                    $this->set($namespace .'.'. $directive, $value2);
+                }
+            }
+        }
+    }
+
+    /**
+     * 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 array $allowed List of allowed namespaces/directives
+     * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
+     *
+     * @return array
+     */
+    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 $array $_GET or $_POST array to import
+     * @param string|bool $index Index/name that the config variables are in
+     * @param array|bool $allowed List of allowed namespaces/directives
+     * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
+     * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
+     *
+     * @return mixed
+     */
+    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.
+     *
+     * @param array $array $_GET or $_POST array to import
+     * @param string|bool $index Index/name that the config variables are in
+     * @param array|bool $allowed List of allowed namespaces/directives
+     * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
+     */
+    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
+     *
+     * @param array $array $_GET or $_POST array to import
+     * @param string|bool $index Index/name that the config variables are in
+     * @param array|bool $allowed List of allowed namespaces/directives
+     * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
+     * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
+     *
+     * @return array
+     */
+    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 string $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 string|bool $error String error message, or false for no error
+     *
+     * @return bool
+     */
+    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.
+     *
+     * @param string $msg An error message
+     * @param int $no An error number
+     */
+    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.
+     *
+     * @return string
+     */
+    public function serialize()
+    {
+        $this->getDefinition('HTML');
+        $this->getDefinition('CSS');
+        $this->getDefinition('URI');
+        return serialize($this);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php
new file mode 100644 (file)
index 0000000..bfbb0f9
--- /dev/null
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * Configuration definition, defines directives and their defaults.
+ */
+class HTMLPurifier_ConfigSchema
+{
+    /**
+     * Defaults of the directives and namespaces.
+     * @type array
+     * @note This shares the exact same structure as HTMLPurifier_Config::$conf
+     */
+    public $defaults = array();
+
+    /**
+     * The default property list. Do not edit this property list.
+     * @type array
+     */
+    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.
+     * @type array
+     */
+    public $info = array();
+
+    /**
+     * Application-wide singleton
+     * @type HTMLPurifier_ConfigSchema
+     */
+    protected static $singleton;
+
+    public function __construct()
+    {
+        $this->defaultPlist = new HTMLPurifier_PropertyList();
+    }
+
+    /**
+     * Unserializes the default ConfigSchema.
+     * @return HTMLPurifier_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.
+     * @param HTMLPurifier_ConfigSchema $prototype
+     * @return HTMLPurifier_ConfigSchema
+     */
+    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 string $key Name of directive
+     * @param mixed $default Default value of directive
+     * @param string $type Allowed type of the directive. See
+     *      HTMLPurifier_DirectiveDef::$type for allowed values
+     * @param bool $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 string $key Name of Directive
+     * @param array $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 string $key Name of directive
+     * @param array $allowed Lookup array of allowed values
+     */
+    public function addAllowedValues($key, $allowed)
+    {
+        $this->info[$key]->allowed = $allowed;
+    }
+
+    /**
+     * Defines a directive alias for backwards compatibility
+     * @param string $key Directive that will be aliased
+     * @param string $new_key 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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php
new file mode 100644 (file)
index 0000000..d5906cd
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Converts HTMLPurifier_ConfigSchema_Interchange to our runtime
+ * representation used to perform checks on user configuration.
+ */
+class HTMLPurifier_ConfigSchema_Builder_ConfigSchema
+{
+
+    /**
+     * @param HTMLPurifier_ConfigSchema_Interchange $interchange
+     * @return HTMLPurifier_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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php
new file mode 100644 (file)
index 0000000..5fa56f7
--- /dev/null
@@ -0,0 +1,144 @@
+<?php
+
+/**
+ * Converts HTMLPurifier_ConfigSchema_Interchange to an XML format,
+ * which can be further processed to generate documentation.
+ */
+class HTMLPurifier_ConfigSchema_Builder_Xml extends XMLWriter
+{
+
+    /**
+     * @type HTMLPurifier_ConfigSchema_Interchange
+     */
+    protected $interchange;
+
+    /**
+     * @type string
+     */
+    private $namespace;
+
+    /**
+     * @param string $html
+     */
+    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
+    }
+
+    /**
+     * @param mixed $var
+     * @return string
+     */
+    protected function export($var)
+    {
+        if ($var === array()) {
+            return 'array()';
+        }
+        return var_export($var, true);
+    }
+
+    /**
+     * @param HTMLPurifier_ConfigSchema_Interchange $interchange
+     */
+    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();
+    }
+
+    /**
+     * @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php
new file mode 100644 (file)
index 0000000..2671516
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange.php
new file mode 100644 (file)
index 0000000..0e08ae8
--- /dev/null
@@ -0,0 +1,47 @@
+<?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.
+     * @type string
+     */
+    public $name;
+
+    /**
+     * Array of Directive ID => array(directive info)
+     * @type HTMLPurifier_ConfigSchema_Interchange_Directive[]
+     */
+    public $directives = array();
+
+    /**
+     * Adds a directive array to $directives
+     * @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive
+     * @throws HTMLPurifier_ConfigSchema_Exception
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php
new file mode 100644 (file)
index 0000000..127a39a
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * Interchange component class describing configuration directives.
+ */
+class HTMLPurifier_ConfigSchema_Interchange_Directive
+{
+
+    /**
+     * ID of directive.
+     * @type HTMLPurifier_ConfigSchema_Interchange_Id
+     */
+    public $id;
+
+    /**
+     * Type, e.g. 'integer' or 'istring'.
+     * @type string
+     */
+    public $type;
+
+    /**
+     * Default value, e.g. 3 or 'DefaultVal'.
+     * @type mixed
+     */
+    public $default;
+
+    /**
+     * HTML description.
+     * @type string
+     */
+    public $description;
+
+    /**
+     * Whether or not null is allowed as a value.
+     * @type bool
+     */
+    public $typeAllowsNull = false;
+
+    /**
+     * Lookup table of allowed scalar values.
+     * e.g. array('allowed' => true).
+     * Null if all values are allowed.
+     * @type array
+     */
+    public $allowed;
+
+    /**
+     * List of aliases for the directive.
+     * e.g. array(new HTMLPurifier_ConfigSchema_Interchange_Id('Ns', 'Dir'))).
+     * @type HTMLPurifier_ConfigSchema_Interchange_Id[]
+     */
+    public $aliases = array();
+
+    /**
+     * Hash of value aliases, e.g. array('alt' => 'real'). Null if value
+     * aliasing is disabled (necessary for non-scalar types).
+     * @type array
+     */
+    public $valueAliases;
+
+    /**
+     * Version of HTML Purifier the directive was introduced, e.g. '1.3.1'.
+     * Null if the directive has always existed.
+     * @type string
+     */
+    public $version;
+
+    /**
+     * ID of directive that supercedes this old directive.
+     * Null if not deprecated.
+     * @type HTMLPurifier_ConfigSchema_Interchange_Id
+     */
+    public $deprecatedUse;
+
+    /**
+     * Version of HTML Purifier this directive was deprecated. Null if not
+     * deprecated.
+     * @type string
+     */
+    public $deprecatedVersion;
+
+    /**
+     * List of external projects this directive depends on, e.g. array('CSSTidy').
+     * @type array
+     */
+    public $external = array();
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php
new file mode 100644 (file)
index 0000000..126f09d
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Represents a directive ID in the interchange format.
+ */
+class HTMLPurifier_ConfigSchema_Interchange_Id
+{
+
+    /**
+     * @type string
+     */
+    public $key;
+
+    /**
+     * @param string $key
+     */
+    public function __construct($key)
+    {
+        $this->key = $key;
+    }
+
+    /**
+     * @return string
+     * @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;
+    }
+
+    /**
+     * @return string
+     */
+    public function getRootNamespace()
+    {
+        return substr($this->key, 0, strpos($this->key, "."));
+    }
+
+    /**
+     * @return string
+     */
+    public function getDirective()
+    {
+        return substr($this->key, strpos($this->key, ".") + 1);
+    }
+
+    /**
+     * @param string $id
+     * @return HTMLPurifier_ConfigSchema_Interchange_Id
+     */
+    public static function make($id)
+    {
+        return new HTMLPurifier_ConfigSchema_Interchange_Id($id);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php
new file mode 100644 (file)
index 0000000..655e6dd
--- /dev/null
@@ -0,0 +1,226 @@
+<?php
+
+class HTMLPurifier_ConfigSchema_InterchangeBuilder
+{
+
+    /**
+     * Used for processing DEFAULT, nothing else.
+     * @type HTMLPurifier_VarParser
+     */
+    protected $varParser;
+
+    /**
+     * @param HTMLPurifier_VarParser $varParser
+     */
+    public function __construct($varParser = null)
+    {
+        $this->varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native();
+    }
+
+    /**
+     * @param string $dir
+     * @return HTMLPurifier_ConfigSchema_Interchange
+     */
+    public static function buildFromDirectory($dir = null)
+    {
+        $builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder();
+        $interchange = new HTMLPurifier_ConfigSchema_Interchange();
+        return $builder->buildDir($interchange, $dir);
+    }
+
+    /**
+     * @param HTMLPurifier_ConfigSchema_Interchange $interchange
+     * @param string $dir
+     * @return HTMLPurifier_ConfigSchema_Interchange
+     */
+    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;
+    }
+
+    /**
+     * @param HTMLPurifier_ConfigSchema_Interchange $interchange
+     * @param string $file
+     */
+    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 HTMLPurifier_ConfigSchema_Interchange $interchange HTMLPurifier_ConfigSchema_Interchange object to build
+     * @param HTMLPurifier_StringHash $hash source data
+     * @throws HTMLPurifier_ConfigSchema_Exception
+     */
+    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);
+    }
+
+    /**
+     * @param HTMLPurifier_ConfigSchema_Interchange $interchange
+     * @param HTMLPurifier_StringHash $hash
+     * @throws HTMLPurifier_ConfigSchema_Exception
+     */
+    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
+     * @param string $contents
+     */
+    protected function evalArray($contents)
+    {
+        return eval('return array(' . $contents . ');');
+    }
+
+    /**
+     * Converts an array list into a lookup array.
+     * @param array $array
+     * @return 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.
+     * @param string $id
+     * @return HTMLPurifier_ConfigSchema_Interchange_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 HTMLPurifier_StringHash $hash Hash 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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php
new file mode 100644 (file)
index 0000000..fb31277
--- /dev/null
@@ -0,0 +1,248 @@
+<?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
+{
+
+    /**
+     * @type HTMLPurifier_ConfigSchema_Interchange
+     */
+    protected $interchange;
+
+    /**
+     * @type array
+     */
+    protected $aliases;
+
+    /**
+     * Context-stack to provide easy to read error messages.
+     * @type array
+     */
+    protected $context = array();
+
+    /**
+     * to test default's type.
+     * @type HTMLPurifier_VarParser
+     */
+    protected $parser;
+
+    public function __construct()
+    {
+        $this->parser = new HTMLPurifier_VarParser();
+    }
+
+    /**
+     * Validates a fully-formed interchange object.
+     * @param HTMLPurifier_ConfigSchema_Interchange $interchange
+     * @return bool
+     */
+    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.
+     * @param HTMLPurifier_ConfigSchema_Interchange_Id $id
+     */
+    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.
+     * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
+     */
+    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.
+     * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
+     */
+    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.
+     * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
+     */
+    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.
+     * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
+     */
+    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.
+     * @param $obj
+     * @param $member
+     * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+     */
+    protected function with($obj, $member)
+    {
+        return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->getFormattedContext(), $obj, $member);
+    }
+
+    /**
+     * Emits an error, providing helpful context.
+     * @throws HTMLPurifier_ConfigSchema_Exception
+     */
+    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.
+     * @return string
+     */
+    protected function getFormattedContext()
+    {
+        return implode(' in ', array_reverse($this->context));
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php
new file mode 100644 (file)
index 0000000..c9aa364
--- /dev/null
@@ -0,0 +1,130 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    protected $context;
+
+    /**
+     * @type object
+     */
+    protected $obj;
+
+    /**
+     * @type string
+     */
+    protected $member;
+
+    /**
+     * @type mixed
+     */
+    protected $contents;
+
+    public function __construct($context, $obj, $member)
+    {
+        $this->context = $context;
+        $this->obj = $obj;
+        $this->member = $member;
+        $this->contents =& $obj->$member;
+    }
+
+    /**
+     * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+     */
+    public function assertIsString()
+    {
+        if (!is_string($this->contents)) {
+            $this->error('must be a string');
+        }
+        return $this;
+    }
+
+    /**
+     * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+     */
+    public function assertIsBool()
+    {
+        if (!is_bool($this->contents)) {
+            $this->error('must be a boolean');
+        }
+        return $this;
+    }
+
+    /**
+     * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+     */
+    public function assertIsArray()
+    {
+        if (!is_array($this->contents)) {
+            $this->error('must be an array');
+        }
+        return $this;
+    }
+
+    /**
+     * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+     */
+    public function assertNotNull()
+    {
+        if ($this->contents === null) {
+            $this->error('must not be null');
+        }
+        return $this;
+    }
+
+    /**
+     * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+     */
+    public function assertAlnum()
+    {
+        $this->assertIsString();
+        if (!ctype_alnum($this->contents)) {
+            $this->error('must be alphanumeric');
+        }
+        return $this;
+    }
+
+    /**
+     * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+     */
+    public function assertNotEmpty()
+    {
+        if (empty($this->contents)) {
+            $this->error('must not be empty');
+        }
+        return $this;
+    }
+
+    /**
+     * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+     */
+    public function assertIsLookup()
+    {
+        $this->assertIsArray();
+        foreach ($this->contents as $v) {
+            if ($v !== true) {
+                $this->error('must be a lookup array');
+            }
+        }
+        return $this;
+    }
+
+    /**
+     * @param string $msg
+     * @throws HTMLPurifier_ConfigSchema_Exception
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser
new file mode 100644 (file)
index 0000000..1e6ccd2
Binary files /dev/null and b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser differ
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt
new file mode 100644 (file)
index 0000000..0517fed
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt
new file mode 100644 (file)
index 0000000..249edd6
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt
new file mode 100644 (file)
index 0000000..9a8fa6a
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt
new file mode 100644 (file)
index 0000000..b017883
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt
new file mode 100644 (file)
index 0000000..e774b82
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt
new file mode 100644 (file)
index 0000000..533165e
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt
new file mode 100644 (file)
index 0000000..9eb7e38
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt
new file mode 100644 (file)
index 0000000..2f17bf4
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt
new file mode 100644 (file)
index 0000000..52654b5
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt
new file mode 100644 (file)
index 0000000..6440d21
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt
new file mode 100644 (file)
index 0000000..f31d226
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt
new file mode 100644 (file)
index 0000000..5f2b5e3
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt
new file mode 100644 (file)
index 0000000..6f58245
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt
new file mode 100644 (file)
index 0000000..cc49d43
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt
new file mode 100644 (file)
index 0000000..2c5924a
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt
new file mode 100644 (file)
index 0000000..d5caa1b
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt
new file mode 100644 (file)
index 0000000..2a47648
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt
new file mode 100644 (file)
index 0000000..663064a
--- /dev/null
@@ -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 &lt;a&gt; 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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt
new file mode 100644 (file)
index 0000000..3a48ba9
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt
new file mode 100644 (file)
index 0000000..db58b13
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt
new file mode 100644 (file)
index 0000000..7996488
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt
new file mode 100644 (file)
index 0000000..6367fe2
--- /dev/null
@@ -0,0 +1,14 @@
+AutoFormat.RemoveEmpty.Predicate
+TYPE: hash
+VERSION: 4.7.0
+DEFAULT: array('colgroup' => array(), 'th' => array(), 'td' => array(), 'iframe' => array('src'))
+--DESCRIPTION--
+<p>
+  Given that an element has no contents, it will be removed by default, unless
+  this predicate dictates otherwise.  The predicate can either be an associative
+  map from tag name to list of attributes that must be present for the element
+  to be considered preserved: thus, the default always preserves <code>colgroup</code>,
+  <code>th</code> and <code>td</code>, and also <code>iframe</code> if it
+  has a <code>src</code>.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt
new file mode 100644 (file)
index 0000000..35c393b
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt
new file mode 100644 (file)
index 0000000..ca17eb1
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt
new file mode 100644 (file)
index 0000000..34657ba
--- /dev/null
@@ -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>&lt;a&gt;&lt;/a&gt;</code> but not
+    <code>&lt;br /&gt;</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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt
new file mode 100644 (file)
index 0000000..dde990a
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt
new file mode 100644 (file)
index 0000000..b324608
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt
new file mode 100644 (file)
index 0000000..748be0e
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt
new file mode 100644 (file)
index 0000000..3fd4654
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt
new file mode 100644 (file)
index 0000000..460112e
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt
new file mode 100644 (file)
index 0000000..5cb7dda
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt
new file mode 100644 (file)
index 0000000..f1f5c5f
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt
new file mode 100644 (file)
index 0000000..7a32914
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt
new file mode 100644 (file)
index 0000000..148eedb
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt
new file mode 100644 (file)
index 0000000..e733a61
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt
new file mode 100644 (file)
index 0000000..c486724
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt
new file mode 100644 (file)
index 0000000..5403650
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt
new file mode 100644 (file)
index 0000000..b2b83d9
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt
new file mode 100644 (file)
index 0000000..568cbf3
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt
new file mode 100644 (file)
index 0000000..2c910cc
--- /dev/null
@@ -0,0 +1,16 @@
+Core.AllowHostnameUnderscore
+TYPE: bool
+VERSION: 4.6.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+    By RFC 1123, underscores are not permitted in host names.
+    (This is in contrast to the specification for DNS, RFC
+    2181, which allows underscores.)
+    However, most browsers do the right thing when faced with
+    an underscore in the host name, and so some poorly written
+    websites are written with the expectation this should work.
+    Setting this parameter to true relaxes our allowed character
+    check so that underscores are permitted.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt
new file mode 100644 (file)
index 0000000..d731791
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt
new file mode 100644 (file)
index 0000000..c572c14
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt
new file mode 100644 (file)
index 0000000..64b114f
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt
new file mode 100644 (file)
index 0000000..36f16e0
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt
new file mode 100644 (file)
index 0000000..1cd4c2c
--- /dev/null
@@ -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>&lt;object&gt;</code> in any descendant of a
+  <code>&lt;pre&gt;</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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt
new file mode 100644 (file)
index 0000000..ce243c3
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt
new file mode 100644 (file)
index 0000000..8bfb47c
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt
new file mode 100644 (file)
index 0000000..a3881be
--- /dev/null
@@ -0,0 +1,12 @@
+Core.EscapeInvalidChildren
+TYPE: bool
+DEFAULT: false
+--DESCRIPTION--
+<p><strong>Warning:</strong> this configuration option is no longer does anything as of 4.6.0.</p>
+
+<p>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.</p>
+--# vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt
new file mode 100644 (file)
index 0000000..a7a5b24
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt
new file mode 100644 (file)
index 0000000..abb4999
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt
new file mode 100644 (file)
index 0000000..915391e
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt
new file mode 100644 (file)
index 0000000..233fca1
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt
new file mode 100644 (file)
index 0000000..8983e2c
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt
new file mode 100644 (file)
index 0000000..eb841a7
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt
new file mode 100644 (file)
index 0000000..d77f536
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt
new file mode 100644 (file)
index 0000000..4070c2a
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt
new file mode 100644 (file)
index 0000000..3397d9f
--- /dev/null
@@ -0,0 +1,11 @@
+Core.RemoveProcessingInstructions
+TYPE: bool
+VERSION: 4.2.0
+DEFAULT: false
+--DESCRIPTION--
+Instead of escaping processing instructions in the form <code>&lt;? ...
+?&gt;</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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt
new file mode 100644 (file)
index 0000000..a4cd966
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt
new file mode 100644 (file)
index 0000000..3db50ef
--- /dev/null
@@ -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-&gt;addFilter()</code>
+  method. Specify an array of concrete implementations.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt
new file mode 100644 (file)
index 0000000..16829bc
--- /dev/null
@@ -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 &lt;, &gt; and &amp;
+  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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt
new file mode 100644 (file)
index 0000000..7f95f54
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt
new file mode 100644 (file)
index 0000000..6c231b2
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt
new file mode 100644 (file)
index 0000000..078d087
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt
new file mode 100644 (file)
index 0000000..321eaa2
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt
new file mode 100644 (file)
index 0000000..0b2c106
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt
new file mode 100644 (file)
index 0000000..fcf093f
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt
new file mode 100644 (file)
index 0000000..140e214
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt
new file mode 100644 (file)
index 0000000..f22e977
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt
new file mode 100644 (file)
index 0000000..1d3fa79
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt
new file mode 100644 (file)
index 0000000..5a59a55
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt
new file mode 100644 (file)
index 0000000..151fb7b
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt
new file mode 100644 (file)
index 0000000..45ae469
--- /dev/null
@@ -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>&lt;blockquote&gt;Foo&lt;/blockquote&gt;</code> would become
+    <code>&lt;blockquote&gt;&lt;p&gt;Foo&lt;/p&gt;&lt;/blockquote&gt;</code>.
+    The <code>&lt;p&gt;</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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt
new file mode 100644 (file)
index 0000000..5246188
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt
new file mode 100644 (file)
index 0000000..6ed70b5
--- /dev/null
@@ -0,0 +1,9 @@
+HTML.CustomDoctype
+TYPE: string/null
+VERSION: 2.0.1
+DEFAULT: NULL
+--DESCRIPTION--
+
+A custom doctype for power-users who defined their own document
+type. This directive only applies when %HTML.Doctype is blank.
+--# vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt
new file mode 100644 (file)
index 0000000..103db75
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt
new file mode 100644 (file)
index 0000000..229ae02
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt
new file mode 100644 (file)
index 0000000..9dab497
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt
new file mode 100644 (file)
index 0000000..7878dc0
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt
new file mode 100644 (file)
index 0000000..57358f9
--- /dev/null
@@ -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@attr</code>. To disallow <code>href</code>
+    attributes in <code>a</code> tags, set this directive to
+    <code>a@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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt
new file mode 100644 (file)
index 0000000..93a53e1
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt
new file mode 100644 (file)
index 0000000..e424c38
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt
new file mode 100644 (file)
index 0000000..700b309
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt
new file mode 100644 (file)
index 0000000..62e8e16
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt
new file mode 100644 (file)
index 0000000..dfb7204
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt
new file mode 100644 (file)
index 0000000..cdda09a
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt
new file mode 100644 (file)
index 0000000..5eb6ec2
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt
new file mode 100644 (file)
index 0000000..ceb342e
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt
new file mode 100644 (file)
index 0000000..5ebc7a1
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt
new file mode 100644 (file)
index 0000000..a8b1de5
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt
new file mode 100644 (file)
index 0000000..587a167
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt
new file mode 100644 (file)
index 0000000..b4c271b
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt
new file mode 100644 (file)
index 0000000..4186ccd
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt
new file mode 100644 (file)
index 0000000..996762b
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt
new file mode 100644 (file)
index 0000000..1db9237
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt
new file mode 100644 (file)
index 0000000..2a47e38
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt
new file mode 100644 (file)
index 0000000..08921fd
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt
new file mode 100644 (file)
index 0000000..d6f0d9f
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt
new file mode 100644 (file)
index 0000000..93398e8
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt
new file mode 100644 (file)
index 0000000..79f8ad8
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt
new file mode 100644 (file)
index 0000000..232b023
--- /dev/null
@@ -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>&lt;el b="" a="" c="" /&gt;</code>
+  to <code>&lt;el a="" b="" c="" /&gt;</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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt
new file mode 100644 (file)
index 0000000..06bab00
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt
new file mode 100644 (file)
index 0000000..071bc02
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt
new file mode 100644 (file)
index 0000000..666635a
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt
new file mode 100644 (file)
index 0000000..876f068
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt
new file mode 100644 (file)
index 0000000..728e378
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt
new file mode 100644 (file)
index 0000000..f05312b
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt
new file mode 100644 (file)
index 0000000..80cfea9
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt
new file mode 100644 (file)
index 0000000..71ce025
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt
new file mode 100644 (file)
index 0000000..13c122c
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt
new file mode 100644 (file)
index 0000000..abcc1ef
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt
new file mode 100644 (file)
index 0000000..f891de4
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt
new file mode 100644 (file)
index 0000000..ee83b12
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt
new file mode 100644 (file)
index 0000000..0b6df76
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt
new file mode 100644 (file)
index 0000000..4214900
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt
new file mode 100644 (file)
index 0000000..58c81dc
--- /dev/null
@@ -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>&lt;a href=""&gt;</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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt
new file mode 100644 (file)
index 0000000..6fce0fd
--- /dev/null
@@ -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>&lt;img src=""&gt;</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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt
new file mode 100644 (file)
index 0000000..1e17c1d
--- /dev/null
@@ -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 === hash_hmac("sha256", $url, $secret_key)</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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt
new file mode 100644 (file)
index 0000000..23331a4
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt
new file mode 100644 (file)
index 0000000..7908483
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/info.ini b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/info.ini
new file mode 100644 (file)
index 0000000..5de4505
--- /dev/null
@@ -0,0 +1,3 @@
+name = "HTML Purifier"
+
+; vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php
new file mode 100644 (file)
index 0000000..543e3f8
--- /dev/null
@@ -0,0 +1,170 @@
+<?php
+
+/**
+ * @todo Unit test
+ */
+class HTMLPurifier_ContentSets
+{
+
+    /**
+     * List of content set strings (pipe separators) indexed by name.
+     * @type array
+     */
+    public $info = array();
+
+    /**
+     * List of content set lookups (element => true) indexed by name.
+     * @type array
+     * @note This is in HTMLPurifier_HTMLDefinition->info_content_sets
+     */
+    public $lookup = array();
+
+    /**
+     * Synchronized list of defined content sets (keys of info).
+     * @type array
+     */
+    protected $keys = array();
+    /**
+     * Synchronized list of defined content values (values of info).
+     * @type array
+     */
+    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 HTMLPurifier_HTMLModule[] $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) {
+            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 HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef reference
+     * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef
+     */
+    public function generateChildDef(&$def, $module)
+    {
+        if (!empty($def->child)) { // already done!
+            return;
+        }
+        $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 HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef to have ChildDef extracted
+     * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef
+     * @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 $string List of elements
+     * @return array Lookup array of elements
+     */
+    protected function convertToLookup($string)
+    {
+        $array = explode('|', str_replace(' ', '', $string));
+        $ret = array();
+        foreach ($array as $k) {
+            $ret[$k] = true;
+        }
+        return $ret;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Context.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Context.php
new file mode 100644 (file)
index 0000000..00e509c
--- /dev/null
@@ -0,0 +1,95 @@
+<?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.
+     * @type array
+     */
+    private $_storage = array();
+
+    /**
+     * Registers a variable into the context.
+     * @param string $name String name
+     * @param mixed $ref Reference to variable to be registered
+     */
+    public function register($name, &$ref)
+    {
+        if (array_key_exists($name, $this->_storage)) {
+            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 string $name String name
+     * @param bool $ignore_error Boolean whether or not to ignore error
+     * @return mixed
+     */
+    public function &get($name, $ignore_error = false)
+    {
+        if (!array_key_exists($name, $this->_storage)) {
+            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];
+    }
+
+    /**
+     * Destroys a variable in the context.
+     * @param string $name String name
+     */
+    public function destroy($name)
+    {
+        if (!array_key_exists($name, $this->_storage)) {
+            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 string $name String name
+     * @return bool
+     */
+    public function exists($name)
+    {
+        return array_key_exists($name, $this->_storage);
+    }
+
+    /**
+     * Loads a series of variables from an associative array
+     * @param array $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/library/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php
new file mode 100644 (file)
index 0000000..bc6d433
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * Super-class for definition datatype objects, implements serialization
+ * functions for the class.
+ */
+abstract class HTMLPurifier_Definition
+{
+
+    /**
+     * Has setup() been called yet?
+     * @type bool
+     */
+    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.
+     * @type bool
+     */
+    public $optimized = null;
+
+    /**
+     * What type of definition is it?
+     * @type string
+     */
+    public $type;
+
+    /**
+     * Sets up the definition object into the final form, something
+     * not done by the constructor
+     * @param HTMLPurifier_Config $config
+     */
+    abstract protected function doSetup($config);
+
+    /**
+     * Setup function that aborts if already setup
+     * @param HTMLPurifier_Config $config
+     */
+    public function setup($config)
+    {
+        if ($this->setup) {
+            return;
+        }
+        $this->setup = true;
+        $this->doSetup($config);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php
new file mode 100644 (file)
index 0000000..67bb5b1
--- /dev/null
@@ -0,0 +1,129 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $type;
+
+    /**
+     * @param string $type 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 HTMLPurifier_Config $config Instance of HTMLPurifier_Config
+     * @return string
+     */
+    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 string $key Key to test
+     * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config to test against
+     * @return bool
+     */
+    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 HTMLPurifier_Definition $def Definition object to check
+     * @return bool 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
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     */
+    abstract public function add($def, $config);
+
+    /**
+     * Unconditionally saves a definition object to the cache
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     */
+    abstract public function set($def, $config);
+
+    /**
+     * Replace an object in the cache
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     */
+    abstract public function replace($def, $config);
+
+    /**
+     * Retrieves a definition object from the cache
+     * @param HTMLPurifier_Config $config
+     */
+    abstract public function get($config);
+
+    /**
+     * Removes a definition object to the cache
+     * @param HTMLPurifier_Config $config
+     */
+    abstract public function remove($config);
+
+    /**
+     * Clears all objects from cache
+     * @param HTMLPurifier_Config $config
+     */
+    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.
+     * @param HTMLPurifier_Config $config
+     */
+    abstract public function cleanup($config);
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php
new file mode 100644 (file)
index 0000000..b57a51b
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+
+class HTMLPurifier_DefinitionCache_Decorator extends HTMLPurifier_DefinitionCache
+{
+
+    /**
+     * Cache object we are decorating
+     * @type HTMLPurifier_DefinitionCache
+     */
+    public $cache;
+
+    /**
+     * The name of the decorator
+     * @var string
+     */
+    public $name;
+
+    public function __construct()
+    {
+    }
+
+    /**
+     * Lazy decorator function
+     * @param HTMLPurifier_DefinitionCache $cache Reference to cache object to decorate
+     * @return HTMLPurifier_DefinitionCache_Decorator
+     */
+    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
+     * @return HTMLPurifier_DefinitionCache_Decorator
+     */
+    public function copy()
+    {
+        return new HTMLPurifier_DefinitionCache_Decorator();
+    }
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function add($def, $config)
+    {
+        return $this->cache->add($def, $config);
+    }
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function set($def, $config)
+    {
+        return $this->cache->set($def, $config);
+    }
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function replace($def, $config)
+    {
+        return $this->cache->replace($def, $config);
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function get($config)
+    {
+        return $this->cache->get($config);
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function remove($config)
+    {
+        return $this->cache->remove($config);
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function flush($config)
+    {
+        return $this->cache->flush($config);
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function cleanup($config)
+    {
+        return $this->cache->cleanup($config);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php
new file mode 100644 (file)
index 0000000..4991777
--- /dev/null
@@ -0,0 +1,78 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $name = 'Cleanup';
+
+    /**
+     * @return HTMLPurifier_DefinitionCache_Decorator_Cleanup
+     */
+    public function copy()
+    {
+        return new HTMLPurifier_DefinitionCache_Decorator_Cleanup();
+    }
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function add($def, $config)
+    {
+        $status = parent::add($def, $config);
+        if (!$status) {
+            parent::cleanup($config);
+        }
+        return $status;
+    }
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function set($def, $config)
+    {
+        $status = parent::set($def, $config);
+        if (!$status) {
+            parent::cleanup($config);
+        }
+        return $status;
+    }
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function replace($def, $config)
+    {
+        $status = parent::replace($def, $config);
+        if (!$status) {
+            parent::cleanup($config);
+        }
+        return $status;
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function get($config)
+    {
+        $ret = parent::get($config);
+        if (!$ret) {
+            parent::cleanup($config);
+        }
+        return $ret;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php
new file mode 100644 (file)
index 0000000..d529dce
--- /dev/null
@@ -0,0 +1,85 @@
+<?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
+{
+    /**
+     * @type array
+     */
+    protected $definitions;
+
+    /**
+     * @type string
+     */
+    public $name = 'Memory';
+
+    /**
+     * @return HTMLPurifier_DefinitionCache_Decorator_Memory
+     */
+    public function copy()
+    {
+        return new HTMLPurifier_DefinitionCache_Decorator_Memory();
+    }
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function add($def, $config)
+    {
+        $status = parent::add($def, $config);
+        if ($status) {
+            $this->definitions[$this->generateKey($config)] = $def;
+        }
+        return $status;
+    }
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function set($def, $config)
+    {
+        $status = parent::set($def, $config);
+        if ($status) {
+            $this->definitions[$this->generateKey($config)] = $def;
+        }
+        return $status;
+    }
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function replace($def, $config)
+    {
+        $status = parent::replace($def, $config);
+        if ($status) {
+            $this->definitions[$this->generateKey($config)] = $def;
+        }
+        return $status;
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in
new file mode 100644 (file)
index 0000000..b1fec8d
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+require_once 'HTMLPurifier/DefinitionCache/Decorator.php';
+
+/**
+ * Definition cache decorator template.
+ */
+class HTMLPurifier_DefinitionCache_Decorator_Template extends HTMLPurifier_DefinitionCache_Decorator
+{
+
+    /**
+     * @type string
+     */
+    public $name = 'Template'; // replace this
+
+    public function copy()
+    {
+        // replace class name with yours
+        return new HTMLPurifier_DefinitionCache_Decorator_Template();
+    }
+
+    // remove methods you don't need
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function add($def, $config)
+    {
+        return parent::add($def, $config);
+    }
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function set($def, $config)
+    {
+        return parent::set($def, $config);
+    }
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function replace($def, $config)
+    {
+        return parent::replace($def, $config);
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function get($config)
+    {
+        return parent::get($config);
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function flush($config)
+    {
+        return parent::flush($config);
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return mixed
+     */
+    public function cleanup($config)
+    {
+        return parent::cleanup($config);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Null.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Null.php
new file mode 100644 (file)
index 0000000..d9a75ce
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * Null cache object to use when no caching is on.
+ */
+class HTMLPurifier_DefinitionCache_Null extends HTMLPurifier_DefinitionCache
+{
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return bool
+     */
+    public function add($def, $config)
+    {
+        return false;
+    }
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return bool
+     */
+    public function set($def, $config)
+    {
+        return false;
+    }
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return bool
+     */
+    public function replace($def, $config)
+    {
+        return false;
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return bool
+     */
+    public function remove($config)
+    {
+        return false;
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return bool
+     */
+    public function get($config)
+    {
+        return false;
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return bool
+     */
+    public function flush($config)
+    {
+        return false;
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return bool
+     */
+    public function cleanup($config)
+    {
+        return false;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php
new file mode 100644 (file)
index 0000000..ce268d9
--- /dev/null
@@ -0,0 +1,291 @@
+<?php
+
+class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCache
+{
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return int|bool
+     */
+    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);
+    }
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return int|bool
+     */
+    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);
+    }
+
+    /**
+     * @param HTMLPurifier_Definition $def
+     * @param HTMLPurifier_Config $config
+     * @return int|bool
+     */
+    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);
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return bool|HTMLPurifier_Config
+     */
+    public function get($config)
+    {
+        $file = $this->generateFilePath($config);
+        if (!file_exists($file)) {
+            return false;
+        }
+        return unserialize(file_get_contents($file));
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return bool
+     */
+    public function remove($config)
+    {
+        $file = $this->generateFilePath($config);
+        if (!file_exists($file)) {
+            return false;
+        }
+        return unlink($file);
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return bool
+     */
+    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);
+        }
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return bool
+     */
+    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
+     * @param HTMLPurifier_Config $config
+     * @return string
+     * @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
+     * @param HTMLPurifier_Config $config
+     * @return string
+     * @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
+     * @param HTMLPurifier_Config $config
+     * @return mixed|string
+     * @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 string $file File name to write to
+     * @param string $data Data to write into file
+     * @param HTMLPurifier_Config $config
+     * @return int|bool 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 HTMLPurifier_Config $config
+     * @return bool 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;
+            }
+            mkdir($directory, $chmod);
+            if (!$this->_testPermissions($directory, $chmod)) {
+                trigger_error(
+                    'Base directory ' . $base . ' does not exist,
+                    please create or change using %Cache.SerializerPath',
+                    E_USER_WARNING
+                );
+                return false;
+            }
+        } 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 string $dir Directory path
+     * @param int $chmod Permissions
+     * @return bool True if directory is 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/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README
new file mode 100644 (file)
index 0000000..2e35c1c
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php
new file mode 100644 (file)
index 0000000..fd1cc9b
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * Responsible for creating definition caches.
+ */
+class HTMLPurifier_DefinitionCacheFactory
+{
+    /**
+     * @type array
+     */
+    protected $caches = array('Serializer' => array());
+
+    /**
+     * @type array
+     */
+    protected $implementations = array();
+
+    /**
+     * @type HTMLPurifier_DefinitionCache_Decorator[]
+     */
+    protected $decorators = array();
+
+    /**
+     * Initialize default decorators
+     */
+    public function setup()
+    {
+        $this->addDecorator('Cleanup');
+    }
+
+    /**
+     * Retrieves an instance of global definition cache factory.
+     * @param HTMLPurifier_DefinitionCacheFactory $prototype
+     * @return HTMLPurifier_DefinitionCacheFactory
+     */
+    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 string $short Short name of cache object, for reference
+     * @param string $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 string $type Name of definitions handled by cache
+     * @param HTMLPurifier_Config $config Config instance
+     * @return mixed
+     */
+    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 HTMLPurifier_DefinitionCache_Decorator|string $decorator An instance or the name of a decorator
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php
new file mode 100644 (file)
index 0000000..4acd06e
--- /dev/null
@@ -0,0 +1,73 @@
+<?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
+     * @type string
+     */
+    public $name;
+
+    /**
+     * List of standard modules (string identifiers or literal objects)
+     * that this doctype uses
+     * @type array
+     */
+    public $modules = array();
+
+    /**
+     * List of modules to use for tidying up code
+     * @type array
+     */
+    public $tidyModules = array();
+
+    /**
+     * Is the language derived from XML (i.e. XHTML)?
+     * @type bool
+     */
+    public $xml = true;
+
+    /**
+     * List of aliases for this doctype
+     * @type array
+     */
+    public $aliases = array();
+
+    /**
+     * Public DTD identifier
+     * @type string
+     */
+    public $dtdPublic;
+
+    /**
+     * System DTD identifier
+     * @type string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php
new file mode 100644 (file)
index 0000000..acc1d64
--- /dev/null
@@ -0,0 +1,142 @@
+<?php
+
+class HTMLPurifier_DoctypeRegistry
+{
+
+    /**
+     * Hash of doctype names to doctype objects.
+     * @type array
+     */
+    protected $doctypes;
+
+    /**
+     * Lookup table of aliases to real doctype names.
+     * @type array
+     */
+    protected $aliases;
+
+    /**
+     * Registers a doctype to the registry
+     * @note Accepts a fully-formed doctype object, or the
+     *       parameters for constructing a doctype object
+     * @param string $doctype Name of doctype or literal doctype object
+     * @param bool $xml
+     * @param array $modules Modules doctype will load
+     * @param array $tidy_modules Modules doctype will load for certain modes
+     * @param array $aliases Alias names for doctype
+     * @param string $dtd_public
+     * @param string $dtd_system
+     * @return HTMLPurifier_Doctype 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 string $doctype Name of doctype
+     * @return HTMLPurifier_Doctype 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).
+     * @param HTMLPurifier_Config $config
+     * @return HTMLPurifier_Doctype
+     */
+    public function make($config)
+    {
+        return clone $this->get($this->getDoctypeFromConfig($config));
+    }
+
+    /**
+     * Retrieves the doctype from the configuration object
+     * @param HTMLPurifier_Config $config
+     * @return string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php
new file mode 100644 (file)
index 0000000..d5311ce
--- /dev/null
@@ -0,0 +1,216 @@
+<?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?
+     * @type bool
+     */
+    public $standalone = true;
+
+    /**
+     * Associative array of attribute name to HTMLPurifier_AttrDef.
+     * @type array
+     * @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.
+     * @type array
+     */
+    public $attr_transform_pre = array();
+
+    /**
+     * List of tags HTMLPurifier_AttrTransform to be done after validation.
+     * @type array
+     */
+    public $attr_transform_post = array();
+
+    /**
+     * HTMLPurifier_ChildDef of this tag.
+     * @type HTMLPurifier_ChildDef
+     */
+    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
+     * @type string
+     */
+    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
+     * @type string
+     */
+    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.
+     * @type bool
+     */
+    public $descendants_are_inline = false;
+
+    /**
+     * List of the names of required attributes this element has.
+     * Dynamically populated by HTMLPurifier_HTMLDefinition::getElement()
+     * @type array
+     */
+    public $required_attr = array();
+
+    /**
+     * Lookup table of tags excluded from all descendants of this tag.
+     * @type array
+     * @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.
+     * @type array
+     */
+    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.
+     * @type string
+     */
+    public $wrap;
+
+    /**
+     * Whether or not this is a formatting element affected by the
+     * "Active Formatting Elements" algorithm.
+     * @type bool
+     */
+    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.
+     * @param HTMLPurifier_ElementDef $def
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php
new file mode 100644 (file)
index 0000000..fef9b58
--- /dev/null
@@ -0,0 +1,611 @@
+<?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.
+     * @param string $in Input encoding
+     * @param string $out Output encoding
+     * @param string $text The text to convert
+     * @return string
+     */
+    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.
+     * @param string $in Input encoding
+     * @param string $out Output encoding
+     * @param string $text The text to convert
+     * @param int $max_chunk_size
+     * @return string
+     */
+    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.
+     *
+     * @param string $str The string to clean
+     * @param bool $force_php
+     * @return string
+     *
+     * @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@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;
+    }
+
+    /**
+     * @return bool
+     */
+    public static function iconvAvailable()
+    {
+        static $iconv = null;
+        if ($iconv === null) {
+            $iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE;
+        }
+        return $iconv;
+    }
+
+    /**
+     * Convert a string to UTF-8 based on configuration.
+     * @param string $str The string to convert
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return string
+     */
+    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.
+     * @param string $str The string to convert
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return string
+     * @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 string $str UTF-8 string to be converted to ASCII
+     * @return string 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.
+     *
+     * @return int 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/library/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php
new file mode 100644 (file)
index 0000000..f12ff13
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Object that provides entity lookup table from entity name to character
+ */
+class HTMLPurifier_EntityLookup
+{
+    /**
+     * Assoc array of entity name to character represented.
+     * @type array
+     */
+    public $table;
+
+    /**
+     * Sets up the entity lookup table from the serialized file contents.
+     * @param bool $file
+     * @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 bool|HTMLPurifier_EntityLookup $prototype Optional prototype of custom lookup table to overload with.
+     * @return HTMLPurifier_EntityLookup
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser b/library/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser
new file mode 100644 (file)
index 0000000..e8b0812
--- /dev/null
@@ -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:"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:6:"sigmaf";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:8:"thetasym";s:2:"ϑ";s:5:"upsih";s:2:"ϒ";s:3:"piv";s:2:"ϖ";s:4:"bull";s:3:"•";s:6:"hellip";s:3:"…";s:5:"prime";s:3:"′";s:5:"Prime";s:3:"″";s:5:"oline";s:3:"‾";s:5:"frasl";s:3:"⁄";s:6:"weierp";s:3:"℘";s:5:"image";s:3:"ℑ";s:4:"real";s:3:"ℜ";s:5:"trade";s:3:"™";s:7:"alefsym";s:3:"ℵ";s:4:"larr";s:3:"←";s:4:"uarr";s:3:"↑";s:4:"rarr";s:3:"→";s:4:"darr";s:3:"↓";s:4:"harr";s:3:"↔";s:5:"crarr";s:3:"↵";s:4:"lArr";s:3:"⇐";s:4:"uArr";s:3:"⇑";s:4:"rArr";s:3:"⇒";s:4:"dArr";s:3:"⇓";s:4:"hArr";s:3:"⇔";s:6:"forall";s:3:"∀";s:4:"part";s:3:"∂";s:5:"exist";s:3:"∃";s:5:"empty";s:3:"∅";s:5:"nabla";s:3:"∇";s:4:"isin";s:3:"∈";s:5:"notin";s:3:"∉";s:2:"ni";s:3:"∋";s:4:"prod";s:3:"∏";s:3:"sum";s:3:"∑";s:5:"minus";s:3:"−";s:6:"lowast";s:3:"∗";s:5:"radic";s:3:"√";s:4:"prop";s:3:"∝";s:5:"infin";s:3:"∞";s:3:"ang";s:3:"∠";s:3:"and";s:3:"∧";s:2:"or";s:3:"∨";s:3:"cap";s:3:"∩";s:3:"cup";s:3:"∪";s:3:"int";s:3:"∫";s:6:"there4";s:3:"∴";s:3:"sim";s:3:"∼";s:4:"cong";s:3:"≅";s:5:"asymp";s:3:"≈";s:2:"ne";s:3:"≠";s:5:"equiv";s:3:"≡";s:2:"le";s:3:"≤";s:2:"ge";s:3:"≥";s:3:"sub";s:3:"⊂";s:3:"sup";s:3:"⊃";s:4:"nsub";s:3:"⊄";s:4:"sube";s:3:"⊆";s:4:"supe";s:3:"⊇";s:5:"oplus";s:3:"⊕";s:6:"otimes";s:3:"⊗";s:4:"perp";s:3:"⊥";s:4:"sdot";s:3:"⋅";s:5:"lceil";s:3:"⌈";s:5:"rceil";s:3:"⌉";s:6:"lfloor";s:3:"⌊";s:6:"rfloor";s:3:"⌋";s:4:"lang";s:3:"〈";s:4:"rang";s:3:"〉";s:3:"loz";s:3:"◊";s:6:"spades";s:3:"♠";s:5:"clubs";s:3:"♣";s:6:"hearts";s:3:"♥";s:5:"diams";s:3:"♦";s:4:"quot";s:1:""";s:3:"amp";s:1:"&";s:2:"lt";s:1:"<";s:2:"gt";s:1:">";s:4:"apos";s:1:"'";s:5:"OElig";s:2:"Œ";s:5:"oelig";s:2:"œ";s:6:"Scaron";s:2:"Š";s:6:"scaron";s:2:"š";s:4:"Yuml";s:2:"Ÿ";s:4:"circ";s:2:"ˆ";s:5:"tilde";s:2:"˜";s:4:"ensp";s:3:" ";s:4:"emsp";s:3:" ";s:6:"thinsp";s:3:" ";s:4:"zwnj";s:3:"‌";s:3:"zwj";s:3:"‍";s:3:"lrm";s:3:"‎";s:3:"rlm";s:3:"‏";s:5:"ndash";s:3:"–";s:5:"mdash";s:3:"—";s:5:"lsquo";s:3:"‘";s:5:"rsquo";s:3:"’";s:5:"sbquo";s:3:"‚";s:5:"ldquo";s:3:"“";s:5:"rdquo";s:3:"”";s:5:"bdquo";s:3:"„";s:6:"dagger";s:3:"†";s:6:"Dagger";s:3:"‡";s:6:"permil";s:3:"‰";s:6:"lsaquo";s:3:"‹";s:6:"rsaquo";s:3:"›";s:4:"euro";s:3:"€";s:4:"nbsp";s:2:" ";s:5:"iexcl";s:2:"¡";s:4:"cent";s:2:"¢";s:5:"pound";s:2:"£";s:6:"curren";s:2:"¤";s:3:"yen";s:2:"¥";s:6:"brvbar";s:2:"¦";s:4:"sect";s:2:"§";s:3:"uml";s:2:"¨";s:4:"copy";s:2:"©";s:4:"ordf";s:2:"ª";s:5:"laquo";s:2:"«";s:3:"not";s:2:"¬";s:3:"shy";s:2:"­";s:3:"reg";s:2:"®";s:4:"macr";s:2:"¯";s:3:"deg";s:2:"°";s:6:"plusmn";s:2:"±";s:4:"sup2";s:2:"²";s:4:"sup3";s:2:"³";s:5:"acute";s:2:"´";s:5:"micro";s:2:"µ";s:4:"para";s:2:"¶";s:6:"middot";s:2:"·";s:5:"cedil";s:2:"¸";s:4:"sup1";s:2:"¹";s:4:"ordm";s:2:"º";s:5:"raquo";s:2:"»";s:6:"frac14";s:2:"¼";s:6:"frac12";s:2:"½";s:6:"frac34";s:2:"¾";s:6:"iquest";s:2:"¿";s:6:"Agrave";s:2:"À";s:6:"Aacute";s:2:"Á";s:5:"Acirc";s:2:"Â";s:6:"Atilde";s:2:"Ã";s:4:"Auml";s:2:"Ä";s:5:"Aring";s:2:"Å";s:5:"AElig";s:2:"Æ";s:6:"Ccedil";s:2:"Ç";s:6:"Egrave";s:2:"È";s:6:"Eacute";s:2:"É";s:5:"Ecirc";s:2:"Ê";s:4:"Euml";s:2:"Ë";s:6:"Igrave";s:2:"Ì";s:6:"Iacute";s:2:"Í";s:5:"Icirc";s:2:"Î";s:4:"Iuml";s:2:"Ï";s:3:"ETH";s:2:"Ð";s:6:"Ntilde";s:2:"Ñ";s:6:"Ograve";s:2:"Ò";s:6:"Oacute";s:2:"Ó";s:5:"Ocirc";s:2:"Ô";s:6:"Otilde";s:2:"Õ";s:4:"Ouml";s:2:"Ö";s:5:"times";s:2:"×";s:6:"Oslash";s:2:"Ø";s:6:"Ugrave";s:2:"Ù";s:6:"Uacute";s:2:"Ú";s:5:"Ucirc";s:2:"Û";s:4:"Uuml";s:2:"Ü";s:6:"Yacute";s:2:"Ý";s:5:"THORN";s:2:"Þ";s:5:"szlig";s:2:"ß";s:6:"agrave";s:2:"à";s:6:"aacute";s:2:"á";s:5:"acirc";s:2:"â";s:6:"atilde";s:2:"ã";s:4:"auml";s:2:"ä";s:5:"aring";s:2:"å";s:5:"aelig";s:2:"æ";s:6:"ccedil";s:2:"ç";s:6:"egrave";s:2:"è";s:6:"eacute";s:2:"é";s:5:"ecirc";s:2:"ê";s:4:"euml";s:2:"ë";s:6:"igrave";s:2:"ì";s:6:"iacute";s:2:"í";s:5:"icirc";s:2:"î";s:4:"iuml";s:2:"ï";s:3:"eth";s:2:"ð";s:6:"ntilde";s:2:"ñ";s:6:"ograve";s:2:"ò";s:6:"oacute";s:2:"ó";s:5:"ocirc";s:2:"ô";s:6:"otilde";s:2:"õ";s:4:"ouml";s:2:"ö";s:6:"divide";s:2:"÷";s:6:"oslash";s:2:"ø";s:6:"ugrave";s:2:"ù";s:6:"uacute";s:2:"ú";s:5:"ucirc";s:2:"û";s:4:"uuml";s:2:"ü";s:6:"yacute";s:2:"ý";s:5:"thorn";s:2:"þ";s:4:"yuml";s:2:"ÿ";}
\ No newline at end of file
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php
new file mode 100644 (file)
index 0000000..61529dc
--- /dev/null
@@ -0,0 +1,153 @@
+<?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.
+     * @type HTMLPurifier_EntityLookup
+     */
+    protected $_entity_lookup;
+
+    /**
+     * Callback regex string for parsing entities.
+     * @type string
+     */
+    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.
+     * @type array
+     */
+    protected $_special_dec2str =
+            array(
+                    34 => '"',
+                    38 => '&',
+                    39 => "'",
+                    60 => '<',
+                    62 => '>'
+            );
+
+    /**
+     * Stripped entity names to decimal conversion table for special entities.
+     * @type array
+     */
+    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 String to have non-special entities parsed.
+     * @return string 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 array $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).
+     * @return string 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 String to have non-special entities parsed.
+     * @return string 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 array $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).
+     * @return string 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/library/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php
new file mode 100644 (file)
index 0000000..d47e3f2
--- /dev/null
@@ -0,0 +1,244 @@
+<?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;
+
+    /**
+     * @type array
+     */
+    protected $errors;
+
+    /**
+     * @type array
+     */
+    protected $_current;
+
+    /**
+     * @type array
+     */
+    protected $_stacks = array(array());
+
+    /**
+     * @type HTMLPurifier_Language
+     */
+    protected $locale;
+
+    /**
+     * @type HTMLPurifier_Generator
+     */
+    protected $generator;
+
+    /**
+     * @type HTMLPurifier_Context
+     */
+    protected $context;
+
+    /**
+     * @type array
+     */
+    protected $lines = array();
+
+    /**
+     * @param HTMLPurifier_Context $context
+     */
+    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 int $severity Error severity, PHP error style (don't use E_USER_)
+     * @param string $msg Error message text
+     */
+    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 ...
+        // 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
+     */
+    public function getRaw()
+    {
+        return $this->errors;
+    }
+
+    /**
+     * Default HTML formatting implementation for error messages
+     * @param HTMLPurifier_Config $config Configuration, vital for HTML output nature
+     * @param array $errors Errors array to display; used for recursion.
+     * @return string
+     */
+    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 $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/library/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php
new file mode 100644 (file)
index 0000000..cf869d3
--- /dev/null
@@ -0,0 +1,74 @@
+<?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.
+     * @type string
+     */
+    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')
+     * @type mixed
+     */
+    public $value;
+
+    /**
+     * Errors registered for this structure.
+     * @type array
+     */
+    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']
+     * @type array
+     */
+    public $children = array();
+
+    /**
+     * @param string $type
+     * @param string $id
+     * @return mixed
+     */
+    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];
+    }
+
+    /**
+     * @param int $severity
+     * @param string $message
+     */
+    public function addError($severity, $message)
+    {
+        $this->errors[] = array($severity, $message);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php
new file mode 100644 (file)
index 0000000..be85b4c
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/Filter.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Filter.php
new file mode 100644 (file)
index 0000000..c1f41ee
--- /dev/null
@@ -0,0 +1,56 @@
+<?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.
+     * @type string
+     */
+    public $name;
+
+    /**
+     * Pre-processor function, handles HTML before HTML Purifier
+     * @param string $html
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return string
+     */
+    public function preFilter($html, $config, $context)
+    {
+        return $html;
+    }
+
+    /**
+     * Post-processor function, handles HTML after HTML Purifier
+     * @param string $html
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return string
+     */
+    public function postFilter($html, $config, $context)
+    {
+        return $html;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php
new file mode 100644 (file)
index 0000000..08e62c1
--- /dev/null
@@ -0,0 +1,338 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $name = 'ExtractStyleBlocks';
+
+    /**
+     * @type array
+     */
+    private $_styleMatches = array();
+
+    /**
+     * @type csstidy
+     */
+    private $_tidy;
+
+    /**
+     * @type HTMLPurifier_AttrDef_HTML_ID
+     */
+    private $_id_attrdef;
+
+    /**
+     * @type HTMLPurifier_AttrDef_CSS_Ident
+     */
+    private $_class_attrdef;
+
+    /**
+     * @type HTMLPurifier_AttrDef_Enum
+     */
+    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 array $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
+     * @param string $html
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return string
+     * @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 string $css CSS styling to clean
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @throws HTMLPurifier_Exception
+     * @return string 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/library/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php
new file mode 100644 (file)
index 0000000..276d836
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+
+class HTMLPurifier_Filter_YouTube extends HTMLPurifier_Filter
+{
+
+    /**
+     * @type string
+     */
+    public $name = 'YouTube';
+
+    /**
+     * @param string $html
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return string
+     */
+    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);
+    }
+
+    /**
+     * @param string $html
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return string
+     */
+    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);
+    }
+
+    /**
+     * @param $url
+     * @return string
+     */
+    protected function armorUrl($url)
+    {
+        return str_replace('--', '-&#45;', $url);
+    }
+
+    /**
+     * @param array $matches
+     * @return string
+     */
+    protected function postFilterCallback($matches)
+    {
+        $url = $this->armorUrl($matches[1]);
+        return '<object width="425" height="350" type="application/x-shockwave-flash" ' .
+        'data="//www.youtube.com/' . $url . '">' .
+        '<param name="movie" value="//www.youtube.com/' . $url . '"></param>' .
+        '<!--[if IE]>' .
+        '<embed src="//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/library/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php
new file mode 100644 (file)
index 0000000..6fb5687
--- /dev/null
@@ -0,0 +1,286 @@
+<?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.
+     * @type bool
+     */
+    private $_xhtml = true;
+
+    /**
+     * :HACK: Whether or not generator should comment the insides of <script> tags.
+     * @type bool
+     */
+    private $_scriptFix = false;
+
+    /**
+     * Cache of HTMLDefinition during HTML output to determine whether or
+     * not attributes should be minimized.
+     * @type HTMLPurifier_HTMLDefinition
+     */
+    private $_def;
+
+    /**
+     * Cache of %Output.SortAttr.
+     * @type bool
+     */
+    private $_sortAttr;
+
+    /**
+     * Cache of %Output.FlashCompat.
+     * @type bool
+     */
+    private $_flashCompat;
+
+    /**
+     * Cache of %Output.FixInnerHTML.
+     * @type bool
+     */
+    private $_innerHTMLFix;
+
+    /**
+     * Stack for keeping track of object information when outputting IE
+     * compatibility code.
+     * @type array
+     */
+    private $_flashStack = array();
+
+    /**
+     * Configuration for the generator
+     * @type HTMLPurifier_Config
+     */
+    protected $config;
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $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 HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token
+     * @return string 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 HTMLPurifier_Token $token HTMLPurifier_Token object.
+     * @return string 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
+     * @param HTMLPurifier_Token $token HTMLPurifier_Token object.
+     * @return string
+     * @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 array $assoc_array_of_attributes Attribute array
+     * @param string $element Name of element attributes are for, used to check
+     *        attribute minimization.
+     * @return string Generated HTML fragment for insertion.
+     */
+    public function generateAttributes($assoc_array_of_attributes, $element = '')
+    {
+        $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 String data to escape for HTML.
+     * @param int $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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php
new file mode 100644 (file)
index 0000000..9b7b334
--- /dev/null
@@ -0,0 +1,493 @@
+<?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.
+     * @type HTMLPurifier_ElementDef[]
+     */
+    public $info = array();
+
+    /**
+     * Associative array of global attribute name to attribute definition.
+     * @type array
+     */
+    public $info_global_attr = array();
+
+    /**
+     * String name of parent element HTML will be going into.
+     * @type string
+     */
+    public $info_parent = 'div';
+
+    /**
+     * Definition for parent element, allows parent element to be a
+     * tag that's not allowed inside the HTML fragment.
+     * @type HTMLPurifier_ElementDef
+     */
+    public $info_parent_def;
+
+    /**
+     * String name of element used to wrap inline elements in block context.
+     * @type string
+     * @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.
+     * @type array
+     */
+    public $info_tag_transform = array();
+
+    /**
+     * Indexed list of HTMLPurifier_AttrTransform to be performed before validation.
+     * @type HTMLPurifier_AttrTransform[]
+     */
+    public $info_attr_transform_pre = array();
+
+    /**
+     * Indexed list of HTMLPurifier_AttrTransform to be performed after validation.
+     * @type HTMLPurifier_AttrTransform[]
+     */
+    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.
+     * @type array
+     */
+    public $info_content_sets = array();
+
+    /**
+     * Indexed list of HTMLPurifier_Injector to be used.
+     * @type HTMLPurifier_Injector[]
+     */
+    public $info_injector = array();
+
+    /**
+     * Doctype object
+     * @type HTMLPurifier_Doctype
+     */
+    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 string $element_name Element name to add attribute to
+     * @param string $attr_name Name of attribute
+     * @param mixed $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
+     * @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
+     * @param string $element_name
+     * @return HTMLPurifier_ElementDef
+     * @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.
+     * @return HTMLPurifier_HTMLModule
+     */
+    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 --------------------------------------
+
+    /**
+     * @type string
+     */
+    public $type = 'HTML';
+
+    /**
+     * @type HTMLPurifier_HTMLModuleManager
+     */
+    public $manager;
+
+    /**
+     * Performs low-cost, preliminary initialization.
+     */
+    public function __construct()
+    {
+        $this->manager = new HTMLPurifier_HTMLModuleManager();
+    }
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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
+     * @param HTMLPurifier_Config $config
+     */
+    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.
+     * @param HTMLPurifier_Config $config
+     */
+    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;
+                } elseif (isset($forbidden_attributes["$tag.$attr"])) { // this segment might get removed eventually
+                    // $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@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 array $list String list to parse
+     * @return array
+     * @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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php
new file mode 100644 (file)
index 0000000..bb3a923
--- /dev/null
@@ -0,0 +1,284 @@
+<?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.
+     * @type string
+     */
+    public $name;
+
+    /**
+     * Informally, a list of elements this module changes.
+     * Not used in any significant way.
+     * @type array
+     */
+    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.
+     * @type array
+     */
+    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.
+     * @type array
+     */
+    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.
+     * @type array
+     */
+    public $attr_collections = array();
+
+    /**
+     * Associative array of deprecated tag name to HTMLPurifier_TagTransform.
+     * @type array
+     */
+    public $info_tag_transform = array();
+
+    /**
+     * List of HTMLPurifier_AttrTransform to be performed before validation.
+     * @type array
+     */
+    public $info_attr_transform_pre = array();
+
+    /**
+     * List of HTMLPurifier_AttrTransform to be performed after validation.
+     * @type array
+     */
+    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.
+     * @type array
+     */
+    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!
+     * @type bool
+     */
+    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")
+     *
+     * @type bool
+     * @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 HTMLPurifier_ElementDef $def
+     * @return HTMLPurifier_ChildDef subclass
+     */
+    public function getChildDef($def)
+    {
+        return false;
+    }
+
+    // -- Convenience -----------------------------------------------------
+
+    /**
+     * Convenience function that sets up a new element
+     * @param string $element Name of element to add
+     * @param string|bool $type What content set should element be registered to?
+     *              Set as false to skip this step.
+     * @param string $contents Allowed children in form of:
+     *              "$content_model_type: $content_model"
+     * @param array $attr_includes What attribute collections to register to
+     *              element?
+     * @param array $attr What unique attributes does the element define?
+     * @see HTMLPurifier_ElementDef:: for in-depth descriptions of these parameters.
+     * @return HTMLPurifier_ElementDef 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 string $element Name of element to create
+     * @return HTMLPurifier_ElementDef 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 string $element Element to register
+     * @param string $type 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 string $contents Allowed children in form of:
+     *                  "$content_model_type: $content_model"
+     * @return array
+     * @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 array $attr Reference to attr array to modify
+     * @param array $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 string $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 array 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 HTMLPurifier_Config $config
+     */
+    public function setup($config)
+    {
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php
new file mode 100644 (file)
index 0000000..1e67c79
--- /dev/null
@@ -0,0 +1,44 @@
+<?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
+{
+
+    /**
+     * @type string
+     */
+    public $name = 'Bdo';
+
+    /**
+     * @type array
+     */
+    public $attr_collections = array(
+        'I18N' => array('dir' => false)
+    );
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php
new file mode 100644 (file)
index 0000000..a96ab1b
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+
+class HTMLPurifier_HTMLModule_CommonAttributes extends HTMLPurifier_HTMLModule
+{
+    /**
+     * @type string
+     */
+    public $name = 'CommonAttributes';
+
+    /**
+     * @type array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php
new file mode 100644 (file)
index 0000000..a9042a3
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * XHTML 1.1 Edit Module, defines editing-related elements. Text Extension
+ * Module.
+ */
+class HTMLPurifier_HTMLModule_Edit extends HTMLPurifier_HTMLModule
+{
+
+    /**
+     * @type string
+     */
+    public $name = 'Edit';
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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)
+
+    /**
+     * @type bool
+     */
+    public $defines_child_def = true;
+
+    /**
+     * @param HTMLPurifier_ElementDef $def
+     * @return HTMLPurifier_ChildDef_Chameleon
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php
new file mode 100644 (file)
index 0000000..6f7ddbc
--- /dev/null
@@ -0,0 +1,190 @@
+<?php
+
+/**
+ * XHTML 1.1 Forms module, defines all form-related elements found in HTML 4.
+ */
+class HTMLPurifier_HTMLModule_Forms extends HTMLPurifier_HTMLModule
+{
+    /**
+     * @type string
+     */
+    public $name = 'Forms';
+
+    /**
+     * @type bool
+     */
+    public $safe = false;
+
+    /**
+     * @type array
+     */
+    public $content_sets = array(
+        'Block' => 'Form',
+        'Inline' => 'Formctrl',
+    );
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php
new file mode 100644 (file)
index 0000000..72d7a31
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * XHTML 1.1 Hypertext Module, defines hypertext links. Core Module.
+ */
+class HTMLPurifier_HTMLModule_Hypertext extends HTMLPurifier_HTMLModule
+{
+
+    /**
+     * @type string
+     */
+    public $name = 'Hypertext';
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php
new file mode 100644 (file)
index 0000000..f7e7c91
--- /dev/null
@@ -0,0 +1,51 @@
+<?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
+{
+
+    /**
+     * @type string
+     */
+    public $name = 'Iframe';
+
+    /**
+     * @type bool
+     */
+    public $safe = false;
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php
new file mode 100644 (file)
index 0000000..0f5fdb3
--- /dev/null
@@ -0,0 +1,49 @@
+<?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
+{
+
+    /**
+     * @type string
+     */
+    public $name = 'Image';
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php
new file mode 100644 (file)
index 0000000..86b5299
--- /dev/null
@@ -0,0 +1,186 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $name = 'Legacy';
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    public function setup($config)
+    {
+        $this->addElement(
+            'basefont',
+            'Inline',
+            'Empty',
+            null,
+            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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php
new file mode 100644 (file)
index 0000000..7a20ff7
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * XHTML 1.1 List Module, defines list-oriented elements. Core Module.
+ */
+class HTMLPurifier_HTMLModule_List extends HTMLPurifier_HTMLModule
+{
+    /**
+     * @type string
+     */
+    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.
+
+    /**
+     * @type array
+     */
+    public $content_sets = array('Flow' => 'List');
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php
new file mode 100644 (file)
index 0000000..60c0545
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+class HTMLPurifier_HTMLModule_Name extends HTMLPurifier_HTMLModule
+{
+    /**
+     * @type string
+     */
+    public $name = 'Name';
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php
new file mode 100644 (file)
index 0000000..dc9410a
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Module adds the nofollow attribute transformation to a tags.  It
+ * is enabled by HTML.Nofollow
+ */
+class HTMLPurifier_HTMLModule_Nofollow extends HTMLPurifier_HTMLModule
+{
+
+    /**
+     * @type string
+     */
+    public $name = 'Nofollow';
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php
new file mode 100644 (file)
index 0000000..da72225
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+class HTMLPurifier_HTMLModule_NonXMLCommonAttributes extends HTMLPurifier_HTMLModule
+{
+    /**
+     * @type string
+     */
+    public $name = 'NonXMLCommonAttributes';
+
+    /**
+     * @type array
+     */
+    public $attr_collections = array(
+        'Lang' => array(
+            'lang' => 'LanguageCode',
+        )
+    );
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php
new file mode 100644 (file)
index 0000000..2f9efc5
--- /dev/null
@@ -0,0 +1,62 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $name = 'Object';
+
+    /**
+     * @type bool
+     */
+    public $safe = false;
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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',
+            null,
+            array(
+                'id' => 'ID',
+                'name*' => 'Text',
+                'type' => 'Text',
+                'value' => 'Text',
+                'valuetype' => 'Enum#data,ref,object'
+            )
+        );
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php
new file mode 100644 (file)
index 0000000..6458ce9
--- /dev/null
@@ -0,0 +1,42 @@
+<?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
+{
+
+    /**
+     * @type string
+     */
+    public $name = 'Presentation';
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php
new file mode 100644 (file)
index 0000000..5ee3c8e
--- /dev/null
@@ -0,0 +1,40 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $name = 'Proprietary';
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php
new file mode 100644 (file)
index 0000000..a0d4892
--- /dev/null
@@ -0,0 +1,36 @@
+<?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
+{
+
+    /**
+     * @type string
+     */
+    public $name = 'Ruby';
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php
new file mode 100644 (file)
index 0000000..04e6689
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * A "safe" embed module. See SafeObject. This is a proprietary element.
+ */
+class HTMLPurifier_HTMLModule_SafeEmbed extends HTMLPurifier_HTMLModule
+{
+    /**
+     * @type string
+     */
+    public $name = 'SafeEmbed';
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php
new file mode 100644 (file)
index 0000000..1297f80
--- /dev/null
@@ -0,0 +1,62 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $name = 'SafeObject';
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php
new file mode 100644 (file)
index 0000000..0330cd9
--- /dev/null
@@ -0,0 +1,40 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $name = 'SafeScripting';
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php
new file mode 100644 (file)
index 0000000..8b28a7b
--- /dev/null
@@ -0,0 +1,73 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $name = 'Scripting';
+
+    /**
+     * @type array
+     */
+    public $elements = array('script', 'noscript');
+
+    /**
+     * @type array
+     */
+    public $content_sets = array('Block' => 'script | noscript', 'Inline' => 'script | noscript');
+
+    /**
+     * @type bool
+     */
+    public $safe = false;
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php
new file mode 100644 (file)
index 0000000..497b832
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * XHTML 1.1 Edit Module, defines editing-related elements. Text Extension
+ * Module.
+ */
+class HTMLPurifier_HTMLModule_StyleAttribute extends HTMLPurifier_HTMLModule
+{
+    /**
+     * @type string
+     */
+    public $name = 'StyleAttribute';
+
+    /**
+     * @type array
+     */
+    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'))
+    );
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    public function setup($config)
+    {
+        $this->attr_collections['Style']['style'] = new HTMLPurifier_AttrDef_CSS();
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php
new file mode 100644 (file)
index 0000000..8a0b3b4
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * XHTML 1.1 Tables Module, fully defines accessible table elements.
+ */
+class HTMLPurifier_HTMLModule_Tables extends HTMLPurifier_HTMLModule
+{
+    /**
+     * @type string
+     */
+    public $name = 'Tables';
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.php
new file mode 100644 (file)
index 0000000..b188ac9
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * XHTML 1.1 Target Module, defines target attribute in link elements.
+ */
+class HTMLPurifier_HTMLModule_Target extends HTMLPurifier_HTMLModule
+{
+    /**
+     * @type string
+     */
+    public $name = 'Target';
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php
new file mode 100644 (file)
index 0000000..58ccc68
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * Module adds the target=blank attribute transformation to a tags.  It
+ * is enabled by HTML.TargetBlank
+ */
+class HTMLPurifier_HTMLModule_TargetBlank extends HTMLPurifier_HTMLModule
+{
+    /**
+     * @type string
+     */
+    public $name = 'TargetBlank';
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.php
new file mode 100644 (file)
index 0000000..7a65e00
--- /dev/null
@@ -0,0 +1,87 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $name = 'Text';
+
+    /**
+     * @type array
+     */
+    public $content_sets = array(
+        'Flow' => 'Heading | Block | Inline'
+    );
+
+    /**
+     * @param HTMLPurifier_Config $config
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php
new file mode 100644 (file)
index 0000000..08aa232
--- /dev/null
@@ -0,0 +1,230 @@
+<?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.
+     * @type array
+     */
+    public $levels = array(0 => 'none', 'light', 'medium', 'heavy');
+
+    /**
+     * Default level to place all fixes in.
+     * Disabled by default.
+     * @type string
+     */
+    public $defaultLevel = null;
+
+    /**
+     * Lists of fixes used by getFixesForLevel().
+     * Format is:
+     *      HTMLModule_Tidy->fixesForLevel[$level] = array('fix-1', 'fix-2');
+     * @type array
+     */
+    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.
+     * @param HTMLPurifier_Config $config
+     * @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 string $level level identifier, see $levels for valid values
+     * @return array 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.
+     * @param array $fixes
+     */
+    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 array $fixes 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.
+     * @return array
+     */
+    public function makeFixes()
+    {
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.php
new file mode 100644 (file)
index 0000000..a995161
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * Name is deprecated, but allowed in strict doctypes, so onl
+ */
+class HTMLPurifier_HTMLModule_Tidy_Name extends HTMLPurifier_HTMLModule_Tidy
+{
+    /**
+     * @type string
+     */
+    public $name = 'Tidy_Name';
+
+    /**
+     * @type string
+     */
+    public $defaultLevel = 'heavy';
+
+    /**
+     * @return array
+     */
+    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@name'] =
+        $r['a@name'] = new HTMLPurifier_AttrTransform_Name();
+        return $r;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php
new file mode 100644 (file)
index 0000000..3326438
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+class HTMLPurifier_HTMLModule_Tidy_Proprietary extends HTMLPurifier_HTMLModule_Tidy
+{
+
+    /**
+     * @type string
+     */
+    public $name = 'Tidy_Proprietary';
+
+    /**
+     * @type string
+     */
+    public $defaultLevel = 'light';
+
+    /**
+     * @return array
+     */
+    public function makeFixes()
+    {
+        $r = array();
+        $r['table@background'] = new HTMLPurifier_AttrTransform_Background();
+        $r['td@background']    = new HTMLPurifier_AttrTransform_Background();
+        $r['th@background']    = new HTMLPurifier_AttrTransform_Background();
+        $r['tr@background']    = new HTMLPurifier_AttrTransform_Background();
+        $r['thead@background'] = new HTMLPurifier_AttrTransform_Background();
+        $r['tfoot@background'] = new HTMLPurifier_AttrTransform_Background();
+        $r['tbody@background'] = new HTMLPurifier_AttrTransform_Background();
+        $r['table@height']     = new HTMLPurifier_AttrTransform_Length('height');
+        return $r;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Strict.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Strict.php
new file mode 100644 (file)
index 0000000..803c44f
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+class HTMLPurifier_HTMLModule_Tidy_Strict extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
+{
+    /**
+     * @type string
+     */
+    public $name = 'Tidy_Strict';
+
+    /**
+     * @type string
+     */
+    public $defaultLevel = 'light';
+
+    /**
+     * @return array
+     */
+    public function makeFixes()
+    {
+        $r = parent::makeFixes();
+        $r['blockquote#content_model_type'] = 'strictblockquote';
+        return $r;
+    }
+
+    /**
+     * @type bool
+     */
+    public $defines_child_def = true;
+
+    /**
+     * @param HTMLPurifier_ElementDef $def
+     * @return HTMLPurifier_ChildDef_StrictBlockquote
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php
new file mode 100644 (file)
index 0000000..c095ad9
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+class HTMLPurifier_HTMLModule_Tidy_Transitional extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
+{
+    /**
+     * @type string
+     */
+    public $name = 'Tidy_Transitional';
+
+    /**
+     * @type string
+     */
+    public $defaultLevel = 'heavy';
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php
new file mode 100644 (file)
index 0000000..3ecddc4
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+class HTMLPurifier_HTMLModule_Tidy_XHTML extends HTMLPurifier_HTMLModule_Tidy
+{
+    /**
+     * @type string
+     */
+    public $name = 'Tidy_XHTML';
+
+    /**
+     * @type string
+     */
+    public $defaultLevel = 'medium';
+
+    /**
+     * @return array
+     */
+    public function makeFixes()
+    {
+        $r = array();
+        $r['@lang'] = new HTMLPurifier_AttrTransform_Lang();
+        return $r;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php
new file mode 100644 (file)
index 0000000..c4f16a4
--- /dev/null
@@ -0,0 +1,179 @@
+<?php
+
+class HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 extends HTMLPurifier_HTMLModule_Tidy
+{
+
+    /**
+     * @return array
+     */
+    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@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@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@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@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@align'] =
+        $r['h2@align'] =
+        $r['h3@align'] =
+        $r['h4@align'] =
+        $r['h5@align'] =
+        $r['h6@align'] =
+        $r['p@align'] =
+        $r['div@align'] =
+            new HTMLPurifier_AttrTransform_EnumToCSS('align', $align_lookup);
+
+        // @bgcolor for table, tr, td, th ---------------------------------
+        $r['table@bgcolor'] =
+        $r['td@bgcolor'] =
+        $r['th@bgcolor'] =
+            new HTMLPurifier_AttrTransform_BgColor();
+
+        // @border for img ------------------------------------------------
+        $r['img@border'] = new HTMLPurifier_AttrTransform_Border();
+
+        // @clear for br --------------------------------------------------
+        $r['br@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@height'] =
+        $r['th@height'] =
+            new HTMLPurifier_AttrTransform_Length('height');
+
+        // @hspace for img ------------------------------------------------
+        $r['img@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@noshade'] =
+            new HTMLPurifier_AttrTransform_BoolToCSS(
+                'noshade',
+                'color:#808080;background-color:#808080;border:0;'
+            );
+
+        // @nowrap for td, th ---------------------------------------------
+        $r['td@nowrap'] =
+        $r['th@nowrap'] =
+            new HTMLPurifier_AttrTransform_BoolToCSS(
+                'nowrap',
+                'white-space:nowrap;'
+            );
+
+        // @size for hr  --------------------------------------------------
+        $r['hr@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@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ul_types);
+        $r['ol@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ol_types, true);
+        $r['li@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $li_types, true);
+
+        // @vspace for img ------------------------------------------------
+        $r['img@vspace'] = new HTMLPurifier_AttrTransform_ImgSpace('vspace');
+
+        // @width for hr, td, th ------------------------------------------
+        $r['td@width'] =
+        $r['th@width'] =
+        $r['hr@width'] = new HTMLPurifier_AttrTransform_Length('width');
+
+        return $r;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php
new file mode 100644 (file)
index 0000000..01dbe9d
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+class HTMLPurifier_HTMLModule_XMLCommonAttributes extends HTMLPurifier_HTMLModule
+{
+    /**
+     * @type string
+     */
+    public $name = 'XMLCommonAttributes';
+
+    /**
+     * @type array
+     */
+    public $attr_collections = array(
+        'Lang' => array(
+            'xml:lang' => 'LanguageCode',
+        )
+    );
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.php
new file mode 100644 (file)
index 0000000..f3a17cb
--- /dev/null
@@ -0,0 +1,459 @@
+<?php
+
+class HTMLPurifier_HTMLModuleManager
+{
+
+    /**
+     * @type HTMLPurifier_DoctypeRegistry
+     */
+    public $doctypes;
+
+    /**
+     * Instance of current doctype.
+     * @type string
+     */
+    public $doctype;
+
+    /**
+     * @type HTMLPurifier_AttrTypes
+     */
+    public $attrTypes;
+
+    /**
+     * Active instances of modules for the specified doctype are
+     * indexed, by name, in this array.
+     * @type HTMLPurifier_HTMLModule[]
+     */
+    public $modules = array();
+
+    /**
+     * Array of recognized HTMLPurifier_HTMLModule 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.
+     * @type HTMLPurifier_HTMLModule[]
+     */
+    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.
+     * @type HTMLPurifier_HTMLModule[]
+     */
+    public $userModules = array();
+
+    /**
+     * Associative array of element name to list of modules that have
+     * definitions for the element; this array is dynamically filled.
+     * @type array
+     */
+    public $elementLookup = array();
+
+    /**
+     * List of prefixes we should use for registering small names.
+     * @type array
+     */
+    public $prefixes = array('HTMLPurifier_HTMLModule_');
+
+    /**
+     * @type HTMLPurifier_ContentSets
+     */
+    public $contentSets;
+
+    /**
+     * @type HTMLPurifier_AttrCollections
+     */
+    public $attrCollections;
+
+    /**
+     * If set to true, unsafe elements and attributes will be allowed.
+     * @type bool
+     */
+    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 HTMLPurifier_Config $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 $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 string $name Name of element
+     * @param bool $trusted Boolean trusted overriding parameter: set to true
+     *                 if you want the full version of an element
+     * @return HTMLPurifier_ElementDef 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/library/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.php
new file mode 100644 (file)
index 0000000..65c902c
--- /dev/null
@@ -0,0 +1,57 @@
+<?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 HTMLPurifier_Config $config Instance of HTMLPurifier_Config
+     * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context
+     * @return HTMLPurifier_IDAccumulator 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 string $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/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php
new file mode 100644 (file)
index 0000000..5060eef
--- /dev/null
@@ -0,0 +1,281 @@
+<?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.
+     * @type string
+     */
+    public $name;
+
+    /**
+     * @type HTMLPurifier_HTMLDefinition
+     */
+    protected $htmlDefinition;
+
+    /**
+     * Reference to CurrentNesting variable in Context. This is an array
+     * list of tokens that we are currently "inside"
+     * @type array
+     */
+    protected $currentNesting;
+
+    /**
+     * Reference to current token.
+     * @type HTMLPurifier_Token
+     */
+    protected $currentToken;
+
+    /**
+     * Reference to InputZipper variable in Context.
+     * @type HTMLPurifier_Zipper
+     */
+    protected $inputZipper;
+
+    /**
+     * 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')
+     * @type array
+     */
+    public $needed = array();
+
+    /**
+     * Number of elements to rewind backwards (relative).
+     * @type bool|int
+     */
+    protected $rewindOffset = 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.
+     * @param bool|int $offset
+     * @warning HTML Purifier will prevent you from fast-forwarding with this
+     *          function.
+     */
+    public function rewindOffset($offset)
+    {
+        $this->rewindOffset = $offset;
+    }
+
+    /**
+     * Retrieves rewind offset, and then unsets it.
+     * @return bool|int
+     */
+    public function getRewindOffset()
+    {
+        $r = $this->rewindOffset;
+        $this->rewindOffset = 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 HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool|string 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->currentToken   =& $context->get('CurrentToken');
+        $this->inputZipper    =& $context->get('InputZipper');
+        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 HTMLPurifier_Config $config
+     * @return bool|string 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 string $name Name of element to test for
+     * @return bool 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 int $i Current integer index variable for inputTokens
+     * @param HTMLPurifier_Token $current Current token variable.
+     *          Do NOT use $token, as that variable is also a reference
+     * @return bool
+     */
+    protected function forward(&$i, &$current)
+    {
+        if ($i === null) {
+            $i = count($this->inputZipper->back) - 1;
+        } else {
+            $i--;
+        }
+        if ($i < 0) {
+            return false;
+        }
+        $current = $this->inputZipper->back[$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.
+     * @param int $i Current integer index variable for inputTokens
+     * @param HTMLPurifier_Token $current Current token variable.
+     *          Do NOT use $token, as that variable is also a reference
+     * @param int $nesting
+     * @return bool
+     */
+    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 int $i Current integer index variable for inputTokens
+     * @param HTMLPurifier_Token $current Current token variable.
+     *          Do NOT use $token, as that variable is also a reference
+     * @return bool
+     */
+    protected function backward(&$i, &$current)
+    {
+        if ($i === null) {
+            $i = count($this->inputZipper->front) - 1;
+        } else {
+            $i--;
+        }
+        if ($i < 0) {
+            return false;
+        }
+        $current = $this->inputZipper->front[$i];
+        return true;
+    }
+
+    /**
+     * 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
+     * @param HTMLPurifier_Token $token Current token variable.
+     * @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/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.php
new file mode 100644 (file)
index 0000000..4afdd12
--- /dev/null
@@ -0,0 +1,356 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $name = 'AutoParagraph';
+
+    /**
+     * @type array
+     */
+    public $needed = array('p');
+
+    /**
+     * @return HTMLPurifier_Token_Start
+     */
+    private function _pStart()
+    {
+        $par = new HTMLPurifier_Token_Start('p');
+        $par->armor['MakeWellFormed_TagClosedError'] = true;
+        return $par;
+    }
+
+    /**
+     * @param HTMLPurifier_Token_Text $token
+     */
+    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
+            //                  ------------
+        }
+    }
+
+    /**
+     * @param HTMLPurifier_Token $token
+     */
+    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 string $data String text data that will be processed
+     *    into paragraphs
+     * @param HTMLPurifier_Token[] $result Reference to array of tokens that the
+     *    tags will be appended onto
+     */
+    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)
+     * @param HTMLPurifier_Token $token
+     * @return bool
+     */
+    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.
+     * @return bool
+     */
+    private function _pLookAhead()
+    {
+        if ($this->currentToken instanceof HTMLPurifier_Token_Start) {
+            $nesting = 1;
+        } else {
+            $nesting = 0;
+        }
+        $ok = false;
+        $i = null;
+        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
+     * @param HTMLPurifier_Token $current
+     * @return bool
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php
new file mode 100644 (file)
index 0000000..c19b1bc
--- /dev/null
@@ -0,0 +1,40 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $name = 'DisplayLinkURI';
+
+    /**
+     * @type array
+     */
+    public $needed = array('a');
+
+    /**
+     * @param $token
+     */
+    public function handleElement(&$token)
+    {
+    }
+
+    /**
+     * @param HTMLPurifier_Token $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/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.php
new file mode 100644 (file)
index 0000000..069708c
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Injector that converts http, https and ftp text URLs to actual links.
+ */
+class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector
+{
+    /**
+     * @type string
+     */
+    public $name = 'Linkify';
+
+    /**
+     * @type array
+     */
+    public $needed = array('a' => array('href'));
+
+    /**
+     * @param HTMLPurifier_Token $token
+     */
+    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\'",<>()]+)#Su', $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/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php
new file mode 100644 (file)
index 0000000..cb9046f
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * Injector that converts configuration directive syntax %Namespace.Directive
+ * to links
+ */
+class HTMLPurifier_Injector_PurifierLinkify extends HTMLPurifier_Injector
+{
+    /**
+     * @type string
+     */
+    public $name = 'PurifierLinkify';
+
+    /**
+     * @type string
+     */
+    public $docURL;
+
+    /**
+     * @type array
+     */
+    public $needed = array('a' => array('href'));
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return string
+     */
+    public function prepare($config, $context)
+    {
+        $this->docURL = $config->get('AutoFormat.PurifierLinkify.DocURL');
+        return parent::prepare($config, $context);
+    }
+
+    /**
+     * @param HTMLPurifier_Token $token
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php
new file mode 100644 (file)
index 0000000..01353ff
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+
+class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector
+{
+    /**
+     * @type HTMLPurifier_Context
+     */
+    private $context;
+
+    /**
+     * @type HTMLPurifier_Config
+     */
+    private $config;
+
+    /**
+     * @type HTMLPurifier_AttrValidator
+     */
+    private $attrValidator;
+
+    /**
+     * @type bool
+     */
+    private $removeNbsp;
+
+    /**
+     * @type bool
+     */
+    private $removeNbspExceptions;
+
+    /**
+     * Cached contents of %AutoFormat.RemoveEmpty.Predicate
+     * @type array
+     */
+    private $exclude;
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return void
+     */
+    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->exclude = $config->get('AutoFormat.RemoveEmpty.Predicate');
+        $this->attrValidator = new HTMLPurifier_AttrValidator();
+    }
+
+    /**
+     * @param HTMLPurifier_Token $token
+     */
+    public function handleElement(&$token)
+    {
+        if (!$token instanceof HTMLPurifier_Token_Start) {
+            return;
+        }
+        $next = false;
+        $deleted = 1; // the current tag
+        for ($i = count($this->inputZipper->back) - 1; $i >= 0; $i--, $deleted++) {
+            $next = $this->inputZipper->back[$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)) {
+            $this->attrValidator->validateToken($token, $this->config, $this->context);
+            $token->armor['ValidateAttributes'] = true;
+            if (isset($this->exclude[$token->name])) {
+                $r = true;
+                foreach ($this->exclude[$token->name] as $elem) {
+                    if (!isset($token->attr[$elem])) $r = false;
+                }
+                if ($r) return;
+            }
+            if (isset($token->attr['id']) || isset($token->attr['name'])) {
+                return;
+            }
+            $token = $deleted + 1;
+            for ($b = 0, $c = count($this->inputZipper->front); $b < $c; $b++) {
+                $prev = $this->inputZipper->front[$b];
+                if ($prev instanceof HTMLPurifier_Token_Text && $prev->is_whitespace) {
+                    continue;
+                }
+                break;
+            }
+            // This is safe because we removed the token that triggered this.
+            $this->rewindOffset($b+$deleted);
+            return;
+        }
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php
new file mode 100644 (file)
index 0000000..9ee7aa8
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * Injector that removes spans with no attributes
+ */
+class HTMLPurifier_Injector_RemoveSpansWithoutAttributes extends HTMLPurifier_Injector
+{
+    /**
+     * @type string
+     */
+    public $name = 'RemoveSpansWithoutAttributes';
+
+    /**
+     * @type array
+     */
+    public $needed = array('span');
+
+    /**
+     * @type HTMLPurifier_AttrValidator
+     */
+    private $attrValidator;
+
+    /**
+     * Used by AttrValidator.
+     * @type HTMLPurifier_Config
+     */
+    private $config;
+
+    /**
+     * @type HTMLPurifier_Context
+     */
+    private $context;
+
+    public function prepare($config, $context)
+    {
+        $this->attrValidator = new HTMLPurifier_AttrValidator();
+        $this->config = $config;
+        $this->context = $context;
+        return parent::prepare($config, $context);
+    }
+
+    /**
+     * @param HTMLPurifier_Token $token
+     */
+    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;
+        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;
+        }
+    }
+
+    /**
+     * @param HTMLPurifier_Token $token
+     */
+    public function handleEnd(&$token)
+    {
+        if ($token->markForDeletion) {
+            $token = false;
+        }
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.php
new file mode 100644 (file)
index 0000000..3d17e07
--- /dev/null
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * Adds important param elements to inside of object in order to make
+ * things safe.
+ */
+class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector
+{
+    /**
+     * @type string
+     */
+    public $name = 'SafeObject';
+
+    /**
+     * @type array
+     */
+    public $needed = array('object', 'param');
+
+    /**
+     * @type array
+     */
+    protected $objectStack = array();
+
+    /**
+     * @type array
+     */
+    protected $paramStack = array();
+
+    /**
+     * Keep this synchronized with AttrTransform/SafeParam.php.
+     * @type array
+     */
+    protected $addParam = array(
+        'allowScriptAccess' => 'never',
+        'allowNetworking' => 'internal',
+    );
+
+    /**
+     * @type array
+     */
+    protected $allowedParam = array(
+        'wmode' => true,
+        'movie' => true,
+        'flashvars' => true,
+        'src' => true,
+        'allowFullScreen' => true, // if omitted, assume to be 'false'
+    );
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return void
+     */
+    public function prepare($config, $context)
+    {
+        parent::prepare($config, $context);
+    }
+
+    /**
+     * @param HTMLPurifier_Token $token
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/Language.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Language.php
new file mode 100644 (file)
index 0000000..65277dd
--- /dev/null
@@ -0,0 +1,204 @@
+<?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.
+     * @type string
+     */
+    public $code = 'en';
+
+    /**
+     * Fallback language code.
+     * @type bool|string
+     */
+    public $fallback = false;
+
+    /**
+     * Array of localizable messages.
+     * @type array
+     */
+    public $messages = array();
+
+    /**
+     * Array of localizable error codes.
+     * @type array
+     */
+    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.
+     * @type bool
+     */
+    public $error = false;
+
+    /**
+     * Has the language object been loaded yet?
+     * @type bool
+     * @todo Make it private, fix usage in HTMLPurifier_LanguageTest
+     */
+    public $_loaded = false;
+
+    /**
+     * @type HTMLPurifier_Config
+     */
+    protected $config;
+
+    /**
+     * @type HTMLPurifier_Context
+     */
+    protected $context;
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $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 string $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 $int 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
+     * @param array $array
+     * @return string
+     */
+    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 string $key string identifier of message
+     * @param array $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/library/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php
new file mode 100644 (file)
index 0000000..8828f5c
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+// private class for unit testing
+
+class HTMLPurifier_Language_en_x_test extends HTMLPurifier_Language
+{
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-test.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-test.php
new file mode 100644 (file)
index 0000000..1c046f3
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-testmini.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-testmini.php
new file mode 100644 (file)
index 0000000..806c83f
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en.php
new file mode 100644 (file)
index 0000000..c7f197e
--- /dev/null
@@ -0,0 +1,55 @@
+<?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 &lt;',
+    '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/library/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php
new file mode 100644 (file)
index 0000000..4e35272
--- /dev/null
@@ -0,0 +1,209 @@
+<?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
+     * @type array
+     */
+    public $cache;
+
+    /**
+     * Valid keys in the HTMLPurifier_Language object. Designates which
+     * variables to slurp out of a message file.
+     * @type array
+     */
+    public $keys = array('fallback', 'messages', 'errorNames');
+
+    /**
+     * Instance to validate language codes.
+     * @type HTMLPurifier_AttrDef_Lang
+     *
+     */
+    protected $validator;
+
+    /**
+     * Cached copy of dirname(__FILE__), directory of current file without
+     * trailing slash.
+     * @type string
+     */
+    protected $dir;
+
+    /**
+     * Keys whose contents are a hash map and can be merged.
+     * @type array
+     */
+    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 HTMLPurifier_LanguageFactory $prototype Optional prototype to overload sole instance with,
+     *                   or bool true to reset to default factory.
+     * @return HTMLPurifier_LanguageFactory
+     */
+    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 HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @param bool|string $code Code to override configuration with. Private parameter.
+     * @return HTMLPurifier_Language
+     */
+    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 string $code language code
+     * @return string|bool
+     */
+    public function getFallbackFor($code)
+    {
+        $this->loadLanguage($code);
+        return $this->cache[$code]['fallback'];
+    }
+
+    /**
+     * Loads language into the cache, handles message file and fallbacks
+     * @param string $code 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/library/ezyang/htmlpurifier/library/HTMLPurifier/Length.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Length.php
new file mode 100644 (file)
index 0000000..bbfbe66
--- /dev/null
@@ -0,0 +1,160 @@
+<?php
+
+/**
+ * Represents a measurable length, with a string numeric magnitude
+ * and a unit. This object is immutable.
+ */
+class HTMLPurifier_Length
+{
+
+    /**
+     * String numeric magnitude.
+     * @type string
+     */
+    protected $n;
+
+    /**
+     * String unit. False is permitted if $n = 0.
+     * @type string|bool
+     */
+    protected $unit;
+
+    /**
+     * Whether or not this length is valid. Null if not calculated yet.
+     * @type bool
+     */
+    protected $isValid;
+
+    /**
+     * Array Lookup array of units recognized by CSS 2.1
+     * @type array
+     */
+    protected static $allowedUnits = array(
+        'em' => true, 'ex' => true, 'px' => true, 'in' => true,
+        'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true
+    );
+
+    /**
+     * @param string $n Magnitude
+     * @param bool|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'
+     * @return HTMLPurifier_Length
+     * @warning Does not perform validation.
+     */
+    public static 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.
+     * @return bool
+     */
+    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.
+     * @return string
+     */
+    public function toString()
+    {
+        if (!$this->isValid()) {
+            return false;
+        }
+        return $this->n . $this->unit;
+    }
+
+    /**
+     * Retrieves string numeric magnitude.
+     * @return string
+     */
+    public function getN()
+    {
+        return $this->n;
+    }
+
+    /**
+     * Retrieves string unit.
+     * @return string
+     */
+    public function getUnit()
+    {
+        return $this->unit;
+    }
+
+    /**
+     * Returns true if this length unit is valid.
+     * @return bool
+     */
+    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.
+     * @param HTMLPurifier_Length $l
+     * @return int
+     * @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/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php
new file mode 100644 (file)
index 0000000..4373262
--- /dev/null
@@ -0,0 +1,357 @@
+<?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 HTMLPurifier_Config $config
+     * @return HTMLPurifier_Lexer
+     * @throws HTMLPurifier_Exception
+     */
+    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.
+     * @type array
+     */
+    protected $_special_entity2str =
+        array(
+            '&quot;' => '"',
+            '&amp;' => '&',
+            '&lt;' => '<',
+            '&gt;' => '>',
+            '&#39;' => "'",
+            '&#039;' => "'",
+            '&#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 String character data to be parsed.
+     * @return string 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, '&amp;');
+        $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.
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @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 $string HTML string to process.
+     * @return string 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>
+     * @param string $string HTML string to process.
+     * @return string HTML with CDATA sections escaped.
+     */
+    protected static function escapeCommentedCDATA($string)
+    {
+        return preg_replace_callback(
+            '#<!--//--><!\[CDATA\[//><!--(.+?)//--><!\]\]>#s',
+            array('HTMLPurifier_Lexer', 'CDATACallback'),
+            $string
+        );
+    }
+
+    /**
+     * Special Internet Explorer conditional comments should be removed.
+     * @param string $string HTML string to process.
+     * @return string HTML with conditional comments 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.
+     * @param array $matches PCRE matches array, with index 0 the entire match
+     *                  and 1 the inside of the CDATA section.
+     * @return string 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.
+     * @param string $html HTML.
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return string
+     * @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/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php
new file mode 100644 (file)
index 0000000..b818192
--- /dev/null
@@ -0,0 +1,279 @@
+<?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
+{
+
+    /**
+     * @type HTMLPurifier_TokenFactory
+     */
+    private $factory;
+
+    public function __construct()
+    {
+        // setup the factory
+        parent::__construct();
+        $this->factory = new HTMLPurifier_TokenFactory();
+    }
+
+    /**
+     * @param string $html
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return HTMLPurifier_Token[]
+     */
+    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", '&lt;\\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>
+            $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 DOMNode $node DOMNode to be tokenized.
+     * @param HTMLPurifier_Token[] $tokens   Array-list of already tokenized tokens.
+     * @return HTMLPurifier_Token of node appended to previously passed tokens.
+     */
+    protected function tokenizeDOM($node, &$tokens)
+    {
+        $level = 0;
+        $nodes = array($level => new HTMLPurifier_Queue(array($node)));
+        $closingNodes = array();
+        do {
+            while (!$nodes[$level]->isEmpty()) {
+                $node = $nodes[$level]->shift(); // 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] = new HTMLPurifier_Queue();
+                    foreach ($node->childNodes as $childNode) {
+                        $nodes[$level]->push($childNode);
+                    }
+                }
+            }
+            $level--;
+            if ($level && isset($closingNodes[$level])) {
+                while ($node = array_pop($closingNodes[$level])) {
+                    $this->createEndNode($node, $tokens);
+                }
+            }
+        } while ($level > 0);
+    }
+
+    /**
+     * @param DOMNode $node DOMNode to be tokenized.
+     * @param HTMLPurifier_Token[] $tokens   Array-list of already tokenized tokens.
+     * @param bool $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.
+     * @return bool if the token needs an endtoken
+     * @todo data and tagName properties don't seem to exist in DOMNode?
+     */
+    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 ($node->nodeType !== XML_ELEMENT_NODE) {
+            // not-well tested: there may be other nodes we have to grab
+            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;
+        }
+    }
+
+    /**
+     * @param DOMNode $node
+     * @param HTMLPurifier_Token[] $tokens
+     */
+    protected function createEndNode($node, &$tokens)
+    {
+        $tokens[] = $this->factory->createEnd($node->tagName);
+    }
+
+
+    /**
+     * Converts a DOMNamedNodeMap of DOMAttr objects into an assoc array.
+     *
+     * @param DOMNamedNodeMap $node_map DOMNamedNodeMap of DOMAttr objects.
+     * @return array 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
+     * @param int $errno
+     * @param string $errstr
+     */
+    public function muteErrorHandler($errno, $errstr)
+    {
+    }
+
+    /**
+     * Callback function for undoing escaping of stray angled brackets
+     * in comments
+     * @param array $matches
+     * @return string
+     */
+    public function callbackUndoCommentSubst($matches)
+    {
+        return '<!--' . strtr($matches[1], array('&amp;' => '&', '&lt;' => '<')) . $matches[2];
+    }
+
+    /**
+     * Callback function that entity-izes ampersands in comments so that
+     * callbackUndoCommentSubst doesn't clobber them
+     * @param array $matches
+     * @return string
+     */
+    public function callbackArmorCommentEntities($matches)
+    {
+        return '<!--' . str_replace('&', '&amp;', $matches[1]) . $matches[2];
+    }
+
+    /**
+     * Wraps an HTML fragment in the necessary HTML
+     * @param string $html
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return string
+     */
+    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>' . $html . '</body></html>';
+        return $ret;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DirectLex.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DirectLex.php
new file mode 100644 (file)
index 0000000..746b6e3
--- /dev/null
@@ -0,0 +1,539 @@
+<?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
+{
+    /**
+     * @type bool
+     */
+    public $tracksLineNumbers = true;
+
+    /**
+     * Whitespace characters for str(c)spn.
+     * @type string
+     */
+    protected $_whitespace = "\x20\x09\x0D\x0A";
+
+    /**
+     * Callback function for script CDATA fudge
+     * @param array $matches, in form of array(opening tag, contents, closing tag)
+     * @return string
+     */
+    protected function scriptCallback($matches)
+    {
+        return $matches[1] . htmlspecialchars($matches[2], ENT_COMPAT, 'UTF-8') . $matches[3];
+    }
+
+    /**
+     * @param String $html
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array|HTMLPurifier_Token[]
+     */
+    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
+     * @param string $haystack
+     * @param string $needle
+     * @param int $offset
+     * @param int $length
+     * @return int
+     */
+    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 $string Inside of tag excluding name.
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array 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 .= ' ';
+
+        $old_cursor = -1;
+        while ($cursor < $size) {
+            if ($old_cursor >= $cursor) {
+                throw new Exception("Infinite loop detected");
+            }
+            $old_cursor = $cursor;
+
+            $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 += 1 + 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/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php
new file mode 100644 (file)
index 0000000..ff4fa21
--- /dev/null
@@ -0,0 +1,4787 @@
+<?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 throws an exception.
+ */
+
+class HTMLPurifier_Lexer_PH5P extends HTMLPurifier_Lexer_DOMLex
+{
+    /**
+     * @param string $html
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return HTMLPurifier_Token[]
+     */
+    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>
+            ,
+            $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/library/ezyang/htmlpurifier/library/HTMLPurifier/Node.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Node.php
new file mode 100644 (file)
index 0000000..3995fec
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * Abstract base node class that all others inherit from.
+ *
+ * Why do we not use the DOM extension?  (1) It is not always available,
+ * (2) it has funny constraints on the data it can represent,
+ * whereas we want a maximally flexible representation, and (3) its
+ * interface is a bit cumbersome.
+ */
+abstract class HTMLPurifier_Node
+{
+    /**
+     * Line number of the start token in the source document
+     * @type int
+     */
+    public $line;
+
+    /**
+     * Column number of the start token in the source document. Null if unknown.
+     * @type int
+     */
+    public $col;
+
+    /**
+     * Lookup array of processing that this token is exempt from.
+     * Currently, valid values are "ValidateAttributes".
+     * @type array
+     */
+    public $armor = array();
+
+    /**
+     * When true, this node should be ignored as non-existent.
+     *
+     * Who is responsible for ignoring dead nodes?  FixNesting is
+     * responsible for removing them before passing on to child
+     * validators.
+     */
+    public $dead = false;
+
+    /**
+     * Returns a pair of start and end tokens, where the end token
+     * is null if it is not necessary. Does not include children.
+     * @type array
+     */
+    abstract public function toTokenPair();
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Comment.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Comment.php
new file mode 100644 (file)
index 0000000..38ba193
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * Concrete comment node class.
+ */
+class HTMLPurifier_Node_Comment extends HTMLPurifier_Node
+{
+    /**
+     * Character data within comment.
+     * @type string
+     */
+    public $data;
+
+    /**
+     * @type bool
+     */
+    public $is_whitespace = true;
+
+    /**
+     * Transparent constructor.
+     *
+     * @param string $data String comment data.
+     * @param int $line
+     * @param int $col
+     */
+    public function __construct($data, $line = null, $col = null)
+    {
+        $this->data = $data;
+        $this->line = $line;
+        $this->col = $col;
+    }
+
+    public function toTokenPair() {
+        return array(new HTMLPurifier_Token_Comment($this->data, $this->line, $this->col), null);
+    }
+}
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php
new file mode 100644 (file)
index 0000000..6cbf56d
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Concrete element node class.
+ */
+class HTMLPurifier_Node_Element extends HTMLPurifier_Node
+{
+    /**
+     * 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.
+     * @type string
+     */
+    public $name;
+
+    /**
+     * Associative array of the node's attributes.
+     * @type array
+     */
+    public $attr = array();
+
+    /**
+     * List of child elements.
+     * @type array
+     */
+    public $children = array();
+
+    /**
+     * Does this use the <a></a> form or the </a> form, i.e.
+     * is it a pair of start/end tokens or an empty token.
+     * @bool
+     */
+    public $empty = false;
+
+    public $endCol = null, $endLine = null, $endArmor = array();
+
+    public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) {
+        $this->name = $name;
+        $this->attr = $attr;
+        $this->line = $line;
+        $this->col = $col;
+        $this->armor = $armor;
+    }
+
+    public function toTokenPair() {
+        // XXX inefficiency here, normalization is not necessary
+        if ($this->empty) {
+            return array(new HTMLPurifier_Token_Empty($this->name, $this->attr, $this->line, $this->col, $this->armor), null);
+        } else {
+            $start = new HTMLPurifier_Token_Start($this->name, $this->attr, $this->line, $this->col, $this->armor);
+            $end = new HTMLPurifier_Token_End($this->name, array(), $this->endLine, $this->endCol, $this->endArmor);
+            //$end->start = $start;
+            return array($start, $end);
+        }
+    }
+}
+
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php
new file mode 100644 (file)
index 0000000..aec9166
--- /dev/null
@@ -0,0 +1,54 @@
+<?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_Node_Text extends HTMLPurifier_Node
+{
+
+    /**
+     * PCDATA tag name compatible with DTD, see
+     * HTMLPurifier_ChildDef_Custom for details.
+     * @type string
+     */
+    public $name = '#PCDATA';
+
+    /**
+     * @type string
+     */
+    public $data;
+    /**< Parsed character data of text. */
+
+    /**
+     * @type bool
+     */
+    public $is_whitespace;
+
+    /**< Bool indicating if node is whitespace. */
+
+    /**
+     * Constructor, accepts data and determines if it is whitespace.
+     * @param string $data String parsed character data.
+     * @param int $line
+     * @param int $col
+     */
+    public function __construct($data, $is_whitespace, $line = null, $col = null)
+    {
+        $this->data = $data;
+        $this->is_whitespace = $is_whitespace;
+        $this->line = $line;
+        $this->col = $col;
+    }
+
+    public function toTokenPair() {
+        return array(new HTMLPurifier_Token_Text($this->data, $this->line, $this->col), null);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php
new file mode 100644 (file)
index 0000000..18c8bbb
--- /dev/null
@@ -0,0 +1,111 @@
+<?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().
+     * @type array
+     */
+    protected $preserve = array();
+
+    /**
+     * String of characters that should be preserved while using encode().
+     * @param bool $preserve
+     */
+    public function __construct($preserve = false)
+    {
+        // unreserved letters, ought to const-ify
+        for ($i = 48; $i <= 57; $i++) { // digits
+            $this->preserve[$i] = true;
+        }
+        for ($i = 65; $i <= 90; $i++) { // upper-case
+            $this->preserve[$i] = true;
+        }
+        for ($i = 97; $i <= 122; $i++) { // lower-case
+            $this->preserve[$i] = true;
+        }
+        $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 String to be encoded
+     * @return string 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 String to normalize
+     * @return string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php
new file mode 100644 (file)
index 0000000..549e4ce
--- /dev/null
@@ -0,0 +1,218 @@
+<?php
+
+// OUT OF DATE, NEEDS UPDATING!
+// USE XMLWRITER!
+
+class HTMLPurifier_Printer
+{
+
+    /**
+     * For HTML generation convenience funcs.
+     * @type HTMLPurifier_Generator
+     */
+    protected $generator;
+
+    /**
+     * For easy access.
+     * @type HTMLPurifier_Config
+     */
+    protected $config;
+
+    /**
+     * Initialize $generator.
+     */
+    public function __construct()
+    {
+    }
+
+    /**
+     * Give generator necessary configuration if possible
+     * @param HTMLPurifier_Config $config
+     */
+    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 string $tag Tag name
+     * @param array $attr Attribute array
+     * @return string
+     */
+    protected function start($tag, $attr = array())
+    {
+        return $this->generator->generateFromToken(
+            new HTMLPurifier_Token_Start($tag, $attr ? $attr : array())
+        );
+    }
+
+    /**
+     * Returns an end tag
+     * @param string $tag Tag name
+     * @return string
+     */
+    protected function end($tag)
+    {
+        return $this->generator->generateFromToken(
+            new HTMLPurifier_Token_End($tag)
+        );
+    }
+
+    /**
+     * Prints a complete element with content inside
+     * @param string $tag Tag name
+     * @param string $contents Element contents
+     * @param array $attr Tag attributes
+     * @param bool $escape whether or not to escape contents
+     * @return string
+     */
+    protected function element($tag, $contents, $attr = array(), $escape = true)
+    {
+        return $this->start($tag, $attr) .
+            ($escape ? $this->escape($contents) : $contents) .
+            $this->end($tag);
+    }
+
+    /**
+     * @param string $tag
+     * @param array $attr
+     * @return string
+     */
+    protected function elementEmpty($tag, $attr = array())
+    {
+        return $this->generator->generateFromToken(
+            new HTMLPurifier_Token_Empty($tag, $attr)
+        );
+    }
+
+    /**
+     * @param string $text
+     * @return string
+     */
+    protected function text($text)
+    {
+        return $this->generator->generateFromToken(
+            new HTMLPurifier_Token_Text($text)
+        );
+    }
+
+    /**
+     * Prints a simple key/value row in a table.
+     * @param string $name Key
+     * @param mixed $value Value
+     * @return string
+     */
+    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 String to escape
+     * @return string
+     */
+    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 string[] $array List of strings
+     * @param bool $polite Bool whether or not to add an end before the last
+     * @return string
+     */
+    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 object $obj Object to determine class of
+     * @param string $sec_prefix Further prefix to remove
+     * @return string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php
new file mode 100644 (file)
index 0000000..29505fe
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+class HTMLPurifier_Printer_CSSDefinition extends HTMLPurifier_Printer
+{
+    /**
+     * @type HTMLPurifier_CSSDefinition
+     */
+    protected $def;
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css b/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css
new file mode 100644 (file)
index 0000000..3ff1a88
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js b/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js
new file mode 100644 (file)
index 0000000..cba00c9
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php
new file mode 100644 (file)
index 0000000..36100ce
--- /dev/null
@@ -0,0 +1,447 @@
+<?php
+
+/**
+ * @todo Rewrite to use Interchange objects
+ */
+class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
+{
+
+    /**
+     * Printers for specific fields.
+     * @type HTMLPurifier_Printer[]
+     */
+    protected $fields = array();
+
+    /**
+     * Documentation URL, can have fragment tagged on end.
+     * @type string
+     */
+    protected $docURL;
+
+    /**
+     * Name of form element to stuff config in.
+     * @type string
+     */
+    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.
+     * @type bool
+     */
+    protected $compress = false;
+
+    /**
+     * @param string $name Form element name for directives to be stuffed into
+     * @param string $doc_url String documentation URL, will have fragment tagged on
+     * @param bool $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 HTMLPurifier_Config|array $config Configuration object of current form state, or an array
+     *        where [0] has an HTML namespace and [1] is being rendered.
+     * @param array|bool $allowed Optional namespace(s) and directives to restrict form to.
+     * @param bool $render_controls
+     * @return string
+     */
+    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 array $directives array of directives to values
+     * @return string
+     */
+    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
+     * @type HTMLPurifier_Printer
+     */
+    protected $obj;
+
+    /**
+     * @param HTMLPurifier_Printer $obj Printer to decorate
+     */
+    public function __construct($obj)
+    {
+        parent::__construct();
+        $this->obj = $obj;
+    }
+
+    /**
+     * @param string $ns
+     * @param string $directive
+     * @param string $value
+     * @param string $name
+     * @param HTMLPurifier_Config|array $config
+     * @return string
+     */
+    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
+{
+    /**
+     * @type int
+     */
+    public $cols = 18;
+
+    /**
+     * @type int
+     */
+    public $rows = 5;
+
+    /**
+     * @param string $ns
+     * @param string $directive
+     * @param string $value
+     * @param string $name
+     * @param HTMLPurifier_Config|array $config
+     * @return string
+     */
+    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;
+                    }
+                    //TODO does this need a break?
+                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
+{
+    /**
+     * @param string $ns
+     * @param string $directive
+     * @param string $value
+     * @param string $name
+     * @param HTMLPurifier_Config|array $config
+     * @return string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php
new file mode 100644 (file)
index 0000000..5f2f2f8
--- /dev/null
@@ -0,0 +1,324 @@
+<?php
+
+class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer
+{
+
+    /**
+     * @type HTMLPurifier_HTMLDefinition, for easy access
+     */
+    protected $def;
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return string
+     */
+    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
+     * @return string
+     */
+    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
+     * @return string
+     */
+    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), null, 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
+     * @return string
+     */
+    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
+     * @return string
+     */
+    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 HTMLPurifier_ChildDef $def HTMLPurifier_ChildDef of pertinent element
+     * @return string
+     */
+    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)),
+                null,
+                0
+            );
+            $ret .= $this->end('tr');
+            $ret .= $this->start('tr');
+            $ret .= $this->element(
+                'td',
+                '<em>Inline</em>: ' .
+                $this->escape($this->listifyTagLookup($def->inline->elements)),
+                null,
+                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)),
+                null,
+                0
+            );
+        }
+        $ret .= $this->end('tr');
+        return $ret;
+    }
+
+    /**
+     * Listifies a tag lookup table.
+     * @param array $array Tag lookup array in form of array('tagname' => true)
+     * @return string
+     */
+    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 $array List of objects
+     * @return string
+     * @todo Also add information about internal state
+     */
+    protected function listifyObjectList($array)
+    {
+        ksort($array);
+        $list = array();
+        foreach ($array as $obj) {
+            $list[] = $this->getClass($obj, 'AttrTransform_');
+        }
+        return $this->listify($list);
+    }
+
+    /**
+     * Listifies a hash of attributes to AttrDef classes
+     * @param array $array Array hash in form of array('attrname' => HTMLPurifier_AttrDef)
+     * @return string
+     */
+    protected function listifyAttr($array)
+    {
+        ksort($array);
+        $list = array();
+        foreach ($array as $name => $obj) {
+            if ($obj === false) {
+                continue;
+            }
+            $list[] = "$name&nbsp;=&nbsp;<i>" . $this->getClass($obj, 'AttrDef_') . '</i>';
+        }
+        return $this->listify($list);
+    }
+
+    /**
+     * Creates a heavy header row
+     * @param string $text
+     * @param int $num
+     * @return string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php
new file mode 100644 (file)
index 0000000..189348f
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * Generic property list implementation
+ */
+class HTMLPurifier_PropertyList
+{
+    /**
+     * Internal data-structure for properties.
+     * @type array
+     */
+    protected $data = array();
+
+    /**
+     * Parent plist.
+     * @type HTMLPurifier_PropertyList
+     */
+    protected $parent;
+
+    /**
+     * Cache.
+     * @type array
+     */
+    protected $cache;
+
+    /**
+     * @param HTMLPurifier_PropertyList $parent Parent plist
+     */
+    public function __construct($parent = null)
+    {
+        $this->parent = $parent;
+    }
+
+    /**
+     * Recursively retrieves the value for a key
+     * @param string $name
+     * @throws HTMLPurifier_Exception
+     */
+    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
+     * @param string $name
+     * @param mixed $value
+     */
+    public function set($name, $value)
+    {
+        $this->data[$name] = $value;
+    }
+
+    /**
+     * Returns true if a given key exists
+     * @param string $name
+     * @return bool
+     */
+    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.
+     * @param string $name
+     */
+    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 bool $force If true, ignores the cache and regenerates the array.
+     * @return 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.
+     * @return HTMLPurifier_PropertyList
+     */
+    public function getParent()
+    {
+        return $this->parent;
+    }
+
+    /**
+     * Sets the parent plist.
+     * @param HTMLPurifier_PropertyList $plist Parent plist
+     */
+    public function setParent($plist)
+    {
+        $this->parent = $plist;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php
new file mode 100644 (file)
index 0000000..15b330e
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Property list iterator. Do not instantiate this class directly.
+ */
+class HTMLPurifier_PropertyListIterator extends FilterIterator
+{
+
+    /**
+     * @type int
+     */
+    protected $l;
+    /**
+     * @type string
+     */
+    protected $filter;
+
+    /**
+     * @param Iterator $iterator Array of data to iterate over
+     * @param string $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;
+    }
+
+    /**
+     * @return bool
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php
new file mode 100644 (file)
index 0000000..f58db90
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * A simple array-backed queue, based off of the classic Okasaki
+ * persistent amortized queue.  The basic idea is to maintain two
+ * stacks: an input stack and an output stack.  When the output
+ * stack runs out, reverse the input stack and use it as the output
+ * stack.
+ *
+ * We don't use the SPL implementation because it's only supported
+ * on PHP 5.3 and later.
+ *
+ * Exercise: Prove that push/pop on this queue take amortized O(1) time.
+ *
+ * Exercise: Extend this queue to be a deque, while preserving amortized
+ * O(1) time.  Some care must be taken on rebalancing to avoid quadratic
+ * behaviour caused by repeatedly shuffling data from the input stack
+ * to the output stack and back.
+ */
+class HTMLPurifier_Queue {
+    private $input;
+    private $output;
+
+    public function __construct($input = array()) {
+        $this->input = $input;
+        $this->output = array();
+    }
+
+    /**
+     * Shifts an element off the front of the queue.
+     */
+    public function shift() {
+        if (empty($this->output)) {
+            $this->output = array_reverse($this->input);
+            $this->input = array();
+        }
+        if (empty($this->output)) {
+            return NULL;
+        }
+        return array_pop($this->output);
+    }
+
+    /**
+     * Pushes an element onto the front of the queue.
+     */
+    public function push($x) {
+        array_push($this->input, $x);
+    }
+
+    /**
+     * Checks if it's empty.
+     */
+    public function isEmpty() {
+        return empty($this->input) && empty($this->output);
+    }
+}
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php
new file mode 100644 (file)
index 0000000..e1ff3b7
--- /dev/null
@@ -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 HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token objects to be operated on.
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return HTMLPurifier_Token[] Processed array of token objects.
+     */
+    abstract public function execute($tokens, $config, $context);
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Composite.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Composite.php
new file mode 100644 (file)
index 0000000..d7d35ce
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * Composite strategy that runs multiple strategies on tokens.
+ */
+abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy
+{
+
+    /**
+     * List of strategies to run tokens through.
+     * @type HTMLPurifier_Strategy[]
+     */
+    protected $strategies = array();
+
+    /**
+     * @param HTMLPurifier_Token[] $tokens
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return HTMLPurifier_Token[]
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php
new file mode 100644 (file)
index 0000000..4414c17
--- /dev/null
@@ -0,0 +1,17 @@
+<?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/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php
new file mode 100644 (file)
index 0000000..6fa673d
--- /dev/null
@@ -0,0 +1,181 @@
+<?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 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.  This is
+ *       easier with our new algorithm.
+ */
+
+class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy
+{
+
+    /**
+     * @param HTMLPurifier_Token[] $tokens
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array|HTMLPurifier_Token[]
+     */
+    public function execute($tokens, $config, $context)
+    {
+
+        //####################################################################//
+        // Pre-processing
+
+        // O(n) pass to convert to a tree, so that we can efficiently
+        // refer to substrings
+        $top_node = HTMLPurifier_Arborize::arborize($tokens, $config, $context);
+
+        // get a copy of the HTML definition
+        $definition = $config->getHTMLDefinition();
+
+        $excludes_enabled = !$config->get('Core.DisableExcludes');
+
+        // 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 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($definition->info_parent_def->excludes);
+
+        // variable that contains the start token while we are processing
+        // nodes. This enables error reporting to do its job
+        $node = $top_node;
+        // dummy token
+        list($token, $d) = $node->toTokenPair();
+        $context->register('CurrentNode', $node);
+        $context->register('CurrentToken', $token);
+
+        //####################################################################//
+        // Loop
+
+        // We need to implement a post-order traversal iteratively, to
+        // avoid running into stack space limits.  This is pretty tricky
+        // to reason about, so we just manually stack-ify the recursive
+        // variant:
+        //
+        //  function f($node) {
+        //      foreach ($node->children as $child) {
+        //          f($child);
+        //      }
+        //      validate($node);
+        //  }
+        //
+        // Thus, we will represent a stack frame as array($node,
+        // $is_inline, stack of children)
+        // e.g. array_reverse($node->children) - already processed
+        // children.
+
+        $parent_def = $definition->info_parent_def;
+        $stack = array(
+            array($top_node,
+                  $parent_def->descendants_are_inline,
+                  $parent_def->excludes, // exclusions
+                  0)
+            );
+
+        while (!empty($stack)) {
+            list($node, $is_inline, $excludes, $ix) = array_pop($stack);
+            // recursive call
+            $go = false;
+            $def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name];
+            while (isset($node->children[$ix])) {
+                $child = $node->children[$ix++];
+                if ($child instanceof HTMLPurifier_Node_Element) {
+                    $go = true;
+                    $stack[] = array($node, $is_inline, $excludes, $ix);
+                    $stack[] = array($child,
+                        // ToDo: I don't think it matters if it's def or
+                        // child_def, but double check this...
+                        $is_inline || $def->descendants_are_inline,
+                        empty($def->excludes) ? $excludes
+                                              : array_merge($excludes, $def->excludes),
+                        0);
+                    break;
+                }
+            };
+            if ($go) continue;
+            list($token, $d) = $node->toTokenPair();
+            // base case
+            if ($excludes_enabled && isset($excludes[$node->name])) {
+                $node->dead = true;
+                if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded');
+            } else {
+                // XXX I suppose it would be slightly more efficient to
+                // avoid the allocation here and have children
+                // strategies handle it
+                $children = array();
+                foreach ($node->children as $child) {
+                    if (!$child->dead) $children[] = $child;
+                }
+                $result = $def->child->validateChildren($children, $config, $context);
+                if ($result === true) {
+                    // nop
+                    $node->children = $children;
+                } elseif ($result === false) {
+                    $node->dead = true;
+                    if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node removed');
+                } else {
+                    $node->children = $result;
+                    if ($e) {
+                        // XXX This will miss mutations of internal nodes. Perhaps defer to the child validators
+                        if (empty($result) && !empty($children)) {
+                            $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed');
+                        } else if ($result != $children) {
+                            $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized');
+                        }
+                    }
+                }
+            }
+        }
+
+        //####################################################################//
+        // Post-processing
+
+        // remove context variables
+        $context->destroy('IsInline');
+        $context->destroy('CurrentNode');
+        $context->destroy('CurrentToken');
+
+        //####################################################################//
+        // Return
+
+        return HTMLPurifier_Arborize::flatten($node, $config, $context);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php
new file mode 100644 (file)
index 0000000..e389e00
--- /dev/null
@@ -0,0 +1,600 @@
+<?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.
+     * @type HTMLPurifier_Token[]
+     */
+    protected $tokens;
+
+    /**
+     * Current token.
+     * @type HTMLPurifier_Token
+     */
+    protected $token;
+
+    /**
+     * Zipper managing the true state.
+     * @type HTMLPurifier_Zipper
+     */
+    protected $zipper;
+
+    /**
+     * Current nesting of elements.
+     * @type array
+     */
+    protected $stack;
+
+    /**
+     * Injectors active in this stream processing.
+     * @type HTMLPurifier_Injector[]
+     */
+    protected $injectors;
+
+    /**
+     * Current instance of HTMLPurifier_Config.
+     * @type HTMLPurifier_Config
+     */
+    protected $config;
+
+    /**
+     * Current instance of HTMLPurifier_Context.
+     * @type HTMLPurifier_Context
+     */
+    protected $context;
+
+    /**
+     * @param HTMLPurifier_Token[] $tokens
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return HTMLPurifier_Token[]
+     * @throws HTMLPurifier_Exception
+     */
+    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 = $definition->info_parent_def->child->getAllowedElements($config);
+        $e = $context->get('ErrorCollector', true);
+        $i = false; // injector index
+        list($zipper, $token) = HTMLPurifier_Zipper::fromArray($tokens);
+        if ($token === NULL) {
+            return array();
+        }
+        $reprocess = false; // whether or not to reprocess the same token
+        $stack = array();
+
+        // member variables
+        $this->stack =& $stack;
+        $this->tokens =& $tokens;
+        $this->token =& $token;
+        $this->zipper =& $zipper;
+        $this->config = $config;
+        $this->context = $context;
+
+        // context variables
+        $context->register('CurrentNesting', $stack);
+        $context->register('InputZipper', $zipper);
+        $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 (;;
+             // only increment if we don't need to reprocess
+             $reprocess ? $reprocess = false : $token = $zipper->next($token)) {
+
+            // check for a rewind
+            if (is_int($i)) {
+                // 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_offset = $this->injectors[$i]->getRewindOffset();
+                if (is_int($rewind_offset)) {
+                    for ($j = 0; $j < $rewind_offset; $j++) {
+                        if (empty($zipper->front)) break;
+                        $token = $zipper->prev($token);
+                        // indicate that other injectors should not process this token,
+                        // but we need to reprocess it
+                        unset($token->skip[$i]);
+                        $token->rewind = $i;
+                        if ($token instanceof HTMLPurifier_Token_Start) {
+                            array_pop($this->stack);
+                        } elseif ($token instanceof HTMLPurifier_Token_End) {
+                            $this->stack[] = $token->start;
+                        }
+                    }
+                }
+                $i = false;
+            }
+
+            // handle case of document end
+            if ($token === NULL) {
+                // 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
+                $token = new HTMLPurifier_Token_End($top_nesting->name);
+
+                // punt!
+                $reprocess = true;
+                continue;
+            }
+
+            //echo '<br>'; printZipper($zipper, $token);//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;
+                        }
+                        // XXX fuckup
+                        $r = $token;
+                        $injector->handleText($r);
+                        $token = $this->processToken($r, $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
+                // NB: this assignment is required
+                $old_token = $token;
+                $token = new HTMLPurifier_Token_End($token->name);
+                $token = $this->insertBefore(
+                    new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_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;
+
+                    $parent_def = null;
+                    $parent_elements = null;
+                    $autoclose = false;
+                    if (isset($definition->info[$parent->name])) {
+                        $parent_def = $definition->info[$parent->name];
+                        $parent_elements = $parent_def->child->getAllowedElements($config);
+                        $autoclose = !isset($parent_elements[$token->name]);
+                    }
+
+                    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);
+                        if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) {
+                            $newtoken = new HTMLPurifier_Token_Start($wrapname);
+                            $token = $this->insertBefore($newtoken);
+                            $reprocess = true;
+                            continue;
+                        }
+                    }
+
+                    $carryover = false;
+                    if ($autoclose && $parent_def->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;
+                            // [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);
+                                }
+                            }
+                            if ($carryover) {
+                                $element = clone $parent;
+                                // [TagClosedAuto]
+                                $element->armor['MakeWellFormed_TagClosedError'] = true;
+                                $element->carryover = true;
+                                $token = $this->processToken(array($new_token, $token, $element));
+                            } else {
+                                $token = $this->insertBefore($new_token);
+                            }
+                        } else {
+                            $token = $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;
+                    }
+                    $r = $token;
+                    $injector->handleElement($r);
+                    $token = $this->processToken($r, $i);
+                    $reprocess = true;
+                    break;
+                }
+                if (!$reprocess) {
+                    // ah, nothing interesting happened; do normal processing
+                    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');
+                    }
+                    $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));
+                } else {
+                    if ($e) {
+                        $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed');
+                    }
+                    $token = $this->remove();
+                }
+                $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;
+                    }
+                    $r = $token;
+                    $injector->handleEnd($r);
+                    $token = $this->processToken($r, $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) {
+                    if ($e) {
+                        $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text');
+                    }
+                    $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));
+                } else {
+                    if ($e) {
+                        $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed');
+                    }
+                    $token = $this->remove();
+                }
+                $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;
+                }
+            }
+            $token = $this->processToken($replace);
+            $reprocess = true;
+            continue;
+        }
+
+        $context->destroy('CurrentToken');
+        $context->destroy('CurrentNesting');
+        $context->destroy('InputZipper');
+
+        unset($this->injectors, $this->stack, $this->tokens);
+        return $zipper->toArray($token);
+    }
+
+    /**
+     * 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 HTMLPurifier_Token|array|int|bool $token Token substitution value
+     * @param HTMLPurifier_Injector|int $injector Injector that performed the substitution; default is if
+     *        this is not an injector related operation.
+     * @throws HTMLPurifier_Exception
+     */
+    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);
+        list($old, $r) = $this->zipper->splice($this->token, $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;
+            }
+        }
+
+        return $r;
+
+    }
+
+    /**
+     * Inserts a token before the current token. Cursor now points to
+     * this token.  You must reprocess after this.
+     * @param HTMLPurifier_Token $token
+     */
+    private function insertBefore($token)
+    {
+        // NB not $this->zipper->insertBefore(), due to positioning
+        // differences
+        $splice = $this->zipper->splice($this->token, 0, array($token));
+
+        return $splice[1];
+    }
+
+    /**
+     * Removes current token. Cursor now points to new token occupying previously
+     * occupied space.  You must reprocess after this.
+     */
+    private function remove()
+    {
+        return $this->zipper->delete();
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php
new file mode 100644 (file)
index 0000000..1a8149e
--- /dev/null
@@ -0,0 +1,207 @@
+<?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
+{
+
+    /**
+     * @param HTMLPurifier_Token[] $tokens
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return array|HTMLPurifier_Token[]
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php
new file mode 100644 (file)
index 0000000..fbb3d27
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * Validate all attributes in the tokens.
+ */
+
+class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy
+{
+
+    /**
+     * @param HTMLPurifier_Token[] $tokens
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return HTMLPurifier_Token[]
+     */
+    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);
+        }
+        $context->destroy('CurrentToken');
+        return $tokens;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php
new file mode 100644 (file)
index 0000000..c073701
--- /dev/null
@@ -0,0 +1,47 @@
+<?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
+{
+    /**
+     * @type array
+     */
+    protected $accessed = array();
+
+    /**
+     * Retrieves a value, and logs the access.
+     * @param mixed $index
+     * @return mixed
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php
new file mode 100644 (file)
index 0000000..7c73f80
--- /dev/null
@@ -0,0 +1,136 @@
+<?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
+{
+
+    /**
+     * @type string
+     */
+    public $default = 'ID';
+
+    /**
+     * Parses a file that contains a single string-hash.
+     * @param string $file
+     * @return array
+     */
+    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 '----'
+     * @param string $file
+     * @return array
+     */
+    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 resource $fh File handle with pointer at start of valid string-hash
+     *            block.
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php
new file mode 100644 (file)
index 0000000..7b8d833
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * Defines a mutation of an obsolete tag into a valid tag.
+ */
+abstract class HTMLPurifier_TagTransform
+{
+
+    /**
+     * Tag name to transform the tag to.
+     * @type string
+     */
+    public $transform_to;
+
+    /**
+     * Transforms the obsolete tag into the valid tag.
+     * @param HTMLPurifier_Token_Tag $tag Tag to be transformed.
+     * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object
+     * @param HTMLPurifier_Context $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 array $attr Attribute array to process (passed by reference)
+     * @param string $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/library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Font.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Font.php
new file mode 100644 (file)
index 0000000..7853d90
--- /dev/null
@@ -0,0 +1,114 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $transform_to = 'span';
+
+    /**
+     * @type array
+     */
+    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%'
+    );
+
+    /**
+     * @param HTMLPurifier_Token_Tag $tag
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return HTMLPurifier_Token_End|string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php
new file mode 100644 (file)
index 0000000..71bf10b
--- /dev/null
@@ -0,0 +1,44 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    protected $style;
+
+    /**
+     * @param string $transform_to Tag name to transform to.
+     * @param string $style CSS style to add to the tag
+     */
+    public function __construct($transform_to, $style = null)
+    {
+        $this->transform_to = $transform_to;
+        $this->style = $style;
+    }
+
+    /**
+     * @param HTMLPurifier_Token_Tag $tag
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/Token.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token.php
new file mode 100644 (file)
index 0000000..85b85e0
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * Abstract base token class that all others inherit from.
+ */
+abstract class HTMLPurifier_Token
+{
+    /**
+     * Line number node was on in source document. Null if unknown.
+     * @type int
+     */
+    public $line;
+
+    /**
+     * Column of line node was on in source document. Null if unknown.
+     * @type int
+     */
+    public $col;
+
+    /**
+     * Lookup array of processing that this token is exempt from.
+     * Currently, valid values are "ValidateAttributes" and
+     * "MakeWellFormed_TagClosedError"
+     * @type array
+     */
+    public $armor = array();
+
+    /**
+     * Used during MakeWellFormed.
+     * @type
+     */
+    public $skip;
+
+    /**
+     * @type
+     */
+    public $rewind;
+
+    /**
+     * @type
+     */
+    public $carryover;
+
+    /**
+     * @param string $n
+     * @return null|string
+     */
+    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.
+     * @param int $l
+     * @param int $c
+     */
+    public function position($l = null, $c = null)
+    {
+        $this->line = $l;
+        $this->col = $c;
+    }
+
+    /**
+     * Convenience function for DirectLex settings line/col position.
+     * @param int $l
+     * @param int $c
+     */
+    public function rawPosition($l, $c)
+    {
+        if ($c === -1) {
+            $l++;
+        }
+        $this->line = $l;
+        $this->col = $c;
+    }
+
+    /**
+     * Converts a token into its corresponding node.
+     */
+    abstract public function toNode();
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php
new file mode 100644 (file)
index 0000000..23453c7
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * Concrete comment token class. Generally will be ignored.
+ */
+class HTMLPurifier_Token_Comment extends HTMLPurifier_Token
+{
+    /**
+     * Character data within comment.
+     * @type string
+     */
+    public $data;
+
+    /**
+     * @type bool
+     */
+    public $is_whitespace = true;
+
+    /**
+     * Transparent constructor.
+     *
+     * @param string $data String comment data.
+     * @param int $line
+     * @param int $col
+     */
+    public function __construct($data, $line = null, $col = null)
+    {
+        $this->data = $data;
+        $this->line = $line;
+        $this->col = $col;
+    }
+
+    public function toNode() {
+        return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php
new file mode 100644 (file)
index 0000000..78a95f5
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * Concrete empty token class.
+ */
+class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag
+{
+    public function toNode() {
+        $n = parent::toNode();
+        $n->empty = true;
+        return $n;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php
new file mode 100644 (file)
index 0000000..59b38fd
--- /dev/null
@@ -0,0 +1,24 @@
+<?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!
+     * @type HTMLPurifier_Token
+     */
+    public $start;
+
+    public function toNode() {
+        throw new Exception("HTMLPurifier_Token_End->toNode not supported!");
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php
new file mode 100644 (file)
index 0000000..019f317
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+
+/**
+ * Concrete start token class.
+ */
+class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag
+{
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Tag.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Tag.php
new file mode 100644 (file)
index 0000000..d643fa6
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * Abstract class of a tag token (start, end or empty), and its behavior.
+ */
+abstract 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>.
+     * @type bool
+     */
+    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.
+     * @type string
+     */
+    public $name;
+
+    /**
+     * Associative array of the tag's attributes.
+     * @type array
+     */
+    public $attr = array();
+
+    /**
+     * Non-overloaded constructor, which lower-cases passed tag name.
+     *
+     * @param string $name String name.
+     * @param array $attr Associative array of attributes.
+     * @param int $line
+     * @param int $col
+     * @param array $armor
+     */
+    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;
+    }
+
+    public function toNode() {
+        return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php
new file mode 100644 (file)
index 0000000..f26a1c2
--- /dev/null
@@ -0,0 +1,53 @@
+<?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
+{
+
+    /**
+     * @type string
+     */
+    public $name = '#PCDATA';
+    /**< PCDATA tag name compatible with DTD. */
+
+    /**
+     * @type string
+     */
+    public $data;
+    /**< Parsed character data of text. */
+
+    /**
+     * @type bool
+     */
+    public $is_whitespace;
+
+    /**< Bool indicating if node is whitespace. */
+
+    /**
+     * Constructor, accepts data and determines if it is whitespace.
+     * @param string $data String parsed character data.
+     * @param int $line
+     * @param int $col
+     */
+    public function __construct($data, $line = null, $col = null)
+    {
+        $this->data = $data;
+        $this->is_whitespace = ctype_space($data);
+        $this->line = $line;
+        $this->col = $col;
+    }
+
+    public function toNode() {
+        return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php
new file mode 100644 (file)
index 0000000..dea2446
--- /dev/null
@@ -0,0 +1,118 @@
+<?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
+{
+    // p stands for prototype
+
+    /**
+     * @type HTMLPurifier_Token_Start
+     */
+    private $p_start;
+
+    /**
+     * @type HTMLPurifier_Token_End
+     */
+    private $p_end;
+
+    /**
+     * @type HTMLPurifier_Token_Empty
+     */
+    private $p_empty;
+
+    /**
+     * @type HTMLPurifier_Token_Text
+     */
+    private $p_text;
+
+    /**
+     * @type HTMLPurifier_Token_Comment
+     */
+    private $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 string $name Tag name
+     * @param array $attr Associative array of attributes
+     * @return HTMLPurifier_Token_Start 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 string $name Tag name
+     * @return HTMLPurifier_Token_End Generated HTMLPurifier_Token_End
+     */
+    public function createEnd($name)
+    {
+        $p = clone $this->p_end;
+        $p->__construct($name);
+        return $p;
+    }
+
+    /**
+     * Creates a HTMLPurifier_Token_Empty.
+     * @param string $name Tag name
+     * @param array $attr Associative array of attributes
+     * @return HTMLPurifier_Token_Empty 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 string $data Data of text token
+     * @return HTMLPurifier_Token_Text Generated HTMLPurifier_Token_Text
+     */
+    public function createText($data)
+    {
+        $p = clone $this->p_text;
+        $p->__construct($data);
+        return $p;
+    }
+
+    /**
+     * Creates a HTMLPurifier_Token_Comment.
+     * @param string $data Data of comment token
+     * @return HTMLPurifier_Token_Comment 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/library/ezyang/htmlpurifier/library/HTMLPurifier/URI.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URI.php
new file mode 100644 (file)
index 0000000..a5e7ae2
--- /dev/null
@@ -0,0 +1,314 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $scheme;
+
+    /**
+     * @type string
+     */
+    public $userinfo;
+
+    /**
+     * @type string
+     */
+    public $host;
+
+    /**
+     * @type int
+     */
+    public $port;
+
+    /**
+     * @type string
+     */
+    public $path;
+
+    /**
+     * @type string
+     */
+    public $query;
+
+    /**
+     * @type string
+     */
+    public $fragment;
+
+    /**
+     * @param string $scheme
+     * @param string $userinfo
+     * @param string $host
+     * @param int $port
+     * @param string $path
+     * @param string $query
+     * @param string $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 HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return HTMLPurifier_URIScheme 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 HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool 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
+        $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.
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    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
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php
new file mode 100644 (file)
index 0000000..e0bd8bc
--- /dev/null
@@ -0,0 +1,112 @@
+<?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/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php
new file mode 100644 (file)
index 0000000..09724e9
--- /dev/null
@@ -0,0 +1,74 @@
+<?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.
+     * @type string
+     */
+    public $name;
+
+    /**
+     * True if this filter should be run after scheme validation.
+     * @type bool
+     */
+    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.
+     * @type bool
+     */
+    public $always_load = false;
+
+    /**
+     * Performs initialization for the filter.  If the filter returns
+     * false, this means that it shouldn't be considered active.
+     * @param HTMLPurifier_Config $config
+     * @return bool
+     */
+    public function prepare($config)
+    {
+        return true;
+    }
+
+    /**
+     * Filter a URI object
+     * @param HTMLPurifier_URI $uri Reference to URI object variable
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $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/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternal.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternal.php
new file mode 100644 (file)
index 0000000..ced1b13
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+
+class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter
+{
+    /**
+     * @type string
+     */
+    public $name = 'DisableExternal';
+
+    /**
+     * @type array
+     */
+    protected $ourHostParts = false;
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return void
+     */
+    public function prepare($config)
+    {
+        $our_host = $config->getDefinition('URI')->host;
+        if ($our_host !== null) {
+            $this->ourHostParts = array_reverse(explode('.', $our_host));
+        }
+    }
+
+    /**
+     * @param HTMLPurifier_URI $uri Reference
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php
new file mode 100644 (file)
index 0000000..c656216
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal
+{
+    /**
+     * @type string
+     */
+    public $name = 'DisableExternalResources';
+
+    /**
+     * @param HTMLPurifier_URI $uri
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php
new file mode 100644 (file)
index 0000000..d5c412c
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+class HTMLPurifier_URIFilter_DisableResources extends HTMLPurifier_URIFilter
+{
+    /**
+     * @type string
+     */
+    public $name = 'DisableResources';
+
+    /**
+     * @param HTMLPurifier_URI $uri
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    public function filter(&$uri, $config, $context)
+    {
+        return !$context->get('EmbeddedURI', true);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php
new file mode 100644 (file)
index 0000000..a6645c1
--- /dev/null
@@ -0,0 +1,46 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $name = 'HostBlacklist';
+
+    /**
+     * @type array
+     */
+    protected $blacklist = array();
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return bool
+     */
+    public function prepare($config)
+    {
+        $this->blacklist = $config->get('URI.HostBlacklist');
+        return true;
+    }
+
+    /**
+     * @param HTMLPurifier_URI $uri
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php
new file mode 100644 (file)
index 0000000..c507bbf
--- /dev/null
@@ -0,0 +1,158 @@
+<?php
+
+// does not support network paths
+
+class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter
+{
+    /**
+     * @type string
+     */
+    public $name = 'MakeAbsolute';
+
+    /**
+     * @type
+     */
+    protected $base;
+
+    /**
+     * @type array
+     */
+    protected $basePathStack = array();
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return bool
+     */
+    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;
+    }
+
+    /**
+     * @param HTMLPurifier_URI $uri
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    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
+     * @param array $stack
+     * @return array
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php
new file mode 100644 (file)
index 0000000..6e03315
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+
+class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter
+{
+    /**
+     * @type string
+     */
+    public $name = 'Munge';
+
+    /**
+     * @type bool
+     */
+    public $post = true;
+
+    /**
+     * @type string
+     */
+    private $target;
+
+    /**
+     * @type HTMLPurifier_URIParser
+     */
+    private $parser;
+
+    /**
+     * @type bool
+     */
+    private $doEmbed;
+
+    /**
+     * @type string
+     */
+    private $secretKey;
+
+    /**
+     * @type array
+     */
+    protected $replace = array();
+
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return bool
+     */
+    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');
+        if ($this->secretKey && !function_exists('hash_hmac')) {
+            throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support.");
+        }
+        return true;
+    }
+
+    /**
+     * @param HTMLPurifier_URI $uri
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    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;
+    }
+
+    /**
+     * @param HTMLPurifier_URI $uri
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     */
+    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'] = hash_hmac("sha256", $string, $this->secretKey);
+        }
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php
new file mode 100644 (file)
index 0000000..f609c47
--- /dev/null
@@ -0,0 +1,68 @@
+<?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
+{
+    /**
+     * @type string
+     */
+    public $name = 'SafeIframe';
+
+    /**
+     * @type bool
+     */
+    public $always_load = true;
+
+    /**
+     * @type string
+     */
+    protected $regexp = null;
+
+    // XXX: The not so good bit about how this is all set up now is we
+    // can't check HTML.SafeIframe in the 'prepare' step: we have to
+    // defer till the actual filtering.
+    /**
+     * @param HTMLPurifier_Config $config
+     * @return bool
+     */
+    public function prepare($config)
+    {
+        $this->regexp = $config->get('URI.SafeIframeRegexp');
+        return true;
+    }
+
+    /**
+     * @param HTMLPurifier_URI $uri
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php
new file mode 100644 (file)
index 0000000..0e7381a
--- /dev/null
@@ -0,0 +1,71 @@
+<?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 = '!'.
+            '(([a-zA-Z0-9\.\+\-]+):)?'. // 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/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php
new file mode 100644 (file)
index 0000000..fe9e82c
--- /dev/null
@@ -0,0 +1,102 @@
+<?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.
+     * @type int
+     */
+    public $default_port = null;
+
+    /**
+     * Whether or not URIs of this scheme are locatable by a browser
+     * http and ftp are accessible, while mailto and news are not.
+     * @type bool
+     */
+    public $browsable = false;
+
+    /**
+     * Whether or not data transmitted over this scheme is encrypted.
+     * https is secure, http is not.
+     * @type bool
+     */
+    public $secure = false;
+
+    /**
+     * Whether or not the URI always uses <hier_part>, resolves edge cases
+     * with making relative URIs absolute
+     * @type bool
+     */
+    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.
+     * @type bool
+     */
+    public $may_omit_host = false;
+
+    /**
+     * Validates the components of a URI for a specific scheme.
+     * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool success or failure
+     */
+    abstract public 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 HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @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/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php
new file mode 100644 (file)
index 0000000..6ebca49
--- /dev/null
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ * Implements data: URI for base64 encoded images supported by GD.
+ */
+class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme
+{
+    /**
+     * @type bool
+     */
+    public $browsable = true;
+
+    /**
+     * @type array
+     */
+    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
+    /**
+     * @type bool
+     */
+    public $may_omit_host = true;
+
+    /**
+     * @param HTMLPurifier_URI $uri
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    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;
+    }
+
+    /**
+     * @param int $errno
+     * @param string $errstr
+     */
+    public function muteErrorHandler($errno, $errstr)
+    {
+    }
+}
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php
new file mode 100644 (file)
index 0000000..215be4b
--- /dev/null
@@ -0,0 +1,44 @@
+<?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.
+     * @type bool
+     */
+    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.
+     * @type bool
+     */
+    public $may_omit_host = true;
+
+    /**
+     * @param HTMLPurifier_URI $uri
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php
new file mode 100644 (file)
index 0000000..1eb43ee
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Validates ftp (File Transfer Protocol) URIs as defined by generic RFC 1738.
+ */
+class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme
+{
+    /**
+     * @type int
+     */
+    public $default_port = 21;
+
+    /**
+     * @type bool
+     */
+    public $browsable = true; // usually
+
+    /**
+     * @type bool
+     */
+    public $hierarchical = true;
+
+    /**
+     * @param HTMLPurifier_URI $uri
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php
new file mode 100644 (file)
index 0000000..ce69ec4
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * Validates http (HyperText Transfer Protocol) as defined by RFC 2616
+ */
+class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme
+{
+    /**
+     * @type int
+     */
+    public $default_port = 80;
+
+    /**
+     * @type bool
+     */
+    public $browsable = true;
+
+    /**
+     * @type bool
+     */
+    public $hierarchical = true;
+
+    /**
+     * @param HTMLPurifier_URI $uri
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    public function doValidate(&$uri, $config, $context)
+    {
+        $uri->userinfo = null;
+        return true;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php
new file mode 100644 (file)
index 0000000..0e96882
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * Validates https (Secure HTTP) according to http scheme.
+ */
+class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http
+{
+    /**
+     * @type int
+     */
+    public $default_port = 443;
+    /**
+     * @type bool
+     */
+    public $secure = true;
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/mailto.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/mailto.php
new file mode 100644 (file)
index 0000000..c3a6b60
--- /dev/null
@@ -0,0 +1,40 @@
+<?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
+{
+    /**
+     * @type bool
+     */
+    public $browsable = false;
+
+    /**
+     * @type bool
+     */
+    public $may_omit_host = true;
+
+    /**
+     * @param HTMLPurifier_URI $uri
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php
new file mode 100644 (file)
index 0000000..7490927
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * Validates news (Usenet) as defined by generic RFC 1738
+ */
+class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme
+{
+    /**
+     * @type bool
+     */
+    public $browsable = false;
+
+    /**
+     * @type bool
+     */
+    public $may_omit_host = true;
+
+    /**
+     * @param HTMLPurifier_URI $uri
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php
new file mode 100644 (file)
index 0000000..f211d71
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738
+ */
+class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme
+{
+    /**
+     * @type int
+     */
+    public $default_port = 119;
+
+    /**
+     * @type bool
+     */
+    public $browsable = false;
+
+    /**
+     * @param HTMLPurifier_URI $uri
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return bool
+     */
+    public function doValidate(&$uri, $config, $context)
+    {
+        $uri->userinfo = null;
+        $uri->query = null;
+        return true;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php
new file mode 100644 (file)
index 0000000..4ac8a0b
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * Registry for retrieving specific URI scheme validator objects.
+ */
+class HTMLPurifier_URISchemeRegistry
+{
+
+    /**
+     * Retrieve sole instance of the registry.
+     * @param HTMLPurifier_URISchemeRegistry $prototype Optional prototype to overload sole instance with,
+     *                   or bool true to reset to default registry.
+     * @return HTMLPurifier_URISchemeRegistry
+     * @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.
+     * @type HTMLPurifier_URIScheme[]
+     */
+    protected $schemes = array();
+
+    /**
+     * Retrieves a scheme validator object
+     * @param string $scheme String scheme name like http or mailto
+     * @param HTMLPurifier_Config $config
+     * @param HTMLPurifier_Context $context
+     * @return HTMLPurifier_URIScheme
+     */
+    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 string $scheme Scheme name
+     * @param HTMLPurifier_URIScheme $scheme_obj
+     */
+    public function register($scheme, $scheme_obj)
+    {
+        $this->schemes[$scheme] = $scheme_obj;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php
new file mode 100644 (file)
index 0000000..166f3bf
--- /dev/null
@@ -0,0 +1,307 @@
+<?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.
+     * @type int
+     */
+    protected $outputPrecision;
+
+    /**
+     * Bcmath precision for internal calculations.
+     * @type int
+     */
+    protected $internalPrecision;
+
+    /**
+     * Whether or not BCMath is available.
+     * @type bool
+     */
+    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.
+     * @return HTMLPurifier_Length|bool
+     * @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.
+     * @param string $s1
+     * @param string $s2
+     * @param int $scale
+     * @return string
+     */
+    private function add($s1, $s2, $scale)
+    {
+        if ($this->bcmath) {
+            return bcadd($s1, $s2, $scale);
+        } else {
+            return $this->scale((float)$s1 + (float)$s2, $scale);
+        }
+    }
+
+    /**
+     * Multiples two numbers, using arbitrary precision when available.
+     * @param string $s1
+     * @param string $s2
+     * @param int $scale
+     * @return string
+     */
+    private function mul($s1, $s2, $scale)
+    {
+        if ($this->bcmath) {
+            return bcmul($s1, $s2, $scale);
+        } else {
+            return $this->scale((float)$s1 * (float)$s2, $scale);
+        }
+    }
+
+    /**
+     * Divides two numbers, using arbitrary precision when available.
+     * @param string $s1
+     * @param string $s2
+     * @param int $scale
+     * @return string
+     */
+    private function div($s1, $s2, $scale)
+    {
+        if ($this->bcmath) {
+            return bcdiv($s1, $s2, $scale);
+        } else {
+            return $this->scale((float)$s1 / (float)$s2, $scale);
+        }
+    }
+
+    /**
+     * Rounds a number according to the number of sigfigs it should have,
+     * using arbitrary precision when available.
+     * @param float $n
+     * @param int $sigfigs
+     * @return string
+     */
+    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.
+     * @param float $r
+     * @param int $scale
+     * @return string
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php
new file mode 100644 (file)
index 0000000..50cba69
--- /dev/null
@@ -0,0 +1,198 @@
+<?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.
+     */
+    public static $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.
+     */
+    public static $stringTypes = array(
+        self::STRING => true,
+        self::ISTRING => true,
+        self::TEXT => true,
+        self::ITEXT => true,
+    );
+
+    /**
+     * Validate a variable according to type.
+     * It may return NULL as a valid type if $allow_null is true.
+     *
+     * @param mixed $var Variable to validate
+     * @param int $type Type of variable, see HTMLPurifier_VarParser->types
+     * @param bool $allow_null Whether or not to permit null as a value
+     * @return string Validated and type-coerced variable
+     * @throws HTMLPurifier_VarParserException
+     */
+    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 does not
+     * do anything to $var. Subclasses should overload this!
+     * @param mixed $var
+     * @param int $type
+     * @param bool $allow_null
+     * @return string
+     */
+    protected function parseImplementation($var, $type, $allow_null)
+    {
+        return $var;
+    }
+
+    /**
+     * Throws an exception.
+     * @throws HTMLPurifier_VarParserException
+     */
+    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.
+     * @param string $class
+     * @param int $type
+     * @throws HTMLPurifier_Exception
+     */
+    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.
+     * @param mixed $var
+     * @param int $type
+     */
+    protected function errorGeneric($var, $type)
+    {
+        $vtype = gettype($var);
+        $this->error("Expected type " . HTMLPurifier_VarParser::getTypeName($type) . ", got $vtype");
+    }
+
+    /**
+     * @param int $type
+     * @return string
+     */
+    public static 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/library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php
new file mode 100644 (file)
index 0000000..b15016c
--- /dev/null
@@ -0,0 +1,130 @@
+<?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
+{
+    /**
+     * @param mixed $var
+     * @param int $type
+     * @param bool $allow_null
+     * @return array|bool|float|int|mixed|null|string
+     * @throws HTMLPurifier_VarParserException
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php
new file mode 100644 (file)
index 0000000..f11c318
--- /dev/null
@@ -0,0 +1,38 @@
+<?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
+{
+
+    /**
+     * @param mixed $var
+     * @param int $type
+     * @param bool $allow_null
+     * @return null|string
+     */
+    protected function parseImplementation($var, $type, $allow_null)
+    {
+        return $this->evalExpression($var);
+    }
+
+    /**
+     * @param string $expr
+     * @return mixed
+     * @throws HTMLPurifier_VarParserException
+     */
+    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/library/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php
new file mode 100644 (file)
index 0000000..5df3414
--- /dev/null
@@ -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/library/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php
new file mode 100644 (file)
index 0000000..6e21ea0
--- /dev/null
@@ -0,0 +1,157 @@
+<?php
+
+/**
+ * A zipper is a purely-functional data structure which contains
+ * a focus that can be efficiently manipulated.  It is known as
+ * a "one-hole context".  This mutable variant implements a zipper
+ * for a list as a pair of two arrays, laid out as follows:
+ *
+ *      Base list: 1 2 3 4 [ ] 6 7 8 9
+ *      Front list: 1 2 3 4
+ *      Back list: 9 8 7 6
+ *
+ * User is expected to keep track of the "current element" and properly
+ * fill it back in as necessary.  (ToDo: Maybe it's more user friendly
+ * to implicitly track the current element?)
+ *
+ * Nota bene: the current class gets confused if you try to store NULLs
+ * in the list.
+ */
+
+class HTMLPurifier_Zipper
+{
+    public $front, $back;
+
+    public function __construct($front, $back) {
+        $this->front = $front;
+        $this->back = $back;
+    }
+
+    /**
+     * Creates a zipper from an array, with a hole in the
+     * 0-index position.
+     * @param Array to zipper-ify.
+     * @return Tuple of zipper and element of first position.
+     */
+    static public function fromArray($array) {
+        $z = new self(array(), array_reverse($array));
+        $t = $z->delete(); // delete the "dummy hole"
+        return array($z, $t);
+    }
+
+    /**
+     * Convert zipper back into a normal array, optionally filling in
+     * the hole with a value. (Usually you should supply a $t, unless you
+     * are at the end of the array.)
+     */
+    public function toArray($t = NULL) {
+        $a = $this->front;
+        if ($t !== NULL) $a[] = $t;
+        for ($i = count($this->back)-1; $i >= 0; $i--) {
+            $a[] = $this->back[$i];
+        }
+        return $a;
+    }
+
+    /**
+     * Move hole to the next element.
+     * @param $t Element to fill hole with
+     * @return Original contents of new hole.
+     */
+    public function next($t) {
+        if ($t !== NULL) array_push($this->front, $t);
+        return empty($this->back) ? NULL : array_pop($this->back);
+    }
+
+    /**
+     * Iterated hole advancement.
+     * @param $t Element to fill hole with
+     * @param $i How many forward to advance hole
+     * @return Original contents of new hole, i away
+     */
+    public function advance($t, $n) {
+        for ($i = 0; $i < $n; $i++) {
+            $t = $this->next($t);
+        }
+        return $t;
+    }
+
+    /**
+     * Move hole to the previous element
+     * @param $t Element to fill hole with
+     * @return Original contents of new hole.
+     */
+    public function prev($t) {
+        if ($t !== NULL) array_push($this->back, $t);
+        return empty($this->front) ? NULL : array_pop($this->front);
+    }
+
+    /**
+     * Delete contents of current hole, shifting hole to
+     * next element.
+     * @return Original contents of new hole.
+     */
+    public function delete() {
+        return empty($this->back) ? NULL : array_pop($this->back);
+    }
+
+    /**
+     * Returns true if we are at the end of the list.
+     * @return bool
+     */
+    public function done() {
+        return empty($this->back);
+    }
+
+    /**
+     * Insert element before hole.
+     * @param Element to insert
+     */
+    public function insertBefore($t) {
+        if ($t !== NULL) array_push($this->front, $t);
+    }
+
+    /**
+     * Insert element after hole.
+     * @param Element to insert
+     */
+    public function insertAfter($t) {
+        if ($t !== NULL) array_push($this->back, $t);
+    }
+
+    /**
+     * Splice in multiple elements at hole.  Functional specification
+     * in terms of array_splice:
+     *
+     *      $arr1 = $arr;
+     *      $old1 = array_splice($arr1, $i, $delete, $replacement);
+     *
+     *      list($z, $t) = HTMLPurifier_Zipper::fromArray($arr);
+     *      $t = $z->advance($t, $i);
+     *      list($old2, $t) = $z->splice($t, $delete, $replacement);
+     *      $arr2 = $z->toArray($t);
+     *
+     *      assert($old1 === $old2);
+     *      assert($arr1 === $arr2);
+     *
+     * NB: the absolute index location after this operation is
+     * *unchanged!*
+     *
+     * @param Current contents of hole.
+     */
+    public function splice($t, $delete, $replacement) {
+        // delete
+        $old = array();
+        $r = $t;
+        for ($i = $delete; $i > 0; $i--) {
+            $old[] = $r;
+            $r = $this->delete();
+        }
+        // insert
+        for ($i = count($replacement)-1; $i >= 0; $i--) {
+            $this->insertAfter($r);
+            $r = $replacement[$i];
+        }
+        return array($old, $r);
+    }
+}
diff --git a/library/ezyang/htmlpurifier/package.php b/library/ezyang/htmlpurifier/package.php
new file mode 100644 (file)
index 0000000..bfef936
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+
+set_time_limit(0);
+
+require_once 'PEAR/PackageFileManager2.php';
+require_once 'PEAR/PackageFileManager/File.php';
+PEAR::setErrorHandling(PEAR_ERROR_PRINT);
+$pkg = new PEAR_PackageFileManager2;
+
+$pkg->setOptions(
+    array(
+        'baseinstalldir' => '/',
+        'packagefile' => 'package.xml',
+        'packagedirectory' => realpath(dirname(__FILE__) . '/library'),
+        'filelistgenerator' => 'file',
+        'include' => array('*'),
+        'dir_roles' => array('/' => 'php'), // hack to put *.ser files in the right place
+        'ignore' => array(
+            'HTMLPurifier.standalone.php',
+            'HTMLPurifier.path.php',
+            '*.tar.gz',
+            '*.tgz',
+            'standalone/'
+        ),
+    )
+);
+
+$pkg->setPackage('HTMLPurifier');
+$pkg->setLicense('LGPL', 'http://www.gnu.org/licenses/lgpl.html');
+$pkg->setSummary('Standards-compliant HTML filter');
+$pkg->setDescription(
+    'HTML Purifier is an HTML filter that will remove all malicious code
+    (better known as XSS) with a thoroughly audited, secure yet permissive
+    whitelist and will also make sure your documents are standards
+    compliant.'
+);
+
+$pkg->addMaintainer('lead', 'ezyang', 'Edward Z. Yang', 'admin@htmlpurifier.org', 'yes');
+
+$version = trim(file_get_contents('VERSION'));
+$api_version = substr($version, 0, strrpos($version, '.'));
+
+$pkg->setChannel('htmlpurifier.org');
+$pkg->setAPIVersion($api_version);
+$pkg->setAPIStability('stable');
+$pkg->setReleaseVersion($version);
+$pkg->setReleaseStability('stable');
+
+$pkg->addRelease();
+
+$pkg->setNotes(file_get_contents('WHATSNEW'));
+$pkg->setPackageType('php');
+
+$pkg->setPhpDep('5.0.0');
+$pkg->setPearinstallerDep('1.4.3');
+
+$pkg->generateContents();
+
+$pkg->writePackageFile();
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/phpdoc.ini b/library/ezyang/htmlpurifier/phpdoc.ini
new file mode 100644 (file)
index 0000000..c4c3723
--- /dev/null
@@ -0,0 +1,102 @@
+;; phpDocumentor parse configuration file
+;;
+;; This file is designed to cut down on repetitive typing on the command-line or web interface
+;; You can copy this file to create a number of configuration files that can be used with the
+;; command-line switch -c, as in phpdoc -c default.ini or phpdoc -c myini.ini.  The web
+;; interface will automatically generate a list of .ini files that can be used.
+;;
+;; default.ini is used to generate the online manual at http://www.phpdoc.org/docs
+;;
+;; ALL .ini files must be in the user subdirectory of phpDocumentor with an extension of .ini
+;;
+;; Copyright 2002, Greg Beaver <cellog@users.sourceforge.net>
+;;
+;; WARNING: do not change the name of any command-line parameters, phpDocumentor will ignore them
+
+[Parse Data]
+;; title of all the documentation
+;; legal values: any string
+title = HTML Purifier API Documentation
+
+;; parse files that start with a . like .bash_profile
+;; legal values: true, false
+hidden = false
+
+;; show elements marked @access private in documentation by setting this to on
+;; legal values: on, off
+parseprivate = off
+
+;; parse with javadoc-like description (first sentence is always the short description)
+;; legal values: on, off
+javadocdesc = on
+
+;; add any custom @tags separated by commas here
+;; legal values: any legal tagname separated by commas.
+;customtags = mytag1,mytag2
+
+;; This is only used by the XML:DocBook/peardoc2 converter
+defaultcategoryname = Documentation
+
+;; what is the main package?
+;; legal values: alphanumeric string plus - and _
+defaultpackagename = HTMLPurifier
+
+;; output any parsing information?  set to on for cron jobs
+;; legal values: on
+;quiet = on
+
+;; parse a PEAR-style repository.  Do not turn this on if your project does
+;; not have a parent directory named "pear"
+;; legal values: on/off
+;pear = on
+
+;; where should the documentation be written?
+;; legal values: a legal path
+target = docs/phpdoc
+
+;; Which files should be parsed out as special documentation files, such as README,
+;; INSTALL and CHANGELOG?  This overrides the default files found in
+;; phpDocumentor.ini (this file is not a user .ini file, but the global file)
+readmeinstallchangelog = README, INSTALL, NEWS, WYSIWYG, SLOW, LICENSE, CREDITS
+
+;; limit output to the specified packages, even if others are parsed
+;; legal values: package names separated by commas
+;packageoutput = package1,package2
+
+;; comma-separated list of files to parse
+;; legal values: paths separated by commas
+;filename = /path/to/file1,/path/to/file2,fileincurrentdirectory
+
+;; comma-separated list of directories to parse
+;; legal values: directory paths separated by commas
+;directory = /path1,/path2,.,..,subdirectory
+;directory = /home/jeichorn/cvs/pear
+directory = .
+
+;; template base directory (the equivalent directory of <installdir>/phpDocumentor)
+;templatebase = /path/to/my/templates
+
+;; directory to find any example files in through @example and {@example} tags
+;examplesdir = /path/to/my/templates
+
+;; comma-separated list of files, directories or wildcards ? and * (any wildcard) to ignore
+;; legal values: any wildcard strings separated by commas
+;ignore = /path/to/ignore*,*list.php,myfile.php,subdirectory/
+ignore = *tests*,*benchmarks*,*docs*,*test-settings.php,*configdoc*,*maintenance*,*smoketests*,*standalone*,*.svn*,*conf*
+
+sourcecode = on
+
+;; comma-separated list of Converters to use in outputformat:Convertername:templatedirectory format
+;; legal values: HTML:frames:default,HTML:frames:l0l33t,HTML:frames:phpdoc.de,HTML:frames:phphtmllib,
+;;               HTML:frames:earthli,
+;;               HTML:frames:DOM/default,HTML:frames:DOM/l0l33t,HTML:frames:DOM/phpdoc.de,
+;;               HTML:frames:DOM/phphtmllib,HTML:frames:DOM/earthli
+;;               HTML:Smarty:default,HTML:Smarty:PHP,HTML:Smarty:HandS
+;;               PDF:default:default,CHM:default:default,XML:DocBook/peardoc2:default
+output=HTML:frames:default
+
+;; turn this option on if you want highlighted source code for every file
+;; legal values: on/off
+sourcecode = on
+
+; vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/plugins/modx.txt b/library/ezyang/htmlpurifier/plugins/modx.txt
new file mode 100644 (file)
index 0000000..0763821
--- /dev/null
@@ -0,0 +1,112 @@
+
+MODx Plugin
+
+MODx <http://www.modxcms.com/> is an open source PHP application framework.
+I first came across them in my referrer logs when tillda asked if anyone
+could implement an HTML Purifier plugin.  This forum thread
+<http://modxcms.com/forums/index.php/topic,6604.0.html> eventually resulted
+in the fruition of this plugin that davidm says, "is on top of my favorite
+list."  HTML Purifier goes great with WYSIWYG editors!
+
+
+
+1. Credits
+
+PaulGregory wrote the overall structure of the code.  I added the
+slashes hack.
+
+
+
+2. Install
+
+First, you need to place HTML Purifier library somewhere.  The code here
+assumes that you've placed in MODx's assets/plugins/htmlpurifier (no version
+number).
+
+Log into the manager, and navigate:
+
+Resources > Manage Resources > Plugins tab > New Plugin
+
+Type in a name (probably HTML Purifier), and copy paste this code into the
+textarea:
+
+--------------------------------------------------------------------------------
+$e = &$modx->Event;
+if ($e->name == 'OnBeforeDocFormSave') {
+    global $content;
+
+    include_once '../assets/plugins/htmlpurifier/library/HTMLPurifier.auto.php';
+    $purifier = new HTMLPurifier();
+
+    static $magic_quotes = null;
+    if ($magic_quotes === null) {
+        // this is an ugly hack because this hook hasn't
+        // had the backslashes removed yet when magic_quotes_gpc is on,
+        // but HTMLPurifier must not have the quotes slashed.
+        $magic_quotes = get_magic_quotes_gpc();
+    }
+
+    if ($magic_quotes) $content = stripslashes($content);
+    $content = $purifier->purify($content);
+    if ($magic_quotes) $content = addslashes($content);
+}
+--------------------------------------------------------------------------------
+
+Then navigate to the System Events tab and check "OnBeforeDocFormSave".
+Save the plugin.  HTML Purifier now is integrated!
+
+
+
+3. Making sure it works
+
+You can test HTML Purifier by deliberately putting in crappy HTML and seeing
+whether or not it gets fixed.  A better way is to put in something like this:
+
+<p lang="fr">Il est bon</p>
+
+...and seeing whether or not the content comes out as:
+
+<p lang="fr" xml:lang="fr">Il est bon</p>
+
+(lang to xml:lang synchronization is one of the many features HTML Purifier
+has).
+
+
+
+4. Caveat Emptor
+
+This code does not intercept save requests from the QuickEdit plugin, this may
+be added in a later version.  It also modifies things on save, so there's a
+slight chance that HTML Purifier may make a boo-boo and accidently mess things
+up (the original version is not saved).
+
+Finally, make sure that MODx is using UTF-8.  If you are using, say, a French
+localisation, you may be using Latin-1, if that's the case, configure
+HTML Purifier properly like this:
+
+$config = HTMLPurifier_Config::createDefault();
+$config->set('Core', 'Encoding', 'ISO-8859-1'); // or whatever encoding
+$purifier = new HTMLPurifier($config);
+
+
+
+5. Known Bugs
+
+'rn' characters sometimes mysteriously appear after purification. We are
+currently investigating this issue. See: <http://htmlpurifier.org/phorum/read.php?3,1866>
+
+
+
+6. See Also
+
+A modified version of Jot 1.1.3 is available, which integrates with HTML
+Purifier. You can check it out here: <http://modxcms.com/forums/index.php/topic,25621.msg161970.html>
+
+
+X. Changelog
+
+2008-06-16
+- Updated code to work with 3.1.0 and later
+- Add Known Bugs and See Also section
+
+    vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/plugins/phorum/.gitignore b/library/ezyang/htmlpurifier/plugins/phorum/.gitignore
new file mode 100644 (file)
index 0000000..8325e09
--- /dev/null
@@ -0,0 +1,2 @@
+migrate.php
+htmlpurifier/*
diff --git a/library/ezyang/htmlpurifier/plugins/phorum/Changelog b/library/ezyang/htmlpurifier/plugins/phorum/Changelog
new file mode 100644 (file)
index 0000000..9f939e5
--- /dev/null
@@ -0,0 +1,27 @@
+Changelog                                         HTMLPurifier : Phorum Mod
+|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+= KEY ====================
+    # Breaks back-compat
+    ! Feature
+    - Bugfix
+      + Sub-comment
+    . Internal change
+==========================
+
+Version 4.0.0 for Phorum 5.2, released July 9, 2009
+# Works only with HTML Purifier 4.0.0
+! Better installation documentation
+- Fixed double encoded quotes
+- Fixed fatal error when migrate.php is blank
+
+Version 3.0.0 for Phorum 5.2, released January 12, 2008
+# WYSIWYG and suppress_message options are now configurable via web
+  interface.
+- Module now compatible with Phorum 5.2, primary bugs were in migration
+  code as well as signature and edit message handling. This module is NOT
+  compatible with Phorum 5.1.
+- Buggy WYSIWYG mode refined
+. AutoFormatParam added to list of default configuration namespaces
+
+    vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/plugins/phorum/INSTALL b/library/ezyang/htmlpurifier/plugins/phorum/INSTALL
new file mode 100644 (file)
index 0000000..23c76fc
--- /dev/null
@@ -0,0 +1,84 @@
+
+Install
+    How to install the Phorum HTML Purifier plugin
+
+0. PREREQUISITES
+----------------
+This Phorum module only works on PHP5 and with HTML Purifier 4.0.0
+or later.
+
+1. UNZIP
+--------
+Unzip phorum-htmlpurifier-x.y.z, producing an htmlpurifier folder.
+You've already done this step if you're reading this!
+
+2. MOVE
+-------
+Move the htmlpurifier folder to the mods/ folder of your Phorum
+installation, so the directory structure looks like:
+
+phorum/
+    mods/
+        htmlpurifier/
+            INSTALL - this install file
+            info.txt, ... - the module files
+            htmlpurifier/
+
+3. INSTALL HTML PURIFIER
+------------------------
+Download and unzip HTML Purifier <htmlpurifier.org>. Place the contents of
+the library/ folder in the htmlpurifier/htmlpurifier folder. Your directory
+structure will look like:
+
+phorum/
+    mods/
+        htmlpurifier/
+            htmlpurifier/
+                HTMLPurifier.auto.php
+                ... - other files
+                HTMLPurifier/
+
+Advanced users:
+    If you have HTML Purifier installed elsewhere on your server,
+    all you need is an HTMLPurifier.auto.php file in the library folder which
+    includes the HTMLPurifier.auto.php file in your install.
+
+4. MIGRATE
+----------
+If you're setting up a new Phorum installation, all you need to do is create
+a blank migrate.php file in the htmlpurifier module folder (NOT the library
+folder.
+
+If you have an old Phorum installation and was using BBCode,
+copy migrate.bbcode.php to migrate.php. If you were using a different input
+format, follow the instructions in migrate.bbcode.php to create your own custom
+migrate.php file.
+
+Your directory structure should now look like this:
+
+phorum/
+    mods/
+        htmlpurifier/
+            migrate.php
+
+5. ENABLE
+---------
+Navigate to your Phorum admin panel at http://example.com/phorum/admin.php,
+click on Global Settings > Modules, scroll to "HTML Purifier Phorum Mod" and
+turn it On.
+
+6. MIGRATE SIGNATURES
+---------------------
+If you're setting up a new Phorum installation, skip this step.
+
+If you allowed your users to make signatures, navigate to the module settings
+page of HTML Purifier (Global Settings > Modules > HTML Purifier Phorum Mod >
+Configure), type in "yes" in the "Confirm" box, and press "Migrate."
+
+ONLY DO THIS ONCE! BE SURE TO BACK UP YOUR DATABASE!
+
+7. CONFIGURE
+------------
+Configure using Edit settings. See that page for more information.
+
+    vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/plugins/phorum/README b/library/ezyang/htmlpurifier/plugins/phorum/README
new file mode 100644 (file)
index 0000000..0524ed3
--- /dev/null
@@ -0,0 +1,45 @@
+
+HTML Purifier Phorum Mod - Filter your HTML the Standards-Compliant Way!
+
+This Phorum mod enables HTML posting on Phorum.  Under normal circumstances,
+this would cause a huge security risk, but because we are running
+HTML through HTML Purifier, output is guaranteed to be XSS free and
+standards-compliant.
+
+This mod requires HTML input, and previous markup languages need to be
+converted accordingly.  Thus, it is vital that you create a 'migrate.php'
+file that works with your installation. If you're using the built-in
+BBCode formatting, simply move migrate.bbcode.php to that place; for
+other markup languages, consult said file for instructions on how
+to adapt it to your needs.
+
+    -- NOTE -------------------------------------------------
+    You can also run this module in parallel with another
+    formatting module; this module attempts to place itself
+    at the end of the filtering chain. However, if any
+    previous modules produce insecure HTML (for instance,
+    a JavaScript email obfuscator) they will get cleaned.
+
+This module will not work if 'migrate.php' is not created, and an improperly
+made migration file may *CORRUPT* Phorum, so please take your time to
+do this correctly. It should go without saying to *BACKUP YOUR DATABASE*
+before attempting anything here. If no migration is necessary, you can
+simply create a blank migrate.php file. HTML Purifier is smart and will
+not re-migrate already processed messages. However, the original code
+is irretrievably lost (we may change this in the future.)
+
+This module will not automatically migrate user signatures, because this
+process may take a long time. After installing the HTML Purifier module and
+then configuring 'migrate.php', navigate to Settings and click 'Migrate
+Signatures' to migrate all user signatures to HTML.
+
+All of HTML Purifier's usual functions are configurable via the mod settings
+page. If you require custom configuration, create config.php file in
+the mod directory that edits a $config variable. Be sure, also, to
+set $PHORUM['mod_htmlpurifier']['wysiwyg'] to TRUE if you are using a
+WYSIWYG editor (you can do this through a common hook or the web
+configuration form).
+
+Visit HTML Purifier at <http://htmlpurifier.org/>.
+
+    vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/plugins/phorum/config.default.php b/library/ezyang/htmlpurifier/plugins/phorum/config.default.php
new file mode 100644 (file)
index 0000000..e047c0b
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+
+if(!defined("PHORUM")) exit;
+
+// default HTML Purifier configuration settings
+$config->set('HTML.Allowed',
+  // alphabetically sorted
+'a[href|title]
+abbr[title]
+acronym[title]
+b
+blockquote[cite]
+br
+caption
+cite
+code
+dd
+del
+dfn
+div
+dl
+dt
+em
+i
+img[src|alt|title|class]
+ins
+kbd
+li
+ol
+p
+pre
+s
+strike
+strong
+sub
+sup
+table
+tbody
+td
+tfoot
+th
+thead
+tr
+tt
+u
+ul
+var');
+$config->set('AutoFormat.AutoParagraph', true);
+$config->set('AutoFormat.Linkify', true);
+$config->set('HTML.Doctype', 'XHTML 1.0 Transitional');
+$config->set('Core.AggressivelyFixLt', true);
+$config->set('Core.Encoding', $GLOBALS['PHORUM']['DATA']['CHARSET']); // we'll change this eventually
+if (strtolower($GLOBALS['PHORUM']['DATA']['CHARSET']) !== 'utf-8') {
+  $config->set('Core.EscapeNonASCIICharacters', true);
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/plugins/phorum/htmlpurifier.php b/library/ezyang/htmlpurifier/plugins/phorum/htmlpurifier.php
new file mode 100644 (file)
index 0000000..f66d8c3
--- /dev/null
@@ -0,0 +1,316 @@
+<?php
+
+/**
+ * HTML Purifier Phorum Mod. Filter your HTML the Standards-Compliant Way!
+ *
+ * This Phorum mod enables users to post raw HTML into Phorum.  But never
+ * fear: with the help of HTML Purifier, this HTML will be beat into
+ * de-XSSed and standards-compliant form, safe for general consumption.
+ * It is not recommended, but possible to run this mod in parallel
+ * with other formatters (in short, please DISABLE the BBcode mod).
+ *
+ * For help migrating from your previous markup language to pure HTML
+ * please check the migrate.bbcode.php file.
+ *
+ * If you'd like to use this with a WYSIWYG editor, make sure that
+ * editor sets $PHORUM['mod_htmlpurifier']['wysiwyg'] to true. Otherwise,
+ * administrators who need to edit other people's comments may be at
+ * risk for some nasty attacks.
+ *
+ * Tested with Phorum 5.2.11.
+ */
+
+// Note: Cache data is base64 encoded because Phorum insists on flinging
+// to the user and expecting it to come back unharmed, newlines and
+// all, which ain't happening. It's slower, it takes up more space, but
+// at least it won't get mutilated
+
+/**
+ * Purifies a data array
+ */
+function phorum_htmlpurifier_format($data)
+{
+    $PHORUM = $GLOBALS["PHORUM"];
+
+    $purifier =& HTMLPurifier::getInstance();
+    $cache_serial = $PHORUM['mod_htmlpurifier']['body_cache_serial'];
+
+    foreach($data as $message_id => $message){
+        if(isset($message['body'])) {
+
+            if ($message_id) {
+                // we're dealing with a real message, not a fake, so
+                // there a number of shortcuts that can be taken
+
+                if (isset($message['meta']['htmlpurifier_light'])) {
+                    // format hook was called outside of Phorum's normal
+                    // functions, do the abridged purification
+                    $data[$message_id]['body'] = $purifier->purify($message['body']);
+                    continue;
+                }
+
+                if (!empty($PHORUM['args']['purge'])) {
+                    // purge the cache, must be below the following if
+                    unset($message['meta']['body_cache']);
+                }
+
+                if (
+                    isset($message['meta']['body_cache']) &&
+                    isset($message['meta']['body_cache_serial']) &&
+                    $message['meta']['body_cache_serial'] == $cache_serial
+                ) {
+                    // cached version is present, bail out early
+                    $data[$message_id]['body'] = base64_decode($message['meta']['body_cache']);
+                    continue;
+                }
+            }
+
+            // migration might edit this array, that's why it's defined
+            // so early
+            $updated_message = array();
+
+            // create the $body variable
+            if (
+                $message_id && // message must be real to migrate
+                !isset($message['meta']['body_cache_serial'])
+            ) {
+                // perform migration
+                $fake_data = array();
+                list($signature, $edit_message) = phorum_htmlpurifier_remove_sig_and_editmessage($message);
+                $fake_data[$message_id] = $message;
+                $fake_data = phorum_htmlpurifier_migrate($fake_data);
+                $body = $fake_data[$message_id]['body'];
+                $body = str_replace("<phorum break>\n", "\n", $body);
+                $updated_message['body'] = $body; // save it in
+                $body .= $signature . $edit_message; // add it back in
+            } else {
+                // reverse Phorum's pre-processing
+                $body = $message['body'];
+                // order is important
+                $body = str_replace("<phorum break>\n", "\n", $body);
+                $body = str_replace(array('&lt;','&gt;','&amp;', '&quot;'), array('<','>','&','"'), $body);
+                if (!$message_id && defined('PHORUM_CONTROL_CENTER')) {
+                    // we're in control.php, so it was double-escaped
+                    $body = str_replace(array('&lt;','&gt;','&amp;', '&quot;'), array('<','>','&','"'), $body);
+                }
+            }
+
+            $body = $purifier->purify($body);
+
+            // dynamically update the cache (MUST BE DONE HERE!)
+            // this is inefficient because it's one db call per
+            // cache miss, but once the cache is in place things are
+            // a lot zippier.
+
+            if ($message_id) { // make sure it's not a fake id
+                $updated_message['meta'] = $message['meta'];
+                $updated_message['meta']['body_cache'] = base64_encode($body);
+                $updated_message['meta']['body_cache_serial'] = $cache_serial;
+                phorum_db_update_message($message_id, $updated_message);
+            }
+
+            // must not get overloaded until after we cache it, otherwise
+            // we'll inadvertently change the original text
+            $data[$message_id]['body'] = $body;
+
+        }
+    }
+
+    return $data;
+}
+
+// -----------------------------------------------------------------------
+// This is fragile code, copied from read.php:596 (Phorum 5.2.6). Please
+// keep this code in-sync with Phorum
+
+/**
+ * Generates a signature based on a message array
+ */
+function phorum_htmlpurifier_generate_sig($row)
+{
+    $phorum_sig = '';
+    if(isset($row["user"]["signature"])
+       && isset($row['meta']['show_signature']) && $row['meta']['show_signature']==1){
+           $phorum_sig=trim($row["user"]["signature"]);
+           if(!empty($phorum_sig)){
+               $phorum_sig="\n\n$phorum_sig";
+           }
+    }
+    return $phorum_sig;
+}
+
+/**
+ * Generates an edit message based on a message array
+ */
+function phorum_htmlpurifier_generate_editmessage($row)
+{
+    $PHORUM = $GLOBALS['PHORUM'];
+    $editmessage = '';
+    if(isset($row['meta']['edit_count']) && $row['meta']['edit_count'] > 0) {
+        $editmessage = str_replace ("%count%", $row['meta']['edit_count'], $PHORUM["DATA"]["LANG"]["EditedMessage"]);
+        $editmessage = str_replace ("%lastedit%", phorum_date($PHORUM["short_date_time"],$row['meta']['edit_date']),  $editmessage);
+        $editmessage = str_replace ("%lastuser%", $row['meta']['edit_username'],  $editmessage);
+        $editmessage = "\n\n\n\n$editmessage";
+    }
+    return $editmessage;
+}
+
+// End fragile code
+// -----------------------------------------------------------------------
+
+/**
+ * Removes the signature and edit message from a message
+ * @param $row Message passed by reference
+ */
+function phorum_htmlpurifier_remove_sig_and_editmessage(&$row)
+{
+    $signature = phorum_htmlpurifier_generate_sig($row);
+    $editmessage = phorum_htmlpurifier_generate_editmessage($row);
+    $replacements = array();
+    // we need to remove add <phorum break> as that is the form these
+    // extra bits are in.
+    if ($signature) $replacements[str_replace("\n", "<phorum break>\n", $signature)] = '';
+    if ($editmessage) $replacements[str_replace("\n", "<phorum break>\n", $editmessage)] = '';
+    $row['body'] = strtr($row['body'], $replacements);
+    return array($signature, $editmessage);
+}
+
+/**
+ * Indicate that data is fully HTML and not from migration, invalidate
+ * previous caches
+ * @note This function could generate the actual cache entries, but
+ *       since there's data missing that must be deferred to the first read
+ */
+function phorum_htmlpurifier_posting($message)
+{
+    $PHORUM = $GLOBALS["PHORUM"];
+    unset($message['meta']['body_cache']); // invalidate the cache
+    $message['meta']['body_cache_serial'] = $PHORUM['mod_htmlpurifier']['body_cache_serial'];
+    return $message;
+}
+
+/**
+ * Overload quoting mechanism to prevent default, mail-style quote from happening
+ */
+function phorum_htmlpurifier_quote($array)
+{
+    $PHORUM = $GLOBALS["PHORUM"];
+    $purifier =& HTMLPurifier::getInstance();
+    $text = $purifier->purify($array[1]);
+    $source = htmlspecialchars($array[0]);
+    return "<blockquote cite=\"$source\">\n$text\n</blockquote>";
+}
+
+/**
+ * Ensure that our format hook is processed last. Also, loads the library.
+ * @credits <http://secretsauce.phorum.org/snippets/make_bbcode_last_formatter.php.txt>
+ */
+function phorum_htmlpurifier_common()
+{
+    require_once(dirname(__FILE__).'/htmlpurifier/HTMLPurifier.auto.php');
+    require(dirname(__FILE__).'/init-config.php');
+
+    $config = phorum_htmlpurifier_get_config();
+    HTMLPurifier::getInstance($config);
+
+    // increment revision.txt if you want to invalidate the cache
+    $GLOBALS['PHORUM']['mod_htmlpurifier']['body_cache_serial'] = $config->getSerial();
+
+    // load migration
+    if (file_exists(dirname(__FILE__) . '/migrate.php')) {
+        include(dirname(__FILE__) . '/migrate.php');
+    } else {
+        echo '<strong>Error:</strong> No migration path specified for HTML Purifier, please check
+        <tt>modes/htmlpurifier/migrate.bbcode.php</tt> for instructions on
+        how to migrate from your previous markup language.';
+        exit;
+    }
+
+    if (!function_exists('phorum_htmlpurifier_migrate')) {
+        // Dummy function
+        function phorum_htmlpurifier_migrate($data) {return $data;}
+    }
+
+}
+
+/**
+ * Pre-emptively performs purification if it looks like a WYSIWYG editor
+ * is being used
+ */
+function phorum_htmlpurifier_before_editor($message)
+{
+    if (!empty($GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg'])) {
+        if (!empty($message['body'])) {
+            $body = $message['body'];
+            // de-entity-ize contents
+            $body = str_replace(array('&lt;','&gt;','&amp;'), array('<','>','&'), $body);
+            $purifier =& HTMLPurifier::getInstance();
+            $body = $purifier->purify($body);
+            // re-entity-ize contents
+            $body = htmlspecialchars($body, ENT_QUOTES, $GLOBALS['PHORUM']['DATA']['CHARSET']);
+            $message['body'] = $body;
+        }
+    }
+    return $message;
+}
+
+function phorum_htmlpurifier_editor_after_subject()
+{
+    // don't show this message if it's a WYSIWYG editor, since it will
+    // then be handled automatically
+    if (!empty($GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg'])) {
+        $i = $GLOBALS['PHORUM']['DATA']['MODE'];
+        if ($i == 'quote' || $i == 'edit' || $i == 'moderation') {
+          ?>
+          <div>
+            <p>
+              <strong>Notice:</strong> HTML has been scrubbed for your safety.
+              If you would like to see the original, turn off WYSIWYG mode
+              (consult your administrator for details.)
+            </p>
+          </div>
+          <?php
+        }
+        return;
+    }
+    if (!empty($GLOBALS['PHORUM']['mod_htmlpurifier']['suppress_message'])) return;
+    ?><div class="htmlpurifier-help">
+    <p>
+        <strong>HTML input</strong> is enabled. Make sure you escape all HTML and
+        angled brackets with <code>&amp;lt;</code> and <code>&amp;gt;</code>.
+    </p><?php
+            $purifier =& HTMLPurifier::getInstance();
+            $config = $purifier->config;
+            if ($config->get('AutoFormat.AutoParagraph')) {
+                ?><p>
+                    <strong>Auto-paragraphing</strong> is enabled. Double
+                    newlines will be converted to paragraphs; for single
+                    newlines, use the <code>pre</code> tag.
+                </p><?php
+            }
+            $html_definition = $config->getDefinition('HTML');
+            $allowed = array();
+            foreach ($html_definition->info as $name => $x) $allowed[] = "<code>$name</code>";
+            sort($allowed);
+            $allowed_text = implode(', ', $allowed);
+            ?><p><strong>Allowed tags:</strong> <?php
+            echo $allowed_text;
+            ?>.</p><?php
+        ?>
+    </p>
+    <p>
+        For inputting literal code such as HTML and PHP for display, use
+        CDATA tags to auto-escape your angled brackets, and <code>pre</code>
+        to preserve newlines:
+    </p>
+    <pre>&lt;pre&gt;&lt;![CDATA[
+<em>Place code here</em>
+]]&gt;&lt;/pre&gt;</pre>
+    <p>
+        Power users, you can hide this notice with:
+        <pre>.htmlpurifier-help {display:none;}</pre>
+    </p>
+    </div><?php
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/plugins/phorum/info.txt b/library/ezyang/htmlpurifier/plugins/phorum/info.txt
new file mode 100644 (file)
index 0000000..7234654
--- /dev/null
@@ -0,0 +1,18 @@
+title:   HTML Purifier Phorum Mod
+desc:    This module enables standards-compliant HTML filtering on Phorum. Please check migrate.bbcode.php before enabling this mod.
+author:  Edward Z. Yang
+url:     http://htmlpurifier.org/
+version: 4.0.0
+
+hook:  format|phorum_htmlpurifier_format
+hook:  quote|phorum_htmlpurifier_quote
+hook:  posting_custom_action|phorum_htmlpurifier_posting
+hook:  common|phorum_htmlpurifier_common
+hook:  before_editor|phorum_htmlpurifier_before_editor
+hook:  tpl_editor_after_subject|phorum_htmlpurifier_editor_after_subject
+
+# This module is meant to be a drop-in for bbcode, so make it run last.
+priority: run module after *
+priority: run hook format after *
+
+    vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/plugins/phorum/init-config.php b/library/ezyang/htmlpurifier/plugins/phorum/init-config.php
new file mode 100644 (file)
index 0000000..e19787b
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * Initializes the appropriate configuration from either a PHP file
+ * or a module configuration value
+ * @return Instance of HTMLPurifier_Config
+ */
+function phorum_htmlpurifier_get_config($default = false)
+{
+    global $PHORUM;
+    $config_exists = phorum_htmlpurifier_config_file_exists();
+    if ($default || $config_exists || !isset($PHORUM['mod_htmlpurifier']['config'])) {
+        $config = HTMLPurifier_Config::createDefault();
+        include(dirname(__FILE__) . '/config.default.php');
+        if ($config_exists) {
+            include(dirname(__FILE__) . '/config.php');
+        }
+        unset($PHORUM['mod_htmlpurifier']['config']); // unnecessary
+    } else {
+        $config = HTMLPurifier_Config::create($PHORUM['mod_htmlpurifier']['config']);
+    }
+    return $config;
+}
+
+function phorum_htmlpurifier_config_file_exists()
+{
+    return file_exists(dirname(__FILE__) . '/config.php');
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/plugins/phorum/migrate.bbcode.php b/library/ezyang/htmlpurifier/plugins/phorum/migrate.bbcode.php
new file mode 100644 (file)
index 0000000..0d09194
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * This file is responsible for migrating from a specific markup language
+ * like BBCode or Markdown to HTML. WARNING: THIS PROCESS IS NOT REVERSIBLE
+ *
+ * Copy this file to 'migrate.php' and it will automatically work for
+ * BBCode; you may need to tweak this a little to get it to work for other
+ * languages (usually, just replace the include name and the function name).
+ *
+ * If you do NOT want to have any migration performed (for instance, you
+ * are installing the module on a new forum with no posts), simply remove
+ * phorum_htmlpurifier_migrate() function. You still need migrate.php
+ * present, otherwise the module won't work. This ensures that the user
+ * explicitly says, "No, I do not need to migrate."
+ */
+
+if(!defined("PHORUM")) exit;
+
+require_once(dirname(__FILE__) . "/../bbcode/bbcode.php");
+
+/**
+ * 'format' hook style function that will be called to convert
+ * legacy markup into HTML.
+ */
+function phorum_htmlpurifier_migrate($data)
+{
+    return phorum_mod_bbcode_format($data); // bbcode's 'format' hook
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/plugins/phorum/settings.php b/library/ezyang/htmlpurifier/plugins/phorum/settings.php
new file mode 100644 (file)
index 0000000..8158f02
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+
+// based off of BBCode's settings file
+
+/**
+ * HTML Purifier Phorum mod settings configuration. This provides
+ * a convenient web-interface for editing the most common HTML Purifier
+ * configuration directives. You can also specify custom configuration
+ * by creating a 'config.php' file.
+ */
+
+if(!defined("PHORUM_ADMIN")) exit;
+
+// error reporting is good!
+error_reporting(E_ALL ^ E_NOTICE);
+
+// load library and other paraphenalia
+require_once './include/admin/PhorumInputForm.php';
+require_once (dirname(__FILE__) . '/htmlpurifier/HTMLPurifier.auto.php');
+require_once (dirname(__FILE__) . '/init-config.php');
+require_once (dirname(__FILE__) . '/settings/migrate-sigs-form.php');
+require_once (dirname(__FILE__) . '/settings/migrate-sigs.php');
+require_once (dirname(__FILE__) . '/settings/form.php');
+require_once (dirname(__FILE__) . '/settings/save.php');
+
+// define friendly configuration directives. you can expand this array
+// to get more web-definable directives
+$PHORUM['mod_htmlpurifier']['directives'] = array(
+    'URI.Host', // auto-detectable
+    'URI.DisableExternal',
+    'URI.DisableExternalResources',
+    'URI.DisableResources',
+    'URI.Munge',
+    'URI.HostBlacklist',
+    'URI.Disable',
+    'HTML.TidyLevel',
+    'HTML.Doctype', // auto-detectable
+    'HTML.Allowed',
+    'AutoFormat',
+    '-AutoFormat.Custom',
+    'AutoFormatParam',
+    'Output.TidyFormat',
+);
+
+// lower this setting if you're getting time outs/out of memory
+$PHORUM['mod_htmlpurifier']['migrate-sigs-increment'] = 100;
+
+if (isset($_POST['reset'])) {
+    unset($PHORUM['mod_htmlpurifier']['config']);
+}
+
+if ($offset = phorum_htmlpurifier_migrate_sigs_check()) {
+    // migrate signatures
+    phorum_htmlpurifier_migrate_sigs($offset);
+} elseif(!empty($_POST)){
+    // save settings
+    phorum_htmlpurifier_save_settings();
+}
+
+phorum_htmlpurifier_show_migrate_sigs_form();
+echo '<br />';
+phorum_htmlpurifier_show_form();
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/plugins/phorum/settings/form.php b/library/ezyang/htmlpurifier/plugins/phorum/settings/form.php
new file mode 100644 (file)
index 0000000..9b6ad5f
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+
+function phorum_htmlpurifier_show_form()
+{
+    if (phorum_htmlpurifier_config_file_exists()) {
+        phorum_htmlpurifier_show_config_info();
+        return;
+    }
+
+    global $PHORUM;
+
+    $config = phorum_htmlpurifier_get_config();
+
+    $frm = new PhorumInputForm ("", "post", "Save");
+    $frm->hidden("module", "modsettings");
+    $frm->hidden("mod", "htmlpurifier"); // this is the directory name that the Settings file lives in
+
+    if (!empty($error)){
+        echo "$error<br />";
+    }
+
+    $frm->addbreak("Edit settings for the HTML Purifier module");
+
+    $frm->addMessage('<p>The box below sets <code>$PHORUM[\'mod_htmlpurifier\'][\'wysiwyg\']</code>.
+    When checked, contents sent for edit are now purified and the
+    informative message is disabled. If your WYSIWYG editor is disabled for
+    admin edits, you can safely keep this unchecked.</p>');
+    $frm->addRow('Use WYSIWYG?', $frm->checkbox('wysiwyg', '1', '', $PHORUM['mod_htmlpurifier']['wysiwyg']));
+
+    $frm->addMessage('<p>The box below sets <code>$PHORUM[\'mod_htmlpurifier\'][\'suppress_message\']</code>,
+    which removes the big how-to use
+    HTML Purifier message.</p>');
+    $frm->addRow('Suppress information?', $frm->checkbox('suppress_message', '1', '', $PHORUM['mod_htmlpurifier']['suppress_message']));
+
+    $frm->addMessage('<p>Click on directive links to read what each option does
+    (links do not open in new windows).</p>
+    <p>For more flexibility (for instance, you want to edit the full
+    range of configuration directives), you can create a <tt>config.php</tt>
+    file in your <tt>mods/htmlpurifier/</tt> directory. Doing so will,
+    however, make the web configuration interface unavailable.</p>');
+
+    require_once 'HTMLPurifier/Printer/ConfigForm.php';
+    $htmlpurifier_form = new HTMLPurifier_Printer_ConfigForm('config', 'http://htmlpurifier.org/live/configdoc/plain.html#%s');
+    $htmlpurifier_form->setTextareaDimensions(23, 7); // widen a little, since we have space
+
+    $frm->addMessage($htmlpurifier_form->render(
+        $config, $PHORUM['mod_htmlpurifier']['directives'], false));
+
+    $frm->addMessage("<strong>Warning: Changing HTML Purifier's configuration will invalidate
+      the cache. Expect to see a flurry of database activity after you change
+      any of these settings.</strong>");
+
+    $frm->addrow('Reset to defaults:', $frm->checkbox("reset", "1", "", false));
+
+    // hack to include extra styling
+    echo '<style type="text/css">' . $htmlpurifier_form->getCSS() . '
+    .hp-config {margin-left:auto;margin-right:auto;}
+    </style>';
+    $js = $htmlpurifier_form->getJavaScript();
+    echo '<script type="text/javascript">'."<!--\n$js\n//-->".'</script>';
+
+    $frm->show();
+}
+
+function phorum_htmlpurifier_show_config_info()
+{
+    global $PHORUM;
+
+    // update mod_htmlpurifier for housekeeping
+    phorum_htmlpurifier_commit_settings();
+
+    // politely tell user how to edit settings manually
+?>
+        <div class="input-form-td-break">How to edit settings for HTML Purifier module</div>
+        <p>
+          A <tt>config.php</tt> file exists in your <tt>mods/htmlpurifier/</tt>
+          directory. This file contains your custom configuration: in order to
+          change it, please navigate to that file and edit it accordingly.
+          You can also set <code>$GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg']</code>
+          or <code>$GLOBALS['PHORUM']['mod_htmlpurifier']['suppress_message']</code>
+        </p>
+        <p>
+          To use the web interface, delete <tt>config.php</tt> (or rename it to
+          <tt>config.php.bak</tt>).
+        </p>
+        <p>
+          <strong>Warning: Changing HTML Purifier's configuration will invalidate
+          the cache. Expect to see a flurry of database activity after you change
+          any of these settings.</strong>
+        </p>
+<?php
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs-form.php b/library/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs-form.php
new file mode 100644 (file)
index 0000000..abea3b5
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+function phorum_htmlpurifier_show_migrate_sigs_form()
+{
+    $frm = new PhorumInputForm ('', "post", "Migrate");
+    $frm->hidden("module", "modsettings");
+    $frm->hidden("mod", "htmlpurifier");
+    $frm->hidden("migrate-sigs", "1");
+    $frm->addbreak("Migrate user signatures to HTML");
+    $frm->addMessage('This operation will migrate your users signatures
+        to HTML. <strong>This process is irreversible and must only be performed once.</strong>
+        Type in yes in the confirmation field to migrate.');
+    if (!file_exists(dirname(__FILE__) . '/../migrate.php')) {
+        $frm->addMessage('Migration file does not exist, cannot migrate signatures.
+            Please check <tt>migrate.bbcode.php</tt> on how to create an appropriate file.');
+    } else {
+        $frm->addrow('Confirm:', $frm->text_box("confirmation", ""));
+    }
+    $frm->show();
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs.php b/library/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs.php
new file mode 100644 (file)
index 0000000..5ea9cd0
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+
+function phorum_htmlpurifier_migrate_sigs_check()
+{
+    global $PHORUM;
+    $offset = 0;
+    if (!empty($_POST['migrate-sigs'])) {
+        if (!isset($_POST['confirmation']) || strtolower($_POST['confirmation']) !== 'yes') {
+            echo 'Invalid confirmation code.';
+            exit;
+        }
+        $PHORUM['mod_htmlpurifier']['migrate-sigs'] = true;
+        phorum_db_update_settings(array("mod_htmlpurifier"=>$PHORUM["mod_htmlpurifier"]));
+        $offset = 1;
+    } elseif (!empty($_GET['migrate-sigs']) && $PHORUM['mod_htmlpurifier']['migrate-sigs']) {
+        $offset = (int) $_GET['migrate-sigs'];
+    }
+    return $offset;
+}
+
+function phorum_htmlpurifier_migrate_sigs($offset)
+{
+    global $PHORUM;
+
+    if(!$offset) return; // bail out quick if $offset == 0
+
+    // theoretically, we could get rid of this multi-request
+    // doo-hickery if safe mode is off
+    @set_time_limit(0); // attempt to let this run
+    $increment = $PHORUM['mod_htmlpurifier']['migrate-sigs-increment'];
+
+    require_once(dirname(__FILE__) . '/../migrate.php');
+    // migrate signatures
+    // do this in batches so we don't run out of time/space
+    $end = $offset + $increment;
+    $user_ids = array();
+    for ($i = $offset; $i < $end; $i++) {
+        $user_ids[] = $i;
+    }
+    $userinfos = phorum_db_user_get_fields($user_ids, 'signature');
+    foreach ($userinfos as $i => $user) {
+        if (empty($user['signature'])) continue;
+        $sig = $user['signature'];
+        // perform standard Phorum processing on the sig
+        $sig = str_replace(array("&","<",">"), array("&amp;","&lt;","&gt;"), $sig);
+        $sig = preg_replace("/<((http|https|ftp):\/\/[a-z0-9;\/\?:@=\&\$\-_\.\+!*'\(\),~%]+?)>/i", "$1", $sig);
+        // prepare fake data to pass to migration function
+        $fake_data = array(array("author"=>"", "email"=>"", "subject"=>"", 'body' => $sig));
+        list($fake_message) = phorum_htmlpurifier_migrate($fake_data);
+        $user['signature'] = $fake_message['body'];
+        if (!phorum_api_user_save($user)) {
+            exit('Error while saving user data');
+        }
+    }
+    unset($userinfos); // free up memory
+
+    // query for highest ID in database
+    $type = $PHORUM['DBCONFIG']['type'];
+    $sql = "select MAX(user_id) from {$PHORUM['user_table']}";
+    $row = phorum_db_interact(DB_RETURN_ROW, $sql);
+    $top_id = (int) $row[0];
+
+    $offset += $increment;
+    if ($offset > $top_id) { // test for end condition
+        echo 'Migration finished';
+        $PHORUM['mod_htmlpurifier']['migrate-sigs'] = false;
+        phorum_htmlpurifier_commit_settings();
+        return true;
+    }
+    $host  = $_SERVER['HTTP_HOST'];
+    $uri   = rtrim(dirname($_SERVER['PHP_SELF']), '/\\');
+    $extra = 'admin.php?module=modsettings&mod=htmlpurifier&migrate-sigs=' . $offset;
+    // relies on output buffering to work
+    header("Location: http://$host$uri/$extra");
+    exit;
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/plugins/phorum/settings/save.php b/library/ezyang/htmlpurifier/plugins/phorum/settings/save.php
new file mode 100644 (file)
index 0000000..2aefaf8
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+function phorum_htmlpurifier_save_settings()
+{
+    global $PHORUM;
+    if (phorum_htmlpurifier_config_file_exists()) {
+        echo "Cannot update settings, <code>mods/htmlpurifier/config.php</code> already exists. To change
+        settings, edit that file. To use the web form, delete that file.<br />";
+    } else {
+        $config = phorum_htmlpurifier_get_config(true);
+        if (!isset($_POST['reset'])) $config->mergeArrayFromForm($_POST, 'config', $PHORUM['mod_htmlpurifier']['directives']);
+        $PHORUM['mod_htmlpurifier']['config'] = $config->getAll();
+    }
+    $PHORUM['mod_htmlpurifier']['wysiwyg'] = !empty($_POST['wysiwyg']);
+    $PHORUM['mod_htmlpurifier']['suppress_message'] = !empty($_POST['suppress_message']);
+    if(!phorum_htmlpurifier_commit_settings()){
+        $error="Database error while updating settings.";
+    } else {
+        echo "Settings Updated<br />";
+    }
+}
+
+function phorum_htmlpurifier_commit_settings()
+{
+    global $PHORUM;
+    return phorum_db_update_settings(array("mod_htmlpurifier"=>$PHORUM["mod_htmlpurifier"]));
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/release1-update.php b/library/ezyang/htmlpurifier/release1-update.php
new file mode 100644 (file)
index 0000000..834d385
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+
+// release script
+// PHP 5.0 only
+
+if (php_sapi_name() != 'cli') {
+    echo 'Release script cannot be called from web-browser.';
+    exit;
+}
+
+if (!isset($argv[1])) {
+    echo
+'php release.php [version]
+    HTML Purifier release script
+';
+    exit;
+}
+
+$version = trim($argv[1]);
+
+// Bump version numbers:
+
+// ...in VERSION
+file_put_contents('VERSION', $version);
+
+// ...in NEWS
+if ($is_dev = (strpos($version, 'dev') === false)) {
+  $date = date('Y-m-d');
+  $news_c = str_replace(
+      $l = "$version, unknown release date",
+      "$version, released $date",
+      file_get_contents('NEWS'),
+      $c
+  );
+  if (!$c) {
+      echo 'Could not update NEWS, missing ' . $l . PHP_EOL;
+      exit;
+  } elseif ($c > 1) {
+      echo 'More than one release declaration in NEWS replaced' . PHP_EOL;
+      exit;
+  }
+  file_put_contents('NEWS', $news_c);
+}
+
+// ...in Doxyfile
+$doxyfile_c = preg_replace(
+    '/(?<=PROJECT_NUMBER {9}= )[^\s]+/m', // brittle
+    $version,
+    file_get_contents('Doxyfile'),
+    1, $c
+);
+if (!$c) {
+    echo 'Could not update Doxyfile, missing PROJECT_NUMBER.' . PHP_EOL;
+    exit;
+}
+file_put_contents('Doxyfile', $doxyfile_c);
+
+// ...in HTMLPurifier.php
+$htmlpurifier_c = file_get_contents('library/HTMLPurifier.php');
+$htmlpurifier_c = preg_replace(
+    '/HTML Purifier .+? - /',
+    "HTML Purifier $version - ",
+    $htmlpurifier_c,
+    1, $c
+);
+if (!$c) {
+    echo 'Could not update HTMLPurifier.php, missing HTML Purifier [version] header.' . PHP_EOL;
+    exit;
+}
+$htmlpurifier_c = preg_replace(
+    '/public \$version = \'.+?\';/',
+    "public \$version = '$version';",
+    $htmlpurifier_c,
+    1, $c
+);
+if (!$c) {
+    echo 'Could not update HTMLPurifier.php, missing public $version.' . PHP_EOL;
+    exit;
+}
+$htmlpurifier_c = preg_replace(
+    '/const VERSION = \'.+?\';/',
+    "const VERSION = '$version';",
+    $htmlpurifier_c,
+    1, $c
+);
+if (!$c) {
+    echo 'Could not update HTMLPurifier.php, missing const $version.' . PHP_EOL;
+    exit;
+}
+file_put_contents('library/HTMLPurifier.php', $htmlpurifier_c);
+
+$config_c = file_get_contents('library/HTMLPurifier/Config.php');
+$config_c = preg_replace(
+    '/public \$version = \'.+?\';/',
+    "public \$version = '$version';",
+    $config_c,
+    1, $c
+);
+if (!$c) {
+    echo 'Could not update Config.php, missing public $version.' . PHP_EOL;
+    exit;
+}
+file_put_contents('library/HTMLPurifier/Config.php', $config_c);
+
+passthru('php maintenance/flush.php');
+
+if ($is_dev) echo "Review changes, write something in WHATSNEW and FOCUS, and then commit with log 'Release $version.'" . PHP_EOL;
+else echo "Numbers updated to dev, no other modifications necessary!";
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/release2-tag.php b/library/ezyang/htmlpurifier/release2-tag.php
new file mode 100644 (file)
index 0000000..25e5300
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+// Tags releases
+
+if (php_sapi_name() != 'cli') {
+    echo 'Release script cannot be called from web-browser.';
+    exit;
+}
+
+require 'svn.php';
+
+$svn_info = my_svn_info('.');
+
+$version = trim(file_get_contents('VERSION'));
+
+$trunk_url  = $svn_info['Repository Root'] . '/htmlpurifier/trunk';
+$trunk_tag_url  = $svn_info['Repository Root'] . '/htmlpurifier/tags/' . $version;
+
+echo "Tagging trunk to tags/$version...";
+passthru("svn copy --message \"Tag $version release.\" $trunk_url $trunk_tag_url");
+
+// vim: et sw=4 sts=4
diff --git a/library/ezyang/htmlpurifier/test-settings.sample.php b/library/ezyang/htmlpurifier/test-settings.sample.php
new file mode 100644 (file)
index 0000000..886b974
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+
+// ATTENTION! DO NOT EDIT THIS FILE!
+// This file is necessary to run the unit tests and profiling scripts.
+// Please copy it to 'test-settings.php' and make the necessary edits.
+
+// Note: The only external library you *need* is SimpleTest; everything else
+//       is optional.
+
+// We've got a lot of tests, so we recommend turning the limit off.
+set_time_limit(0);
+
+// Turning off output buffering will prevent mysterious errors from core dumps.
+$data = @ob_get_clean();
+if ($data !== false && $data !== '') {
+    echo "Output buffer contains data [".urlencode($data)."]\n";
+    exit;
+}
+
+// -----------------------------------------------------------------------------
+// REQUIRED SETTINGS
+
+// Note on running SimpleTest:
+//      Because HTML Purifier is PHP5-only and E_STRICT compliant, SimpleTest
+//      1.0.1 will not work; you need to run SimpleTest off its trunk using:
+//
+//        $ svn co https://simpletest.svn.sourceforge.net/svnroot/simpletest/simpletest/trunk simpletest
+//
+//      If SimpleTest is borked with HTML Purifier, please contact me or
+//      the SimpleTest devs; I am a developer for SimpleTest so I should be
+//      able to quickly assess a fix. SimpleTest's problem is my problem!
+
+// Where is SimpleTest located? Remember to include a trailing slash!
+$simpletest_location = '/path/to/simpletest/';
+
+// -----------------------------------------------------------------------------
+// OPTIONAL SETTINGS
+
+// Note on running PHPT:
+//      Vanilla PHPT from https://github.com/tswicegood/PHPT_Core should
+//      work fine on Linux w/o multitest.
+//
+//      To do multitest or Windows testing, you'll need some more
+//      patches at https://github.com/ezyang/PHPT_Core
+//
+//      I haven't tested the Windows setup in a while so I don't know if
+//      it still works.
+
+// Should PHPT tests be enabled?
+$GLOBALS['HTMLPurifierTest']['PHPT'] = false;
+
+// If PHPT isn't in your Path via PEAR, set that here:
+// set_include_path('/path/to/phpt/Core/src' . PATH_SEPARATOR . get_include_path());
+
+// Where is CSSTidy located? (Include trailing slash. Leave false to disable.)
+$csstidy_location    = false;
+
+// For tests/multitest.php, which versions to test?
+$versions_to_test    = array();
+
+// Stable PHP binary to use when invoking maintenance scripts.
+$php = 'php';
+
+// For tests/multitest.php, what is the multi-version executable? It must
+// accept an extra parameter (version number) before all other arguments
+$phpv = false;
+
+// Should PEAR tests be run? If you've got a valid PEAR installation, set this
+// to true (or, if it's not in the include path, to its install directory).
+$GLOBALS['HTMLPurifierTest']['PEAR'] = false;
+
+// If PEAR is enabled, what PEAR tests should be run? (Note: you will
+// need to ensure these libraries are installed)
+$GLOBALS['HTMLPurifierTest']['Net_IDNA2'] = true;
+
+// vim: et sw=4 sts=4
diff --git a/library/perfect-scrollbar/README.md b/library/perfect-scrollbar/README.md
new file mode 100644 (file)
index 0000000..b0e7328
--- /dev/null
@@ -0,0 +1,405 @@
+# `perfect-scrollbar`
+
+Minimalistic but perfect custom scrollbar plugin
+
+[![Travis CI](https://travis-ci.org/noraesae/perfect-scrollbar.svg?branch=master)](https://travis-ci.org/noraesae/perfect-scrollbar)
+
+If you want information of old versions&lt;0.6.0, please refer to
+[an old documentation](https://github.com/noraesae/perfect-scrollbar/tree/0.5.9).
+
+## Why perfect-scrollbar?
+
+I was once working on a personal project, and trying to find the jQuery
+scrollbar plugin that's *perfect*. But there was no *perfect* one.
+That's why I decided to make one.
+
+perfect-scrollbar is minimalistic but *perfect* (for me, and maybe for most developers)
+scrollbar plugin working with jQuery or vanilla JavaScript as well.
+
+I hope you love it!
+
+## [Demo](http://noraesae.github.com/perfect-scrollbar/)
+
+[It's on the GitHub Pages](http://noraesae.github.com/perfect-scrollbar/).
+
+## What does *perfect* mean?
+
+*perfect* means...
+
+* There should be no css change on any original element.
+* The scrollbar should not affect the original design layout.
+* The design of the scrollbar should be (nearly) fully customizable.
+* If the size of the container or the content changes, the scrollbar
+  size and position should be able to change.
+* *New!* It should work with vanilla JavaScript and major tools like
+  NPM or Browserify.
+
+## Then perfect-scrollbar is really *perfect*?
+
+* perfect-scrollbar has some requirements, but doesn't change or add
+  any style on original elements.
+* perfect-scrollbar is designed not to have width or height. It's fixed
+  on the right and bottom side of the container.
+* You can change nearly all css styles of the scrollbar. The scrollbar
+  design has no dependency on scripts.
+* perfect-scrollbar supports an 'update' function. Whenever you need
+  to update the size or position of the scrollbar, just update.
+* Additionally, perfect-scrollbar uses 'scrollTop' and 'scrollLeft',
+  not absolute positioning or something messy.
+* perfect-scrollbar supports RTL perfectly on both WebKit and Gecko based browsers.
+
+It's cool, isn't it?
+
+## Install
+
+The best way to install and use perfect-scrollbar is with NPM.
+It's registered on [npm](https://www.npmjs.org/package/perfect-scrollbar) as `perfect-scrollbar`.
+
+```
+$ npm install perfect-scrollbar
+```
+
+You can download the latest stable version with download links [here](http://noraesae.github.io/perfect-scrollbar/).
+You also can find all releases on [Releases](https://github.com/noraesae/perfect-scrollbar/releases).
+
+If you want to use the development version of the plugin, use the
+source files which are not minified. They're in the `src` directory.
+The development version may be unstable, but some known bugs may
+have been fixed.
+
+```
+$ git clone https://github.com/noraesae/perfect-scrollbar.git
+$ cd perfect-scrollbar/src
+$ npm install
+$ gulp # will lint and build the source code.
+```
+
+
+There is a Bower package for perfect-scrollbar as well. It is managed
+under the [perfect-scrollbar-bower](https://github.com/noraesae/perfect-scrollbar-bower)
+repository. The plugin is registered as `perfect-scrollbar`.
+
+```
+$ bower install perfect-scrollbar
+```
+
+
+You can also load it from [cdnjs](http://cdnjs.com/).
+It is registered as [`jquery.perfect-scrollbar`](http://www.cdnjs.com/libraries/jquery.perfect-scrollbar).
+
+## Requirements
+
+To make this plugin *perfect*, some requirements were unavoidable.
+But, they're all very trivial and there is nothing to worry about.
+
+The following requirements should meet.
+
+* the container must have a 'position' css style.
+
+The following requirements are included in the basic CSS, but please
+keep in mind when you'd like to change the CSS files.
+
+* the container must have an 'overflow:hidden' css style.
+* the scrollbar's position must be 'absolute'.
+* the scrollbar-x must have a 'bottom' css style, and the scrollbar-y
+  must have a 'right' css style.
+
+## How to use
+
+First of all, please check if the container element meets the
+requirements.
+
+```html
+<link rel='stylesheet' href='dist/css/perfect-scrollbar.css' />
+<style>
+  #container {
+    position: relative;
+    height: 100%; /* Or whatever you want (eg. 400px) */
+  }
+</style>
+```
+
+I would recommend using the plugin with NPM and CommonJS module system
+like Browserify.
+
+```javascript
+var Ps = require('perfect-scrollbar');
+```
+
+Or you can just load the script file as usual.
+
+```html
+<script src='dist/js/perfect-scrollbar.js'></script>
+```
+
+To initialise the plugin, `Ps.initialize` is used.
+
+```javascript
+var container = document.getElementById('container');
+Ps.initialize(container);
+```
+
+It can be initialised with optional parameters.
+
+```javascript
+Ps.initialize(container, {
+  wheelSpeed: 2,
+  wheelPropagation: true,
+  minScrollbarLength: 20
+});
+```
+
+If the size of your container or content changes, call `update`.
+
+```javascript
+Ps.update(container);
+```
+
+If you want to destory the scrollbar, use `destroy`.
+
+```javascript
+Ps.destroy(container);
+```
+
+If you want to scroll to somewhere, just use a `scrollTop`
+property and update.
+
+```javascript
+container.scrollTop = 0;
+Ps.update(container);
+```
+
+You can also get information about how to use the plugin
+from code in the `examples` directory of the source tree.
+
+## jQuery
+
+As you may already know, perfect-scrollbar was a jQuery plugin.
+And it *is* as well. There's a jQuery adaptor and the plugin can
+be used in the same way it used to be used before.
+
+I also recommend using NPM and CommonJS here, but it's not mandatory.
+
+```javascript
+var $ = require('jquery');
+require('perfect-scrollbar/jquery')($);
+```
+
+For sure, you can just import a built script.
+
+```html
+<script src='dist/js/perfect-scrollbar.jquery.js'></script>
+```
+
+After importing it, you can use the plugin in the usual way.
+
+```javascript
+$('#container').perfectScrollbar();          // Initialize
+$('#container').perfectScrollbar({ ... });   // with options
+$('#container').perfectScrollbar('update');  // Update
+$('#container').perfectScrollbar('destroy'); // Destroy
+```
+
+## RequireJS  usage
+
+For RequireJS loader, no need to write shim, simply import two libs:
+
+```javascript
+require.config({
+  paths: {
+    perfectScrollbarJQuery: '.../perfect-scrollbar.jquery',
+    perfectScrollbar: '.../perfect-scrollbar',
+  }
+  ...
+})
+```
+
+
+and load `perfectScrollbar` in the initialiser of your app:
+
+```javascript
+// for vanilla JS:
+window.Ps = require('perfectScrollbar');
+
+// for jQuery:
+require('perfectScrollbarJQuery');
+```
+
+
+## AngularJS + RequireJS usage
+
+With the require.config settings above, at the beginning of your app module
+definition, you can have following code:
+
+```javascript
+define([
+  'angular',
+  'perfectScrollbar',
+  'perfectScrollbarJquery'
+],
+function (angular) {
+  var myApp = angular.module('myApp', [])
+  .run(function() {
+    window.Ps = require('perfectScrollbar');
+    require('perfectScrollbarJQuery');
+  })
+  return myApp;
+});
+```
+
+And initialise perfectScrollbar in a controller:
+
+```javascript
+// by vanilla JS:
+var container = document.getElementById('imgLoader');
+Ps.initialize(container);
+Ps.update(container);
+
+// or by jQuery:
+var imgLoader = $("#imgLoader")
+imgLoader.perfectScrollbar();
+```
+
+## Optional parameters
+
+perfect-scrollbar supports optional parameters.
+
+### wheelSpeed
+The scroll speed applied to mousewheel event.  
+**Default: 1**
+
+### wheelPropagation
+If this option is true, when the scroll reaches the end of the side, mousewheel event will be propagated to parent element.  
+**Default: false**
+
+### swipePropagation
+If this option is true, when the scroll reaches the end of the side, touch scrolling will be propagated to parent element.  
+**Default: true**
+
+### minScrollbarLength
+When set to an integer value, the thumb part of the scrollbar will not shrink below that number of pixels.  
+**Default: null**
+
+### maxScrollbarLength
+When set to an integer value, the thumb part of the scrollbar will not expand over that number of pixels.  
+**Default: null**
+
+### useBothWheelAxes
+When set to true, and only one (vertical or horizontal) scrollbar is visible then both vertical and horizontal scrolling will affect the scrollbar.  
+**Default: false**
+
+### useKeyboard
+When set to true, the scroll works with arrow keys on the keyboard. The element is scrolled only when the mouse cursor hovers the element.  
+**Default: true**
+
+### suppressScrollX
+When set to true, the scroll bar in X axis will not be available, regardless of the content width.  
+**Default: false**
+
+### suppressScrollY
+When set to true, the scroll bar in Y axis will not be available, regardless of the content height.  
+**Default: false**
+
+### scrollXMarginOffset
+The number of pixels the content width can surpass the container width without enabling the X axis scroll bar. Allows some "wiggle room" or "offset break", so that X axis scroll bar is not enabled just because of a few pixels.  
+**Default: 0**
+
+### scrollYMarginOffset
+The number of pixels the content height can surpass the container height without enabling the Y axis scroll bar. Allows some "wiggle room" or "offset break", so that Y axis scroll bar is not enabled just because of a few pixels.  
+**Default: 0**
+
+### stopPropagationOnClick
+When set to false, when clicking on a rail, the click event will be allowed to propagate.  
+**Default: true**
+
+### useSelectionScroll
+When set to true, you can scroll the container by selecting text and move the cursor.  
+**Default: false**
+
+## Events
+
+perfect-scrollbar dispatches custom events.
+
+### ps-scroll-y
+This event fires when the y-axis is scrolled in either direction.
+
+### ps-scroll-x
+This event fires when the x-axis is scrolled in either direction.
+
+### ps-scroll-up
+This event fires when scrolling upwards.
+
+### ps-scroll-down
+This event fires when scrolling downwards.
+
+### ps-scroll-left
+This event fires when scrolling to the left.
+
+### ps-scroll-right
+This event fires when scrolling to the right.
+
+### ps-y-reach-start
+This event fires when scrolling reaches the start of the y-axis.
+
+### ps-y-reach-end
+This event fires when scrolling reaches the end of the y-axis (useful for infinite scroll).
+
+### ps-x-reach-start
+This event fires when scrolling reaches the start of the x-axis.
+
+### ps-x-reach-end
+This event fires when scrolling reaches the end of the x-axis.
+
+You can listen to these events either with vanilla JavaScript
+```javascript
+document.addEventListener('ps-scroll-x', function () {
+  // ...
+})
+```
+or with jQuery
+```javascript
+$(document).on('ps-scroll-x', function () {
+  // ...
+})
+```
+
+## Contribution
+
+#### Please read [Contributing](https://github.com/noraesae/perfect-scrollbar/wiki/Contributing) in the wiki before making any contribution.
+
+
+I *really* welcome contributions! Please feel free to fork and issue pull requests when...
+
+* You have a very nice idea to improve this plugin!
+* You found a bug!
+* You're good at English and can help my bad English!
+
+For IE problems, please refer to [IE Support](https://github.com/noraesae/perfect-scrollbar#ie-support).
+
+## IE Support
+
+The plugin would work in IEs >= IE9 (not well, though).
+
+**The patches to fix problems in IE<=8 won't be accepted.**
+
+When old IEs should be supported, please fork the project and
+make patches personally.
+
+## Helpdesk
+
+If you have any idea to improve this project or any problem
+using this, please feel free to upload an
+[issue](https://github.com/noraesae/perfect-scrollbar/issues).
+
+For common problems there is a
+[FAQ](https://github.com/noraesae/perfect-scrollbar/wiki/FAQ) wiki page. Please check the page before uploading an issue.
+
+## License
+
+The MIT License (MIT) Copyright (c) 2015 Hyunje Alex Jun and other contributors.
+
+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/library/perfect-scrollbar/perfect-scrollbar.css b/library/perfect-scrollbar/perfect-scrollbar.css
new file mode 100644 (file)
index 0000000..354705d
--- /dev/null
@@ -0,0 +1,111 @@
+/* perfect-scrollbar v0.6.10 */
+.ps-container {
+  -ms-touch-action: none;
+  touch-action: none;
+  overflow: hidden !important;
+  -ms-overflow-style: none; }
+  @supports (-ms-overflow-style: none) {
+    .ps-container {
+      overflow: auto !important; } }
+  @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
+    .ps-container {
+      overflow: auto !important; } }
+  .ps-container.ps-active-x > .ps-scrollbar-x-rail,
+  .ps-container.ps-active-y > .ps-scrollbar-y-rail {
+    display: block;
+    background-color: transparent; }
+  .ps-container.ps-in-scrolling {
+    pointer-events: none; }
+    .ps-container.ps-in-scrolling.ps-x > .ps-scrollbar-x-rail {
+      background-color: #eee;
+      opacity: 0.9; }
+      .ps-container.ps-in-scrolling.ps-x > .ps-scrollbar-x-rail > .ps-scrollbar-x {
+        background-color: #999; }
+    .ps-container.ps-in-scrolling.ps-y > .ps-scrollbar-y-rail {
+      background-color: #eee;
+      opacity: 0.9; }
+      .ps-container.ps-in-scrolling.ps-y > .ps-scrollbar-y-rail > .ps-scrollbar-y {
+        background-color: #999; }
+  .ps-container > .ps-scrollbar-x-rail {
+    display: none;
+    position: absolute;
+    /* please don't change 'position' */
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;
+    border-radius: 4px;
+    opacity: 0;
+    -webkit-transition: background-color .2s linear, opacity .2s linear;
+    -moz-transition: background-color .2s linear, opacity .2s linear;
+    -o-transition: background-color .2s linear, opacity .2s linear;
+    transition: background-color .2s linear, opacity .2s linear;
+    bottom: 3px;
+    /* there must be 'bottom' for ps-scrollbar-x-rail */
+    height: 8px; }
+    .ps-container > .ps-scrollbar-x-rail > .ps-scrollbar-x {
+      position: absolute;
+      /* please don't change 'position' */
+      background-color: #aaa;
+      -webkit-border-radius: 4px;
+      -moz-border-radius: 4px;
+      border-radius: 4px;
+      -webkit-transition: background-color .2s linear;
+      -moz-transition: background-color .2s linear;
+      -o-transition: background-color .2s linear;
+      transition: background-color .2s linear;
+      bottom: 0;
+      /* there must be 'bottom' for ps-scrollbar-x */
+      height: 8px; }
+  .ps-container > .ps-scrollbar-y-rail {
+    display: none;
+    position: absolute;
+    /* please don't change 'position' */
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;
+    border-radius: 4px;
+    opacity: 0;
+    -webkit-transition: background-color .2s linear, opacity .2s linear;
+    -moz-transition: background-color .2s linear, opacity .2s linear;
+    -o-transition: background-color .2s linear, opacity .2s linear;
+    transition: background-color .2s linear, opacity .2s linear;
+    right: 3px;
+    /* there must be 'right' for ps-scrollbar-y-rail */
+    width: 8px; }
+    .ps-container > .ps-scrollbar-y-rail > .ps-scrollbar-y {
+      position: absolute;
+      /* please don't change 'position' */
+      background-color: #aaa;
+      -webkit-border-radius: 4px;
+      -moz-border-radius: 4px;
+      border-radius: 4px;
+      -webkit-transition: background-color .2s linear;
+      -moz-transition: background-color .2s linear;
+      -o-transition: background-color .2s linear;
+      transition: background-color .2s linear;
+      right: 0;
+      /* there must be 'right' for ps-scrollbar-y */
+      width: 8px; }
+  .ps-container:hover.ps-in-scrolling {
+    pointer-events: none; }
+    .ps-container:hover.ps-in-scrolling.ps-x > .ps-scrollbar-x-rail {
+      background-color: #eee;
+      opacity: 0.9; }
+      .ps-container:hover.ps-in-scrolling.ps-x > .ps-scrollbar-x-rail > .ps-scrollbar-x {
+        background-color: #999; }
+    .ps-container:hover.ps-in-scrolling.ps-y > .ps-scrollbar-y-rail {
+      background-color: #eee;
+      opacity: 0.9; }
+      .ps-container:hover.ps-in-scrolling.ps-y > .ps-scrollbar-y-rail > .ps-scrollbar-y {
+        background-color: #999; }
+  .ps-container:hover > .ps-scrollbar-x-rail,
+  .ps-container:hover > .ps-scrollbar-y-rail {
+    opacity: 0.6; }
+  .ps-container:hover > .ps-scrollbar-x-rail:hover {
+    background-color: #eee;
+    opacity: 0.9; }
+    .ps-container:hover > .ps-scrollbar-x-rail:hover > .ps-scrollbar-x {
+      background-color: #999; }
+  .ps-container:hover > .ps-scrollbar-y-rail:hover {
+    background-color: #eee;
+    opacity: 0.9; }
+    .ps-container:hover > .ps-scrollbar-y-rail:hover > .ps-scrollbar-y {
+      background-color: #999; }
diff --git a/library/perfect-scrollbar/perfect-scrollbar.jquery.js b/library/perfect-scrollbar/perfect-scrollbar.jquery.js
new file mode 100644 (file)
index 0000000..d94ed91
--- /dev/null
@@ -0,0 +1,1576 @@
+/* perfect-scrollbar v0.6.10
+ * 
+ * Copyright (c) 2015 Hyunje Alex Jun and other contributors
+ * Licensed under the MIT License
+ * 
+ * Source: https://github.com/noraesae/perfect-scrollbar
+ */
+
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+'use strict';
+
+var ps = require('../main')
+  , psInstances = require('../plugin/instances');
+
+function mountJQuery(jQuery) {
+  jQuery.fn.perfectScrollbar = function (settingOrCommand) {
+    return this.each(function () {
+      if (typeof settingOrCommand === 'object' ||
+          typeof settingOrCommand === 'undefined') {
+        // If it's an object or none, initialize.
+        var settings = settingOrCommand;
+
+        if (!psInstances.get(this)) {
+          ps.initialize(this, settings);
+        }
+      } else {
+        // Unless, it may be a command.
+        var command = settingOrCommand;
+
+        if (command === 'update') {
+          ps.update(this);
+        } else if (command === 'destroy') {
+          ps.destroy(this);
+        }
+      }
+
+      return jQuery(this);
+    });
+  };
+}
+
+if (typeof define === 'function' && define.amd) {
+  // AMD. Register as an anonymous module.
+  define(['jquery'], mountJQuery);
+} else {
+  var jq = window.jQuery ? window.jQuery : window.$;
+  if (typeof jq !== 'undefined') {
+    mountJQuery(jq);
+  }
+}
+
+module.exports = mountJQuery;
+
+},{"../main":7,"../plugin/instances":18}],2:[function(require,module,exports){
+'use strict';
+
+function oldAdd(element, className) {
+  var classes = element.className.split(' ');
+  if (classes.indexOf(className) < 0) {
+    classes.push(className);
+  }
+  element.className = classes.join(' ');
+}
+
+function oldRemove(element, className) {
+  var classes = element.className.split(' ');
+  var idx = classes.indexOf(className);
+  if (idx >= 0) {
+    classes.splice(idx, 1);
+  }
+  element.className = classes.join(' ');
+}
+
+exports.add = function (element, className) {
+  if (element.classList) {
+    element.classList.add(className);
+  } else {
+    oldAdd(element, className);
+  }
+};
+
+exports.remove = function (element, className) {
+  if (element.classList) {
+    element.classList.remove(className);
+  } else {
+    oldRemove(element, className);
+  }
+};
+
+exports.list = function (element) {
+  if (element.classList) {
+    return Array.prototype.slice.apply(element.classList);
+  } else {
+    return element.className.split(' ');
+  }
+};
+
+},{}],3:[function(require,module,exports){
+'use strict';
+
+var DOM = {};
+
+DOM.e = function (tagName, className) {
+  var element = document.createElement(tagName);
+  element.className = className;
+  return element;
+};
+
+DOM.appendTo = function (child, parent) {
+  parent.appendChild(child);
+  return child;
+};
+
+function cssGet(element, styleName) {
+  return window.getComputedStyle(element)[styleName];
+}
+
+function cssSet(element, styleName, styleValue) {
+  if (typeof styleValue === 'number') {
+    styleValue = styleValue.toString() + 'px';
+  }
+  element.style[styleName] = styleValue;
+  return element;
+}
+
+function cssMultiSet(element, obj) {
+  for (var key in obj) {
+    var val = obj[key];
+    if (typeof val === 'number') {
+      val = val.toString() + 'px';
+    }
+    element.style[key] = val;
+  }
+  return element;
+}
+
+DOM.css = function (element, styleNameOrObject, styleValue) {
+  if (typeof styleNameOrObject === 'object') {
+    // multiple set with object
+    return cssMultiSet(element, styleNameOrObject);
+  } else {
+    if (typeof styleValue === 'undefined') {
+      return cssGet(element, styleNameOrObject);
+    } else {
+      return cssSet(element, styleNameOrObject, styleValue);
+    }
+  }
+};
+
+DOM.matches = function (element, query) {
+  if (typeof element.matches !== 'undefined') {
+    return element.matches(query);
+  } else {
+    if (typeof element.matchesSelector !== 'undefined') {
+      return element.matchesSelector(query);
+    } else if (typeof element.webkitMatchesSelector !== 'undefined') {
+      return element.webkitMatchesSelector(query);
+    } else if (typeof element.mozMatchesSelector !== 'undefined') {
+      return element.mozMatchesSelector(query);
+    } else if (typeof element.msMatchesSelector !== 'undefined') {
+      return element.msMatchesSelector(query);
+    }
+  }
+};
+
+DOM.remove = function (element) {
+  if (typeof element.remove !== 'undefined') {
+    element.remove();
+  } else {
+    if (element.parentNode) {
+      element.parentNode.removeChild(element);
+    }
+  }
+};
+
+DOM.queryChildren = function (element, selector) {
+  return Array.prototype.filter.call(element.childNodes, function (child) {
+    return DOM.matches(child, selector);
+  });
+};
+
+module.exports = DOM;
+
+},{}],4:[function(require,module,exports){
+'use strict';
+
+var EventElement = function (element) {
+  this.element = element;
+  this.events = {};
+};
+
+EventElement.prototype.bind = function (eventName, handler) {
+  if (typeof this.events[eventName] === 'undefined') {
+    this.events[eventName] = [];
+  }
+  this.events[eventName].push(handler);
+  this.element.addEventListener(eventName, handler, false);
+};
+
+EventElement.prototype.unbind = function (eventName, handler) {
+  var isHandlerProvided = (typeof handler !== 'undefined');
+  this.events[eventName] = this.events[eventName].filter(function (hdlr) {
+    if (isHandlerProvided && hdlr !== handler) {
+      return true;
+    }
+    this.element.removeEventListener(eventName, hdlr, false);
+    return false;
+  }, this);
+};
+
+EventElement.prototype.unbindAll = function () {
+  for (var name in this.events) {
+    this.unbind(name);
+  }
+};
+
+var EventManager = function () {
+  this.eventElements = [];
+};
+
+EventManager.prototype.eventElement = function (element) {
+  var ee = this.eventElements.filter(function (eventElement) {
+    return eventElement.element === element;
+  })[0];
+  if (typeof ee === 'undefined') {
+    ee = new EventElement(element);
+    this.eventElements.push(ee);
+  }
+  return ee;
+};
+
+EventManager.prototype.bind = function (element, eventName, handler) {
+  this.eventElement(element).bind(eventName, handler);
+};
+
+EventManager.prototype.unbind = function (element, eventName, handler) {
+  this.eventElement(element).unbind(eventName, handler);
+};
+
+EventManager.prototype.unbindAll = function () {
+  for (var i = 0; i < this.eventElements.length; i++) {
+    this.eventElements[i].unbindAll();
+  }
+};
+
+EventManager.prototype.once = function (element, eventName, handler) {
+  var ee = this.eventElement(element);
+  var onceHandler = function (e) {
+    ee.unbind(eventName, onceHandler);
+    handler(e);
+  };
+  ee.bind(eventName, onceHandler);
+};
+
+module.exports = EventManager;
+
+},{}],5:[function(require,module,exports){
+'use strict';
+
+module.exports = (function () {
+  function s4() {
+    return Math.floor((1 + Math.random()) * 0x10000)
+               .toString(16)
+               .substring(1);
+  }
+  return function () {
+    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
+           s4() + '-' + s4() + s4() + s4();
+  };
+})();
+
+},{}],6:[function(require,module,exports){
+'use strict';
+
+var cls = require('./class')
+  , d = require('./dom');
+
+exports.toInt = function (x) {
+  return parseInt(x, 10) || 0;
+};
+
+exports.clone = function (obj) {
+  if (obj === null) {
+    return null;
+  } else if (typeof obj === 'object') {
+    var result = {};
+    for (var key in obj) {
+      result[key] = this.clone(obj[key]);
+    }
+    return result;
+  } else {
+    return obj;
+  }
+};
+
+exports.extend = function (original, source) {
+  var result = this.clone(original);
+  for (var key in source) {
+    result[key] = this.clone(source[key]);
+  }
+  return result;
+};
+
+exports.isEditable = function (el) {
+  return d.matches(el, "input,[contenteditable]") ||
+         d.matches(el, "select,[contenteditable]") ||
+         d.matches(el, "textarea,[contenteditable]") ||
+         d.matches(el, "button,[contenteditable]");
+};
+
+exports.removePsClasses = function (element) {
+  var clsList = cls.list(element);
+  for (var i = 0; i < clsList.length; i++) {
+    var className = clsList[i];
+    if (className.indexOf('ps-') === 0) {
+      cls.remove(element, className);
+    }
+  }
+};
+
+exports.outerWidth = function (element) {
+  return this.toInt(d.css(element, 'width')) +
+         this.toInt(d.css(element, 'paddingLeft')) +
+         this.toInt(d.css(element, 'paddingRight')) +
+         this.toInt(d.css(element, 'borderLeftWidth')) +
+         this.toInt(d.css(element, 'borderRightWidth'));
+};
+
+exports.startScrolling = function (element, axis) {
+  cls.add(element, 'ps-in-scrolling');
+  if (typeof axis !== 'undefined') {
+    cls.add(element, 'ps-' + axis);
+  } else {
+    cls.add(element, 'ps-x');
+    cls.add(element, 'ps-y');
+  }
+};
+
+exports.stopScrolling = function (element, axis) {
+  cls.remove(element, 'ps-in-scrolling');
+  if (typeof axis !== 'undefined') {
+    cls.remove(element, 'ps-' + axis);
+  } else {
+    cls.remove(element, 'ps-x');
+    cls.remove(element, 'ps-y');
+  }
+};
+
+exports.env = {
+  isWebKit: 'WebkitAppearance' in document.documentElement.style,
+  supportsTouch: (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch),
+  supportsIePointer: window.navigator.msMaxTouchPoints !== null
+};
+
+},{"./class":2,"./dom":3}],7:[function(require,module,exports){
+'use strict';
+
+var destroy = require('./plugin/destroy')
+  , initialize = require('./plugin/initialize')
+  , update = require('./plugin/update');
+
+module.exports = {
+  initialize: initialize,
+  update: update,
+  destroy: destroy
+};
+
+},{"./plugin/destroy":9,"./plugin/initialize":17,"./plugin/update":21}],8:[function(require,module,exports){
+'use strict';
+
+module.exports = {
+  maxScrollbarLength: null,
+  minScrollbarLength: null,
+  scrollXMarginOffset: 0,
+  scrollYMarginOffset: 0,
+  stopPropagationOnClick: true,
+  suppressScrollX: false,
+  suppressScrollY: false,
+  swipePropagation: true,
+  useBothWheelAxes: false,
+  useKeyboard: true,
+  useSelectionScroll: false,
+  wheelPropagation: false,
+  wheelSpeed: 1,
+  theme: 'default'
+};
+
+},{}],9:[function(require,module,exports){
+'use strict';
+
+var d = require('../lib/dom')
+  , h = require('../lib/helper')
+  , instances = require('./instances');
+
+module.exports = function (element) {
+  var i = instances.get(element);
+
+  if (!i) {
+    return;
+  }
+
+  i.event.unbindAll();
+  d.remove(i.scrollbarX);
+  d.remove(i.scrollbarY);
+  d.remove(i.scrollbarXRail);
+  d.remove(i.scrollbarYRail);
+  h.removePsClasses(element);
+
+  instances.remove(element);
+};
+
+},{"../lib/dom":3,"../lib/helper":6,"./instances":18}],10:[function(require,module,exports){
+'use strict';
+
+var h = require('../../lib/helper')
+  , instances = require('../instances')
+  , updateGeometry = require('../update-geometry')
+  , updateScroll = require('../update-scroll');
+
+function bindClickRailHandler(element, i) {
+  function pageOffset(el) {
+    return el.getBoundingClientRect();
+  }
+  var stopPropagation = window.Event.prototype.stopPropagation.bind;
+
+  if (i.settings.stopPropagationOnClick) {
+    i.event.bind(i.scrollbarY, 'click', stopPropagation);
+  }
+  i.event.bind(i.scrollbarYRail, 'click', function (e) {
+    var halfOfScrollbarLength = h.toInt(i.scrollbarYHeight / 2);
+    var positionTop = i.railYRatio * (e.pageY - window.pageYOffset - pageOffset(i.scrollbarYRail).top - halfOfScrollbarLength);
+    var maxPositionTop = i.railYRatio * (i.railYHeight - i.scrollbarYHeight);
+    var positionRatio = positionTop / maxPositionTop;
+
+    if (positionRatio < 0) {
+      positionRatio = 0;
+    } else if (positionRatio > 1) {
+      positionRatio = 1;
+    }
+
+    updateScroll(element, 'top', (i.contentHeight - i.containerHeight) * positionRatio);
+    updateGeometry(element);
+
+    e.stopPropagation();
+  });
+
+  if (i.settings.stopPropagationOnClick) {
+    i.event.bind(i.scrollbarX, 'click', stopPropagation);
+  }
+  i.event.bind(i.scrollbarXRail, 'click', function (e) {
+    var halfOfScrollbarLength = h.toInt(i.scrollbarXWidth / 2);
+    var positionLeft = i.railXRatio * (e.pageX - window.pageXOffset - pageOffset(i.scrollbarXRail).left - halfOfScrollbarLength);
+    var maxPositionLeft = i.railXRatio * (i.railXWidth - i.scrollbarXWidth);
+    var positionRatio = positionLeft / maxPositionLeft;
+
+    if (positionRatio < 0) {
+      positionRatio = 0;
+    } else if (positionRatio > 1) {
+      positionRatio = 1;
+    }
+
+    updateScroll(element, 'left', ((i.contentWidth - i.containerWidth) * positionRatio) - i.negativeScrollAdjustment);
+    updateGeometry(element);
+
+    e.stopPropagation();
+  });
+}
+
+module.exports = function (element) {
+  var i = instances.get(element);
+  bindClickRailHandler(element, i);
+};
+
+},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],11:[function(require,module,exports){
+'use strict';
+
+var d = require('../../lib/dom')
+  , h = require('../../lib/helper')
+  , instances = require('../instances')
+  , updateGeometry = require('../update-geometry')
+  , updateScroll = require('../update-scroll');
+
+function bindMouseScrollXHandler(element, i) {
+  var currentLeft = null;
+  var currentPageX = null;
+
+  function updateScrollLeft(deltaX) {
+    var newLeft = currentLeft + (deltaX * i.railXRatio);
+    var maxLeft = Math.max(0, i.scrollbarXRail.getBoundingClientRect().left) + (i.railXRatio * (i.railXWidth - i.scrollbarXWidth));
+
+    if (newLeft < 0) {
+      i.scrollbarXLeft = 0;
+    } else if (newLeft > maxLeft) {
+      i.scrollbarXLeft = maxLeft;
+    } else {
+      i.scrollbarXLeft = newLeft;
+    }
+
+    var scrollLeft = h.toInt(i.scrollbarXLeft * (i.contentWidth - i.containerWidth) / (i.containerWidth - (i.railXRatio * i.scrollbarXWidth))) - i.negativeScrollAdjustment;
+    updateScroll(element, 'left', scrollLeft);
+  }
+
+  var mouseMoveHandler = function (e) {
+    updateScrollLeft(e.pageX - currentPageX);
+    updateGeometry(element);
+    e.stopPropagation();
+    e.preventDefault();
+  };
+
+  var mouseUpHandler = function () {
+    h.stopScrolling(element, 'x');
+    i.event.unbind(i.ownerDocument, 'mousemove', mouseMoveHandler);
+  };
+
+  i.event.bind(i.scrollbarX, 'mousedown', function (e) {
+    currentPageX = e.pageX;
+    currentLeft = h.toInt(d.css(i.scrollbarX, 'left')) * i.railXRatio;
+    h.startScrolling(element, 'x');
+
+    i.event.bind(i.ownerDocument, 'mousemove', mouseMoveHandler);
+    i.event.once(i.ownerDocument, 'mouseup', mouseUpHandler);
+
+    e.stopPropagation();
+    e.preventDefault();
+  });
+}
+
+function bindMouseScrollYHandler(element, i) {
+  var currentTop = null;
+  var currentPageY = null;
+
+  function updateScrollTop(deltaY) {
+    var newTop = currentTop + (deltaY * i.railYRatio);
+    var maxTop = Math.max(0, i.scrollbarYRail.getBoundingClientRect().top) + (i.railYRatio * (i.railYHeight - i.scrollbarYHeight));
+
+    if (newTop < 0) {
+      i.scrollbarYTop = 0;
+    } else if (newTop > maxTop) {
+      i.scrollbarYTop = maxTop;
+    } else {
+      i.scrollbarYTop = newTop;
+    }
+
+    var scrollTop = h.toInt(i.scrollbarYTop * (i.contentHeight - i.containerHeight) / (i.containerHeight - (i.railYRatio * i.scrollbarYHeight)));
+    updateScroll(element, 'top', scrollTop);
+  }
+
+  var mouseMoveHandler = function (e) {
+    updateScrollTop(e.pageY - currentPageY);
+    updateGeometry(element);
+    e.stopPropagation();
+    e.preventDefault();
+  };
+
+  var mouseUpHandler = function () {
+    h.stopScrolling(element, 'y');
+    i.event.unbind(i.ownerDocument, 'mousemove', mouseMoveHandler);
+  };
+
+  i.event.bind(i.scrollbarY, 'mousedown', function (e) {
+    currentPageY = e.pageY;
+    currentTop = h.toInt(d.css(i.scrollbarY, 'top')) * i.railYRatio;
+    h.startScrolling(element, 'y');
+
+    i.event.bind(i.ownerDocument, 'mousemove', mouseMoveHandler);
+    i.event.once(i.ownerDocument, 'mouseup', mouseUpHandler);
+
+    e.stopPropagation();
+    e.preventDefault();
+  });
+}
+
+module.exports = function (element) {
+  var i = instances.get(element);
+  bindMouseScrollXHandler(element, i);
+  bindMouseScrollYHandler(element, i);
+};
+
+},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],12:[function(require,module,exports){
+'use strict';
+
+var h = require('../../lib/helper')
+  , d = require('../../lib/dom')
+  , instances = require('../instances')
+  , updateGeometry = require('../update-geometry')
+  , updateScroll = require('../update-scroll');
+
+function bindKeyboardHandler(element, i) {
+  var hovered = false;
+  i.event.bind(element, 'mouseenter', function () {
+    hovered = true;
+  });
+  i.event.bind(element, 'mouseleave', function () {
+    hovered = false;
+  });
+
+  var shouldPrevent = false;
+  function shouldPreventDefault(deltaX, deltaY) {
+    var scrollTop = element.scrollTop;
+    if (deltaX === 0) {
+      if (!i.scrollbarYActive) {
+        return false;
+      }
+      if ((scrollTop === 0 && deltaY > 0) || (scrollTop >= i.contentHeight - i.containerHeight && deltaY < 0)) {
+        return !i.settings.wheelPropagation;
+      }
+    }
+
+    var scrollLeft = element.scrollLeft;
+    if (deltaY === 0) {
+      if (!i.scrollbarXActive) {
+        return false;
+      }
+      if ((scrollLeft === 0 && deltaX < 0) || (scrollLeft >= i.contentWidth - i.containerWidth && deltaX > 0)) {
+        return !i.settings.wheelPropagation;
+      }
+    }
+    return true;
+  }
+
+  i.event.bind(i.ownerDocument, 'keydown', function (e) {
+    if (e.isDefaultPrevented && e.isDefaultPrevented()) {
+      return;
+    }
+
+    var focused = d.matches(i.scrollbarX, ':focus') ||
+                  d.matches(i.scrollbarY, ':focus');
+
+    if (!hovered && !focused) {
+      return;
+    }
+
+    var activeElement = document.activeElement ? document.activeElement : i.ownerDocument.activeElement;
+    if (activeElement) {
+      // go deeper if element is a webcomponent
+      while (activeElement.shadowRoot) {
+        activeElement = activeElement.shadowRoot.activeElement;
+      }
+      if (h.isEditable(activeElement)) {
+        return;
+      }
+    }
+
+    var deltaX = 0;
+    var deltaY = 0;
+
+    switch (e.which) {
+    case 37: // left
+      deltaX = -30;
+      break;
+    case 38: // up
+      deltaY = 30;
+      break;
+    case 39: // right
+      deltaX = 30;
+      break;
+    case 40: // down
+      deltaY = -30;
+      break;
+    case 33: // page up
+      deltaY = 90;
+      break;
+    case 32: // space bar
+      if (e.shiftKey) {
+        deltaY = 90;
+      } else {
+        deltaY = -90;
+      }
+      break;
+    case 34: // page down
+      deltaY = -90;
+      break;
+    case 35: // end
+      if (e.ctrlKey) {
+        deltaY = -i.contentHeight;
+      } else {
+        deltaY = -i.containerHeight;
+      }
+      break;
+    case 36: // home
+      if (e.ctrlKey) {
+        deltaY = element.scrollTop;
+      } else {
+        deltaY = i.containerHeight;
+      }
+      break;
+    default:
+      return;
+    }
+
+    updateScroll(element, 'top', element.scrollTop - deltaY);
+    updateScroll(element, 'left', element.scrollLeft + deltaX);
+    updateGeometry(element);
+
+    shouldPrevent = shouldPreventDefault(deltaX, deltaY);
+    if (shouldPrevent) {
+      e.preventDefault();
+    }
+  });
+}
+
+module.exports = function (element) {
+  var i = instances.get(element);
+  bindKeyboardHandler(element, i);
+};
+
+},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],13:[function(require,module,exports){
+'use strict';
+
+var instances = require('../instances')
+  , updateGeometry = require('../update-geometry')
+  , updateScroll = require('../update-scroll');
+
+function bindMouseWheelHandler(element, i) {
+  var shouldPrevent = false;
+
+  function shouldPreventDefault(deltaX, deltaY) {
+    var scrollTop = element.scrollTop;
+    if (deltaX === 0) {
+      if (!i.scrollbarYActive) {
+        return false;
+      }
+      if ((scrollTop === 0 && deltaY > 0) || (scrollTop >= i.contentHeight - i.containerHeight && deltaY < 0)) {
+        return !i.settings.wheelPropagation;
+      }
+    }
+
+    var scrollLeft = element.scrollLeft;
+    if (deltaY === 0) {
+      if (!i.scrollbarXActive) {
+        return false;
+      }
+      if ((scrollLeft === 0 && deltaX < 0) || (scrollLeft >= i.contentWidth - i.containerWidth && deltaX > 0)) {
+        return !i.settings.wheelPropagation;
+      }
+    }
+    return true;
+  }
+
+  function getDeltaFromEvent(e) {
+    var deltaX = e.deltaX;
+    var deltaY = -1 * e.deltaY;
+
+    if (typeof deltaX === "undefined" || typeof deltaY === "undefined") {
+      // OS X Safari
+      deltaX = -1 * e.wheelDeltaX / 6;
+      deltaY = e.wheelDeltaY / 6;
+    }
+
+    if (e.deltaMode && e.deltaMode === 1) {
+      // Firefox in deltaMode 1: Line scrolling
+      deltaX *= 10;
+      deltaY *= 10;
+    }
+
+    if (deltaX !== deltaX && deltaY !== deltaY/* NaN checks */) {
+      // IE in some mouse drivers
+      deltaX = 0;
+      deltaY = e.wheelDelta;
+    }
+
+    return [deltaX, deltaY];
+  }
+
+  function shouldBeConsumedByTextarea(deltaX, deltaY) {
+    var hoveredTextarea = element.querySelector('textarea:hover');
+    if (hoveredTextarea) {
+      var maxScrollTop = hoveredTextarea.scrollHeight - hoveredTextarea.clientHeight;
+      if (maxScrollTop > 0) {
+        if (!(hoveredTextarea.scrollTop === 0 && deltaY > 0) &&
+            !(hoveredTextarea.scrollTop === maxScrollTop && deltaY < 0)) {
+          return true;
+        }
+      }
+      var maxScrollLeft = hoveredTextarea.scrollLeft - hoveredTextarea.clientWidth;
+      if (maxScrollLeft > 0) {
+        if (!(hoveredTextarea.scrollLeft === 0 && deltaX < 0) &&
+            !(hoveredTextarea.scrollLeft === maxScrollLeft && deltaX > 0)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  function mousewheelHandler(e) {
+    var delta = getDeltaFromEvent(e);
+
+    var deltaX = delta[0];
+    var deltaY = delta[1];
+
+    if (shouldBeConsumedByTextarea(deltaX, deltaY)) {
+      return;
+    }
+
+    shouldPrevent = false;
+    if (!i.settings.useBothWheelAxes) {
+      // deltaX will only be used for horizontal scrolling and deltaY will
+      // only be used for vertical scrolling - this is the default
+      updateScroll(element, 'top', element.scrollTop - (deltaY * i.settings.wheelSpeed));
+      updateScroll(element, 'left', element.scrollLeft + (deltaX * i.settings.wheelSpeed));
+    } else if (i.scrollbarYActive && !i.scrollbarXActive) {
+      // only vertical scrollbar is active and useBothWheelAxes option is
+      // active, so let's scroll vertical bar using both mouse wheel axes
+      if (deltaY) {
+        updateScroll(element, 'top', element.scrollTop - (deltaY * i.settings.wheelSpeed));
+      } else {
+        updateScroll(element, 'top', element.scrollTop + (deltaX * i.settings.wheelSpeed));
+      }
+      shouldPrevent = true;
+    } else if (i.scrollbarXActive && !i.scrollbarYActive) {
+      // useBothWheelAxes and only horizontal bar is active, so use both
+      // wheel axes for horizontal bar
+      if (deltaX) {
+        updateScroll(element, 'left', element.scrollLeft + (deltaX * i.settings.wheelSpeed));
+      } else {
+        updateScroll(element, 'left', element.scrollLeft - (deltaY * i.settings.wheelSpeed));
+      }
+      shouldPrevent = true;
+    }
+
+    updateGeometry(element);
+
+    shouldPrevent = (shouldPrevent || shouldPreventDefault(deltaX, deltaY));
+    if (shouldPrevent) {
+      e.stopPropagation();
+      e.preventDefault();
+    }
+  }
+
+  if (typeof window.onwheel !== "undefined") {
+    i.event.bind(element, 'wheel', mousewheelHandler);
+  } else if (typeof window.onmousewheel !== "undefined") {
+    i.event.bind(element, 'mousewheel', mousewheelHandler);
+  }
+}
+
+module.exports = function (element) {
+  var i = instances.get(element);
+  bindMouseWheelHandler(element, i);
+};
+
+},{"../instances":18,"../update-geometry":19,"../update-scroll":20}],14:[function(require,module,exports){
+'use strict';
+
+var instances = require('../instances')
+  , updateGeometry = require('../update-geometry');
+
+function bindNativeScrollHandler(element, i) {
+  i.event.bind(element, 'scroll', function () {
+    updateGeometry(element);
+  });
+}
+
+module.exports = function (element) {
+  var i = instances.get(element);
+  bindNativeScrollHandler(element, i);
+};
+
+},{"../instances":18,"../update-geometry":19}],15:[function(require,module,exports){
+'use strict';
+
+var h = require('../../lib/helper')
+  , instances = require('../instances')
+  , updateGeometry = require('../update-geometry')
+  , updateScroll = require('../update-scroll');
+
+function bindSelectionHandler(element, i) {
+  function getRangeNode() {
+    var selection = window.getSelection ? window.getSelection() :
+                    document.getSelection ? document.getSelection() : '';
+    if (selection.toString().length === 0) {
+      return null;
+    } else {
+      return selection.getRangeAt(0).commonAncestorContainer;
+    }
+  }
+
+  var scrollingLoop = null;
+  var scrollDiff = {top: 0, left: 0};
+  function startScrolling() {
+    if (!scrollingLoop) {
+      scrollingLoop = setInterval(function () {
+        if (!instances.get(element)) {
+          clearInterval(scrollingLoop);
+          return;
+        }
+
+        updateScroll(element, 'top', element.scrollTop + scrollDiff.top);
+        updateScroll(element, 'left', element.scrollLeft + scrollDiff.left);
+        updateGeometry(element);
+      }, 50); // every .1 sec
+    }
+  }
+  function stopScrolling() {
+    if (scrollingLoop) {
+      clearInterval(scrollingLoop);
+      scrollingLoop = null;
+    }
+    h.stopScrolling(element);
+  }
+
+  var isSelected = false;
+  i.event.bind(i.ownerDocument, 'selectionchange', function () {
+    if (element.contains(getRangeNode())) {
+      isSelected = true;
+    } else {
+      isSelected = false;
+      stopScrolling();
+    }
+  });
+  i.event.bind(window, 'mouseup', function () {
+    if (isSelected) {
+      isSelected = false;
+      stopScrolling();
+    }
+  });
+
+  i.event.bind(window, 'mousemove', function (e) {
+    if (isSelected) {
+      var mousePosition = {x: e.pageX, y: e.pageY};
+      var containerGeometry = {
+        left: element.offsetLeft,
+        right: element.offsetLeft + element.offsetWidth,
+        top: element.offsetTop,
+        bottom: element.offsetTop + element.offsetHeight
+      };
+
+      if (mousePosition.x < containerGeometry.left + 3) {
+        scrollDiff.left = -5;
+        h.startScrolling(element, 'x');
+      } else if (mousePosition.x > containerGeometry.right - 3) {
+        scrollDiff.left = 5;
+        h.startScrolling(element, 'x');
+      } else {
+        scrollDiff.left = 0;
+      }
+
+      if (mousePosition.y < containerGeometry.top + 3) {
+        if (containerGeometry.top + 3 - mousePosition.y < 5) {
+          scrollDiff.top = -5;
+        } else {
+          scrollDiff.top = -20;
+        }
+        h.startScrolling(element, 'y');
+      } else if (mousePosition.y > containerGeometry.bottom - 3) {
+        if (mousePosition.y - containerGeometry.bottom + 3 < 5) {
+          scrollDiff.top = 5;
+        } else {
+          scrollDiff.top = 20;
+        }
+        h.startScrolling(element, 'y');
+      } else {
+        scrollDiff.top = 0;
+      }
+
+      if (scrollDiff.top === 0 && scrollDiff.left === 0) {
+        stopScrolling();
+      } else {
+        startScrolling();
+      }
+    }
+  });
+}
+
+module.exports = function (element) {
+  var i = instances.get(element);
+  bindSelectionHandler(element, i);
+};
+
+},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],16:[function(require,module,exports){
+'use strict';
+
+var instances = require('../instances')
+  , updateGeometry = require('../update-geometry')
+  , updateScroll = require('../update-scroll');
+
+function bindTouchHandler(element, i, supportsTouch, supportsIePointer) {
+  function shouldPreventDefault(deltaX, deltaY) {
+    var scrollTop = element.scrollTop;
+    var scrollLeft = element.scrollLeft;
+    var magnitudeX = Math.abs(deltaX);
+    var magnitudeY = Math.abs(deltaY);
+
+    if (magnitudeY > magnitudeX) {
+      // user is perhaps trying to swipe up/down the page
+
+      if (((deltaY < 0) && (scrollTop === i.contentHeight - i.containerHeight)) ||
+          ((deltaY > 0) && (scrollTop === 0))) {
+        return !i.settings.swipePropagation;
+      }
+    } else if (magnitudeX > magnitudeY) {
+      // user is perhaps trying to swipe left/right across the page
+
+      if (((deltaX < 0) && (scrollLeft === i.contentWidth - i.containerWidth)) ||
+          ((deltaX > 0) && (scrollLeft === 0))) {
+        return !i.settings.swipePropagation;
+      }
+    }
+
+    return true;
+  }
+
+  function applyTouchMove(differenceX, differenceY) {
+    updateScroll(element, 'top', element.scrollTop - differenceY);
+    updateScroll(element, 'left', element.scrollLeft - differenceX);
+
+    updateGeometry(element);
+  }
+
+  var startOffset = {};
+  var startTime = 0;
+  var speed = {};
+  var easingLoop = null;
+  var inGlobalTouch = false;
+  var inLocalTouch = false;
+
+  function globalTouchStart() {
+    inGlobalTouch = true;
+  }
+  function globalTouchEnd() {
+    inGlobalTouch = false;
+  }
+
+  function getTouch(e) {
+    if (e.targetTouches) {
+      return e.targetTouches[0];
+    } else {
+      // Maybe IE pointer
+      return e;
+    }
+  }
+  function shouldHandle(e) {
+    if (e.targetTouches && e.targetTouches.length === 1) {
+      return true;
+    }
+    if (e.pointerType && e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
+      return true;
+    }
+    return false;
+  }
+  function touchStart(e) {
+    if (shouldHandle(e)) {
+      inLocalTouch = true;
+
+      var touch = getTouch(e);
+
+      startOffset.pageX = touch.pageX;
+      startOffset.pageY = touch.pageY;
+
+      startTime = (new Date()).getTime();
+
+      if (easingLoop !== null) {
+        clearInterval(easingLoop);
+      }
+
+      e.stopPropagation();
+    }
+  }
+  function touchMove(e) {
+    if (!inGlobalTouch && inLocalTouch && shouldHandle(e)) {
+      var touch = getTouch(e);
+
+      var currentOffset = {pageX: touch.pageX, pageY: touch.pageY};
+
+      var differenceX = currentOffset.pageX - startOffset.pageX;
+      var differenceY = currentOffset.pageY - startOffset.pageY;
+
+      applyTouchMove(differenceX, differenceY);
+      startOffset = currentOffset;
+
+      var currentTime = (new Date()).getTime();
+
+      var timeGap = currentTime - startTime;
+      if (timeGap > 0) {
+        speed.x = differenceX / timeGap;
+        speed.y = differenceY / timeGap;
+        startTime = currentTime;
+      }
+
+      if (shouldPreventDefault(differenceX, differenceY)) {
+        e.stopPropagation();
+        e.preventDefault();
+      }
+    }
+  }
+  function touchEnd() {
+    if (!inGlobalTouch && inLocalTouch) {
+      inLocalTouch = false;
+
+      clearInterval(easingLoop);
+      easingLoop = setInterval(function () {
+        if (!instances.get(element)) {
+          clearInterval(easingLoop);
+          return;
+        }
+
+        if (Math.abs(speed.x) < 0.01 && Math.abs(speed.y) < 0.01) {
+          clearInterval(easingLoop);
+          return;
+        }
+
+        applyTouchMove(speed.x * 30, speed.y * 30);
+
+        speed.x *= 0.8;
+        speed.y *= 0.8;
+      }, 10);
+    }
+  }
+
+  if (supportsTouch) {
+    i.event.bind(window, 'touchstart', globalTouchStart);
+    i.event.bind(window, 'touchend', globalTouchEnd);
+    i.event.bind(element, 'touchstart', touchStart);
+    i.event.bind(element, 'touchmove', touchMove);
+    i.event.bind(element, 'touchend', touchEnd);
+  }
+
+  if (supportsIePointer) {
+    if (window.PointerEvent) {
+      i.event.bind(window, 'pointerdown', globalTouchStart);
+      i.event.bind(window, 'pointerup', globalTouchEnd);
+      i.event.bind(element, 'pointerdown', touchStart);
+      i.event.bind(element, 'pointermove', touchMove);
+      i.event.bind(element, 'pointerup', touchEnd);
+    } else if (window.MSPointerEvent) {
+      i.event.bind(window, 'MSPointerDown', globalTouchStart);
+      i.event.bind(window, 'MSPointerUp', globalTouchEnd);
+      i.event.bind(element, 'MSPointerDown', touchStart);
+      i.event.bind(element, 'MSPointerMove', touchMove);
+      i.event.bind(element, 'MSPointerUp', touchEnd);
+    }
+  }
+}
+
+module.exports = function (element, supportsTouch, supportsIePointer) {
+  var i = instances.get(element);
+  bindTouchHandler(element, i, supportsTouch, supportsIePointer);
+};
+
+},{"../instances":18,"../update-geometry":19,"../update-scroll":20}],17:[function(require,module,exports){
+'use strict';
+
+var cls = require('../lib/class')
+  , h = require('../lib/helper')
+  , instances = require('./instances')
+  , updateGeometry = require('./update-geometry');
+
+// Handlers
+var clickRailHandler = require('./handler/click-rail')
+  , dragScrollbarHandler = require('./handler/drag-scrollbar')
+  , keyboardHandler = require('./handler/keyboard')
+  , mouseWheelHandler = require('./handler/mouse-wheel')
+  , nativeScrollHandler = require('./handler/native-scroll')
+  , selectionHandler = require('./handler/selection')
+  , touchHandler = require('./handler/touch');
+
+module.exports = function (element, userSettings) {
+  userSettings = typeof userSettings === 'object' ? userSettings : {};
+
+  cls.add(element, 'ps-container');
+
+  // Create a plugin instance.
+  var i = instances.add(element);
+
+  i.settings = h.extend(i.settings, userSettings);
+  cls.add(element, 'ps-theme-' + i.settings.theme);
+
+  clickRailHandler(element);
+  dragScrollbarHandler(element);
+  mouseWheelHandler(element);
+  nativeScrollHandler(element);
+
+  if (i.settings.useSelectionScroll) {
+    selectionHandler(element);
+  }
+
+  if (h.env.supportsTouch || h.env.supportsIePointer) {
+    touchHandler(element, h.env.supportsTouch, h.env.supportsIePointer);
+  }
+  if (i.settings.useKeyboard) {
+    keyboardHandler(element);
+  }
+
+  updateGeometry(element);
+};
+
+},{"../lib/class":2,"../lib/helper":6,"./handler/click-rail":10,"./handler/drag-scrollbar":11,"./handler/keyboard":12,"./handler/mouse-wheel":13,"./handler/native-scroll":14,"./handler/selection":15,"./handler/touch":16,"./instances":18,"./update-geometry":19}],18:[function(require,module,exports){
+'use strict';
+
+var cls = require('../lib/class')
+  , d = require('../lib/dom')
+  , defaultSettings = require('./default-setting')
+  , EventManager = require('../lib/event-manager')
+  , guid = require('../lib/guid')
+  , h = require('../lib/helper');
+
+var instances = {};
+
+function Instance(element) {
+  var i = this;
+
+  i.settings = h.clone(defaultSettings);
+  i.containerWidth = null;
+  i.containerHeight = null;
+  i.contentWidth = null;
+  i.contentHeight = null;
+
+  i.isRtl = d.css(element, 'direction') === "rtl";
+  i.isNegativeScroll = (function () {
+    var originalScrollLeft = element.scrollLeft;
+    var result = null;
+    element.scrollLeft = -1;
+    result = element.scrollLeft < 0;
+    element.scrollLeft = originalScrollLeft;
+    return result;
+  })();
+  i.negativeScrollAdjustment = i.isNegativeScroll ? element.scrollWidth - element.clientWidth : 0;
+  i.event = new EventManager();
+  i.ownerDocument = element.ownerDocument || document;
+
+  function focus() {
+    cls.add(element, 'ps-focus');
+  }
+
+  function blur() {
+    cls.remove(element, 'ps-focus');
+  }
+
+  i.scrollbarXRail = d.appendTo(d.e('div', 'ps-scrollbar-x-rail'), element);
+  i.scrollbarX = d.appendTo(d.e('div', 'ps-scrollbar-x'), i.scrollbarXRail);
+  i.scrollbarX.setAttribute('tabindex', 0);
+  i.event.bind(i.scrollbarX, 'focus', focus);
+  i.event.bind(i.scrollbarX, 'blur', blur);
+  i.scrollbarXActive = null;
+  i.scrollbarXWidth = null;
+  i.scrollbarXLeft = null;
+  i.scrollbarXBottom = h.toInt(d.css(i.scrollbarXRail, 'bottom'));
+  i.isScrollbarXUsingBottom = i.scrollbarXBottom === i.scrollbarXBottom; // !isNaN
+  i.scrollbarXTop = i.isScrollbarXUsingBottom ? null : h.toInt(d.css(i.scrollbarXRail, 'top'));
+  i.railBorderXWidth = h.toInt(d.css(i.scrollbarXRail, 'borderLeftWidth')) + h.toInt(d.css(i.scrollbarXRail, 'borderRightWidth'));
+  // Set rail to display:block to calculate margins
+  d.css(i.scrollbarXRail, 'display', 'block');
+  i.railXMarginWidth = h.toInt(d.css(i.scrollbarXRail, 'marginLeft')) + h.toInt(d.css(i.scrollbarXRail, 'marginRight'));
+  d.css(i.scrollbarXRail, 'display', '');
+  i.railXWidth = null;
+  i.railXRatio = null;
+
+  i.scrollbarYRail = d.appendTo(d.e('div', 'ps-scrollbar-y-rail'), element);
+  i.scrollbarY = d.appendTo(d.e('div', 'ps-scrollbar-y'), i.scrollbarYRail);
+  i.scrollbarY.setAttribute('tabindex', 0);
+  i.event.bind(i.scrollbarY, 'focus', focus);
+  i.event.bind(i.scrollbarY, 'blur', blur);
+  i.scrollbarYActive = null;
+  i.scrollbarYHeight = null;
+  i.scrollbarYTop = null;
+  i.scrollbarYRight = h.toInt(d.css(i.scrollbarYRail, 'right'));
+  i.isScrollbarYUsingRight = i.scrollbarYRight === i.scrollbarYRight; // !isNaN
+  i.scrollbarYLeft = i.isScrollbarYUsingRight ? null : h.toInt(d.css(i.scrollbarYRail, 'left'));
+  i.scrollbarYOuterWidth = i.isRtl ? h.outerWidth(i.scrollbarY) : null;
+  i.railBorderYWidth = h.toInt(d.css(i.scrollbarYRail, 'borderTopWidth')) + h.toInt(d.css(i.scrollbarYRail, 'borderBottomWidth'));
+  d.css(i.scrollbarYRail, 'display', 'block');
+  i.railYMarginHeight = h.toInt(d.css(i.scrollbarYRail, 'marginTop')) + h.toInt(d.css(i.scrollbarYRail, 'marginBottom'));
+  d.css(i.scrollbarYRail, 'display', '');
+  i.railYHeight = null;
+  i.railYRatio = null;
+}
+
+function getId(element) {
+  if (typeof element.dataset === 'undefined') {
+    return element.getAttribute('data-ps-id');
+  } else {
+    return element.dataset.psId;
+  }
+}
+
+function setId(element, id) {
+  if (typeof element.dataset === 'undefined') {
+    element.setAttribute('data-ps-id', id);
+  } else {
+    element.dataset.psId = id;
+  }
+}
+
+function removeId(element) {
+  if (typeof element.dataset === 'undefined') {
+    element.removeAttribute('data-ps-id');
+  } else {
+    delete element.dataset.psId;
+  }
+}
+
+exports.add = function (element) {
+  var newId = guid();
+  setId(element, newId);
+  instances[newId] = new Instance(element);
+  return instances[newId];
+};
+
+exports.remove = function (element) {
+  delete instances[getId(element)];
+  removeId(element);
+};
+
+exports.get = function (element) {
+  return instances[getId(element)];
+};
+
+},{"../lib/class":2,"../lib/dom":3,"../lib/event-manager":4,"../lib/guid":5,"../lib/helper":6,"./default-setting":8}],19:[function(require,module,exports){
+'use strict';
+
+var cls = require('../lib/class')
+  , d = require('../lib/dom')
+  , h = require('../lib/helper')
+  , instances = require('./instances')
+  , updateScroll = require('./update-scroll');
+
+function getThumbSize(i, thumbSize) {
+  if (i.settings.minScrollbarLength) {
+    thumbSize = Math.max(thumbSize, i.settings.minScrollbarLength);
+  }
+  if (i.settings.maxScrollbarLength) {
+    thumbSize = Math.min(thumbSize, i.settings.maxScrollbarLength);
+  }
+  return thumbSize;
+}
+
+function updateCss(element, i) {
+  var xRailOffset = {width: i.railXWidth};
+  if (i.isRtl) {
+    xRailOffset.left = i.negativeScrollAdjustment + element.scrollLeft + i.containerWidth - i.contentWidth;
+  } else {
+    xRailOffset.left = element.scrollLeft;
+  }
+  if (i.isScrollbarXUsingBottom) {
+    xRailOffset.bottom = i.scrollbarXBottom - element.scrollTop;
+  } else {
+    xRailOffset.top = i.scrollbarXTop + element.scrollTop;
+  }
+  d.css(i.scrollbarXRail, xRailOffset);
+
+  var yRailOffset = {top: element.scrollTop, height: i.railYHeight};
+  if (i.isScrollbarYUsingRight) {
+    if (i.isRtl) {
+      yRailOffset.right = i.contentWidth - (i.negativeScrollAdjustment + element.scrollLeft) - i.scrollbarYRight - i.scrollbarYOuterWidth;
+    } else {
+      yRailOffset.right = i.scrollbarYRight - element.scrollLeft;
+    }
+  } else {
+    if (i.isRtl) {
+      yRailOffset.left = i.negativeScrollAdjustment + element.scrollLeft + i.containerWidth * 2 - i.contentWidth - i.scrollbarYLeft - i.scrollbarYOuterWidth;
+    } else {
+      yRailOffset.left = i.scrollbarYLeft + element.scrollLeft;
+    }
+  }
+  d.css(i.scrollbarYRail, yRailOffset);
+
+  d.css(i.scrollbarX, {left: i.scrollbarXLeft, width: i.scrollbarXWidth - i.railBorderXWidth});
+  d.css(i.scrollbarY, {top: i.scrollbarYTop, height: i.scrollbarYHeight - i.railBorderYWidth});
+}
+
+module.exports = function (element) {
+  var i = instances.get(element);
+
+  i.containerWidth = element.clientWidth;
+  i.containerHeight = element.clientHeight;
+  i.contentWidth = element.scrollWidth;
+  i.contentHeight = element.scrollHeight;
+
+  var existingRails;
+  if (!element.contains(i.scrollbarXRail)) {
+    existingRails = d.queryChildren(element, '.ps-scrollbar-x-rail');
+    if (existingRails.length > 0) {
+      existingRails.forEach(function (rail) {
+        d.remove(rail);
+      });
+    }
+    d.appendTo(i.scrollbarXRail, element);
+  }
+  if (!element.contains(i.scrollbarYRail)) {
+    existingRails = d.queryChildren(element, '.ps-scrollbar-y-rail');
+    if (existingRails.length > 0) {
+      existingRails.forEach(function (rail) {
+        d.remove(rail);
+      });
+    }
+    d.appendTo(i.scrollbarYRail, element);
+  }
+
+  if (!i.settings.suppressScrollX && i.containerWidth + i.settings.scrollXMarginOffset < i.contentWidth) {
+    i.scrollbarXActive = true;
+    i.railXWidth = i.containerWidth - i.railXMarginWidth;
+    i.railXRatio = i.containerWidth / i.railXWidth;
+    i.scrollbarXWidth = getThumbSize(i, h.toInt(i.railXWidth * i.containerWidth / i.contentWidth));
+    i.scrollbarXLeft = h.toInt((i.negativeScrollAdjustment + element.scrollLeft) * (i.railXWidth - i.scrollbarXWidth) / (i.contentWidth - i.containerWidth));
+  } else {
+    i.scrollbarXActive = false;
+  }
+
+  if (!i.settings.suppressScrollY && i.containerHeight + i.settings.scrollYMarginOffset < i.contentHeight) {
+    i.scrollbarYActive = true;
+    i.railYHeight = i.containerHeight - i.railYMarginHeight;
+    i.railYRatio = i.containerHeight / i.railYHeight;
+    i.scrollbarYHeight = getThumbSize(i, h.toInt(i.railYHeight * i.containerHeight / i.contentHeight));
+    i.scrollbarYTop = h.toInt(element.scrollTop * (i.railYHeight - i.scrollbarYHeight) / (i.contentHeight - i.containerHeight));
+  } else {
+    i.scrollbarYActive = false;
+  }
+
+  if (i.scrollbarXLeft >= i.railXWidth - i.scrollbarXWidth) {
+    i.scrollbarXLeft = i.railXWidth - i.scrollbarXWidth;
+  }
+  if (i.scrollbarYTop >= i.railYHeight - i.scrollbarYHeight) {
+    i.scrollbarYTop = i.railYHeight - i.scrollbarYHeight;
+  }
+
+  updateCss(element, i);
+
+  if (i.scrollbarXActive) {
+    cls.add(element, 'ps-active-x');
+  } else {
+    cls.remove(element, 'ps-active-x');
+    i.scrollbarXWidth = 0;
+    i.scrollbarXLeft = 0;
+    updateScroll(element, 'left', 0);
+  }
+  if (i.scrollbarYActive) {
+    cls.add(element, 'ps-active-y');
+  } else {
+    cls.remove(element, 'ps-active-y');
+    i.scrollbarYHeight = 0;
+    i.scrollbarYTop = 0;
+    updateScroll(element, 'top', 0);
+  }
+};
+
+},{"../lib/class":2,"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-scroll":20}],20:[function(require,module,exports){
+'use strict';
+
+var instances = require('./instances');
+
+var upEvent = document.createEvent('Event')
+  , downEvent = document.createEvent('Event')
+  , leftEvent = document.createEvent('Event')
+  , rightEvent = document.createEvent('Event')
+  , yEvent = document.createEvent('Event')
+  , xEvent = document.createEvent('Event')
+  , xStartEvent = document.createEvent('Event')
+  , xEndEvent = document.createEvent('Event')
+  , yStartEvent = document.createEvent('Event')
+  , yEndEvent = document.createEvent('Event')
+  , lastTop
+  , lastLeft;
+
+upEvent.initEvent('ps-scroll-up', true, true);
+downEvent.initEvent('ps-scroll-down', true, true);
+leftEvent.initEvent('ps-scroll-left', true, true);
+rightEvent.initEvent('ps-scroll-right', true, true);
+yEvent.initEvent('ps-scroll-y', true, true);
+xEvent.initEvent('ps-scroll-x', true, true);
+xStartEvent.initEvent('ps-x-reach-start', true, true);
+xEndEvent.initEvent('ps-x-reach-end', true, true);
+yStartEvent.initEvent('ps-y-reach-start', true, true);
+yEndEvent.initEvent('ps-y-reach-end', true, true);
+
+module.exports = function (element, axis, value) {
+  if (typeof element === 'undefined') {
+    throw 'You must provide an element to the update-scroll function';
+  }
+
+  if (typeof axis === 'undefined') {
+    throw 'You must provide an axis to the update-scroll function';
+  }
+
+  if (typeof value === 'undefined') {
+    throw 'You must provide a value to the update-scroll function';
+  }
+
+  if (axis === 'top' && value <= 0) {
+    element.scrollTop = value = 0; // don't allow negative scroll
+    element.dispatchEvent(yStartEvent);
+  }
+
+  if (axis === 'left' && value <= 0) {
+    element.scrollLeft = value = 0; // don't allow negative scroll
+    element.dispatchEvent(xStartEvent);
+  }
+
+  var i = instances.get(element);
+
+  if (axis === 'top' && value >= i.contentHeight - i.containerHeight) {
+    element.scrollTop = value = i.contentHeight - i.containerHeight; // don't allow scroll past container
+    element.dispatchEvent(yEndEvent);
+  }
+
+  if (axis === 'left' && value >= i.contentWidth - i.containerWidth) {
+    element.scrollLeft = value = i.contentWidth - i.containerWidth; // don't allow scroll past container
+    element.dispatchEvent(xEndEvent);
+  }
+
+  if (!lastTop) {
+    lastTop = element.scrollTop;
+  }
+
+  if (!lastLeft) {
+    lastLeft = element.scrollLeft;
+  }
+
+  if (axis === 'top' && value < lastTop) {
+    element.dispatchEvent(upEvent);
+  }
+
+  if (axis === 'top' && value > lastTop) {
+    element.dispatchEvent(downEvent);
+  }
+
+  if (axis === 'left' && value < lastLeft) {
+    element.dispatchEvent(leftEvent);
+  }
+
+  if (axis === 'left' && value > lastLeft) {
+    element.dispatchEvent(rightEvent);
+  }
+
+  if (axis === 'top') {
+    element.scrollTop = lastTop = value;
+    element.dispatchEvent(yEvent);
+  }
+
+  if (axis === 'left') {
+    element.scrollLeft = lastLeft = value;
+    element.dispatchEvent(xEvent);
+  }
+
+};
+
+},{"./instances":18}],21:[function(require,module,exports){
+'use strict';
+
+var d = require('../lib/dom')
+  , h = require('../lib/helper')
+  , instances = require('./instances')
+  , updateGeometry = require('./update-geometry')
+  , updateScroll = require('./update-scroll');
+
+module.exports = function (element) {
+  var i = instances.get(element);
+
+  if (!i) {
+    return;
+  }
+
+  // Recalcuate negative scrollLeft adjustment
+  i.negativeScrollAdjustment = i.isNegativeScroll ? element.scrollWidth - element.clientWidth : 0;
+
+  // Recalculate rail margins
+  d.css(i.scrollbarXRail, 'display', 'block');
+  d.css(i.scrollbarYRail, 'display', 'block');
+  i.railXMarginWidth = h.toInt(d.css(i.scrollbarXRail, 'marginLeft')) + h.toInt(d.css(i.scrollbarXRail, 'marginRight'));
+  i.railYMarginHeight = h.toInt(d.css(i.scrollbarYRail, 'marginTop')) + h.toInt(d.css(i.scrollbarYRail, 'marginBottom'));
+
+  // Hide scrollbars not to affect scrollWidth and scrollHeight
+  d.css(i.scrollbarXRail, 'display', 'none');
+  d.css(i.scrollbarYRail, 'display', 'none');
+
+  updateGeometry(element);
+
+  // Update top/left scroll to trigger events
+  updateScroll(element, 'top', element.scrollTop);
+  updateScroll(element, 'left', element.scrollLeft);
+
+  d.css(i.scrollbarXRail, 'display', '');
+  d.css(i.scrollbarYRail, 'display', '');
+};
+
+},{"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-geometry":19,"./update-scroll":20}]},{},[1]);
diff --git a/library/perfect-scrollbar/perfect-scrollbar.min.css b/library/perfect-scrollbar/perfect-scrollbar.min.css
new file mode 100644 (file)
index 0000000..e8d2f37
--- /dev/null
@@ -0,0 +1,2 @@
+/* perfect-scrollbar v0.6.10 */
+.ps-container{-ms-touch-action:none;touch-action:none;overflow:hidden !important;-ms-overflow-style:none}@supports (-ms-overflow-style: none){.ps-container{overflow:auto !important}}@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none){.ps-container{overflow:auto !important}}.ps-container.ps-active-x>.ps-scrollbar-x-rail,.ps-container.ps-active-y>.ps-scrollbar-y-rail{display:block;background-color:transparent}.ps-container.ps-in-scrolling{pointer-events:none}.ps-container.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail{background-color:#eee;opacity:0.9}.ps-container.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail>.ps-scrollbar-x{background-color:#999}.ps-container.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail{background-color:#eee;opacity:0.9}.ps-container.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail>.ps-scrollbar-y{background-color:#999}.ps-container>.ps-scrollbar-x-rail{display:none;position:absolute;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;opacity:0;-webkit-transition:background-color .2s linear, opacity .2s linear;-moz-transition:background-color .2s linear, opacity .2s linear;-o-transition:background-color .2s linear, opacity .2s linear;transition:background-color .2s linear, opacity .2s linear;bottom:3px;height:8px}.ps-container>.ps-scrollbar-x-rail>.ps-scrollbar-x{position:absolute;background-color:#aaa;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-transition:background-color .2s linear;-moz-transition:background-color .2s linear;-o-transition:background-color .2s linear;transition:background-color .2s linear;bottom:0;height:8px}.ps-container>.ps-scrollbar-y-rail{display:none;position:absolute;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;opacity:0;-webkit-transition:background-color .2s linear, opacity .2s linear;-moz-transition:background-color .2s linear, opacity .2s linear;-o-transition:background-color .2s linear, opacity .2s linear;transition:background-color .2s linear, opacity .2s linear;right:3px;width:8px}.ps-container>.ps-scrollbar-y-rail>.ps-scrollbar-y{position:absolute;background-color:#aaa;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-transition:background-color .2s linear;-moz-transition:background-color .2s linear;-o-transition:background-color .2s linear;transition:background-color .2s linear;right:0;width:8px}.ps-container:hover.ps-in-scrolling{pointer-events:none}.ps-container:hover.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail{background-color:#eee;opacity:0.9}.ps-container:hover.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail>.ps-scrollbar-x{background-color:#999}.ps-container:hover.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail{background-color:#eee;opacity:0.9}.ps-container:hover.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail>.ps-scrollbar-y{background-color:#999}.ps-container:hover>.ps-scrollbar-x-rail,.ps-container:hover>.ps-scrollbar-y-rail{opacity:0.6}.ps-container:hover>.ps-scrollbar-x-rail:hover{background-color:#eee;opacity:0.9}.ps-container:hover>.ps-scrollbar-x-rail:hover>.ps-scrollbar-x{background-color:#999}.ps-container:hover>.ps-scrollbar-y-rail:hover{background-color:#eee;opacity:0.9}.ps-container:hover>.ps-scrollbar-y-rail:hover>.ps-scrollbar-y{background-color:#999}
index 9e330fd1dc0380bf127d61be8016a67cbb10c8f4..7f9000807be62021fa9535778bdc5bd5972b5c97 100644 (file)
@@ -1,15 +1,27 @@
 <?php
 
  /**
-  * Friendica admin
-  */
-require_once("include/remoteupdate.php");
+ * @file mod/admin.php
+ * 
+ * @brief Friendica admin
+ */
+
+
 require_once("include/enotify.php");
 require_once("include/text.php");
 
-
 /**
+ * @brief Process send data from the admin panels subpages
+ *
+ * This function acts as relais for processing the data send from the subpages
+ * of the admin panel. Depending on the 1st parameter of the url (argv[1])
+ * specialized functions are called to process the data from the subpages.
+ *
+ * The function itself does not return anything, but the subsequencely function
+ * return the HTML for the pages of the admin panel.
+ *
  * @param App $a
+ *
  */
 function admin_post(&$a){
 
@@ -26,7 +38,7 @@ function admin_post(&$a){
 
 
        // urls
-       if ($a->argc > 1){
+       if($a->argc > 1) {
                switch ($a->argv[1]){
                        case 'site':
                                admin_page_site_post($a);
@@ -35,8 +47,8 @@ function admin_post(&$a){
                                admin_page_users_post($a);
                                break;
                        case 'plugins':
-                               if ($a->argc > 2 &&
-                                       is_file("addon/".$a->argv[2]."/".$a->argv[2].".php")){
+                               if($a->argc > 2 &&
+                                       is_file("addon/".$a->argv[2]."/".$a->argv[2].".php")) {
                                                @include_once("addon/".$a->argv[2]."/".$a->argv[2].".php");
                                                if(function_exists($a->argv[2].'_plugin_admin_post')) {
                                                        $func = $a->argv[2].'_plugin_admin_post';
@@ -47,14 +59,14 @@ function admin_post(&$a){
                                return; // NOTREACHED
                                break;
                        case 'themes':
-                               if ($a->argc < 2) {
+                               if($a->argc < 2) {
                                        if(is_ajax()) return;
                                        goaway($a->get_baseurl(true) . '/admin/' );
                                        return;
                                }
 
                                $theme = $a->argv[2];
-                               if (is_file("view/theme/$theme/config.php")){
+                               if(is_file("view/theme/$theme/config.php")){
                                        function __call_theme_admin_post(&$a, $theme) {
                                                $orig_theme = $a->theme;
                                                $orig_page = $a->page;
@@ -66,7 +78,7 @@ function admin_post(&$a){
 
                                                $init = $theme."_init";
                                                if(function_exists($init)) $init($a);
-                                               if(function_exists("theme_admin_post")){
+                                               if(function_exists("theme_admin_post")) {
                                                        $admin_form = theme_admin_post($a);
                                                }
 
@@ -83,15 +95,15 @@ function admin_post(&$a){
                                goaway($a->get_baseurl(true) . '/admin/themes/' . $theme );
                                return;
                                break;
+                       case 'features':
+                               admin_page_features_post($a);
+                               break;
                        case 'logs':
                                admin_page_logs_post($a);
                                break;
                        case 'dbsync':
                                admin_page_dbsync_post($a);
                                break;
-                       case 'update':
-                               admin_page_remoteupdate_post($a);
-                               break;
                }
        }
 
@@ -100,6 +112,19 @@ function admin_post(&$a){
 }
 
 /**
+ * @brief Generates content of the admin panel pages
+ *
+ * This function generates the content for the admin panel. It consists of the
+ * aside menu (same for the entire admin panel) and the code for the soecified
+ * subpage of the panel.
+ *
+ * The structure of the adress is: /admin/subpage/details though "details" is
+ * only necessary for some subpages, like themes or addons where it is the name
+ * of one theme resp. addon from which the details should be shown. Content for
+ * the subpages is generated in separate functions for each of the subpages.
+ *
+ * The returned string hold the generated HTML code of the page.
+ *
  * @param App $a
  * @return string
  */
@@ -118,55 +143,62 @@ function admin_content(&$a) {
        //      apc_delete($toDelete);
        //}
 
-       /**
+       // Header stuff
+       $a->page['htmlhead'] .= replace_macros(get_markup_template('admin_settings_head.tpl'), array());
+
+       /*
         * Side bar links
         */
-
+       $aside_tools = array();
        // array( url, name, extra css classes )
-       $aside = Array(
-               'site'   =>     Array($a->get_baseurl(true)."/admin/site/", t("Site") , "site"),
-               'users'  =>     Array($a->get_baseurl(true)."/admin/users/", t("Users") , "users"),
-               'plugins'=>     Array($a->get_baseurl(true)."/admin/plugins/", t("Plugins") , "plugins"),
-               'themes' =>     Array($a->get_baseurl(true)."/admin/themes/", t("Themes") , "themes"),
-               'dbsync' =>     Array($a->get_baseurl(true)."/admin/dbsync/", t('DB updates'), "dbsync"),
-               'queue'  =>     Array($a->get_baseurl(true)."/admin/queue/", t('Inspect Queue'), "queue"),
-               //'update' =>   Array($a->get_baseurl(true)."/admin/update/", t("Software Update") , "update")
+       // not part of $aside to make the template more adjustable
+       $aside_sub = array(
+               'site'   =>     array($a->get_baseurl(true)."/admin/site/", t("Site") , "site"),
+               'users'  =>     array($a->get_baseurl(true)."/admin/users/", t("Users") , "users"),
+               'plugins'=>     array($a->get_baseurl(true)."/admin/plugins/", t("Plugins") , "plugins"),
+               'themes' =>     array($a->get_baseurl(true)."/admin/themes/", t("Themes") , "themes"),
+               'features' =>   array($a->get_baseurl(true)."/admin/features/", t("Additional features") , "features"),
+               'dbsync' =>     array($a->get_baseurl(true)."/admin/dbsync/", t('DB updates'), "dbsync"),
+               'queue'  =>     array($a->get_baseurl(true)."/admin/queue/", t('Inspect Queue'), "queue"),
+               'federation' => array($a->get_baseurl(true)."/admin/federation/", t('Federation Statistics'), "federation"),
        );
 
        /* get plugins admin page */
 
        $r = q("SELECT `name` FROM `addon` WHERE `plugin_admin`=1 ORDER BY `name`");
-       $aside['plugins_admin']=Array();
+       $aside_tools['plugins_admin']=array();
        foreach ($r as $h){
                $plugin =$h['name'];
-               $aside['plugins_admin'][] = Array($a->get_baseurl(true)."/admin/plugins/".$plugin, $plugin, "plugin");
+               $aside_tools['plugins_admin'][] = array($a->get_baseurl(true)."/admin/plugins/".$plugin, $plugin, "plugin");
                // temp plugins with admin
                $a->plugins_admin[] = $plugin;
        }
 
-       $aside['logs'] = Array($a->get_baseurl(true)."/admin/logs/", t("Logs"), "logs");
-       $aside['diagnostics_probe'] = Array($a->get_baseurl(true).'/probe/', t('probe address'), 'probe');
-       $aside['diagnostics_webfinger'] = Array($a->get_baseurl(true).'/webfinger/', t('check webfinger'), 'webfinger');
+       $aside_tools['logs'] = array($a->get_baseurl(true)."/admin/logs/", t("Logs"), "logs");
+       $aside_tools['viewlogs'] = array($a->get_baseurl(true)."/admin/viewlogs/", t("View Logs"), 'viewlogs');
+       $aside_tools['diagnostics_probe'] = array($a->get_baseurl(true).'/probe/', t('probe address'), 'probe');
+       $aside_tools['diagnostics_webfinger'] = array($a->get_baseurl(true).'/webfinger/', t('check webfinger'), 'webfinger');
 
        $t = get_markup_template("admin_aside.tpl");
        $a->page['aside'] .= replace_macros( $t, array(
-                       '$admin' => $aside,
-                       '$admtxt' => t('Admin'),
-                       '$plugadmtxt' => t('Plugin Features'),
-                       '$logtxt' => t('Logs'),
-                       '$diagnosticstxt' => t('diagnostics'),
-                       '$h_pending' => t('User registrations waiting for confirmation'),
-                       '$admurl'=> $a->get_baseurl(true)."/admin/"
+               '$admin' => $aside_tools,
+               '$subpages' => $aside_sub,
+               '$admtxt' => t('Admin'),
+               '$plugadmtxt' => t('Plugin Features'),
+               '$logtxt' => t('Logs'),
+               '$diagnosticstxt' => t('diagnostics'),
+               '$h_pending' => t('User registrations waiting for confirmation'),
+               '$admurl'=> $a->get_baseurl(true)."/admin/"
        ));
 
 
 
-       /**
+       /*
         * Page content
         */
        $o = '';
        // urls
-       if ($a->argc > 1){
+       if($a->argc > 1) {
                switch ($a->argv[1]){
                        case 'site':
                                $o = admin_page_site($a);
@@ -180,17 +212,23 @@ function admin_content(&$a) {
                        case 'themes':
                                $o = admin_page_themes($a);
                                break;
+                       case 'features':
+                               $o = admin_page_features($a);
+                               break;
                        case 'logs':
                                $o = admin_page_logs($a);
                                break;
+                       case 'viewlogs':
+                               $o = admin_page_viewlogs($a);
+                               break;
                        case 'dbsync':
                                $o = admin_page_dbsync($a);
                                break;
-                       case 'update':
-                               $o = admin_page_remoteupdate($a);
-                               break;
                        case 'queue':
-                               $o = admin_page_queue($a);
+                               $o = admin_page_queue($a);
+                               break;
+                       case 'federation':
+                               $o = admin_page_federation($a);
                                break;
                        default:
                                notice( t("Item not found.") );
@@ -209,13 +247,137 @@ function admin_content(&$a) {
 }
 
 /**
- * Admin Inspect Queue Page
+ * @brief Subpage with some stats about "the federation" network
+ *
+ * This function generates the "Federation Statistics" subpage for the admin
+ * panel. The page lists some numbers to the part of "The Federation" known to
+ * the node. This data includes the different connected networks (e.g.
+ * Diaspora, Hubzilla, GNU Social) and the used versions in the different
+ * networks.
+ *
+ * The returned string contains the HTML code of the subpage for display.
+ *
  * @param App $a
- * return string
+ * @return string
+ */
+function admin_page_federation(&$a) {
+       // get counts on active friendica, diaspora, redmatrix, hubzilla, gnu
+       // social and statusnet nodes this node is knowing
+       //
+       // We are looking for the following platforms in the DB, "Red" should find
+       // all variants of that platform ID string as the q() function is stripping
+       // off one % two of them are needed in the query
+       // Add more platforms if you like, when one returns 0 known nodes it is not
+       // displayed on the stats page.
+       $platforms = array('Friendica', 'Diaspora', '%%red%%', 'Hubzilla', 'GNU Social', 'StatusNet');
+       $counts = array();
+       $total = 0;
+
+       foreach ($platforms as $p) {
+               // get a total count for the platform, the name and version of the
+               // highest version and the protocol tpe
+               $c = q('SELECT count(*) AS total, platform, network, version FROM gserver
+                       WHERE platform LIKE "%s" AND last_contact > last_failure
+                       ORDER BY version ASC;', $p);
+               $total = $total + $c[0]['total'];
+
+               // what versions for that platform do we know at all?
+               // again only the active nodes
+               $v = q('SELECT count(*) AS total, version FROM gserver
+                       WHERE last_contact > last_failure AND platform LIKE "%s" 
+                       GROUP BY version
+                       ORDER BY version;', $p);
+
+               //
+               // clean up version numbers
+               //
+               // in the DB the Diaspora versions have the format x.x.x.x-xx the last
+               // part (-xx) should be removed to clean up the versions from the "head
+               // commit" information and combined into a single entry for x.x.x.x
+               if($p=='Diaspora') {
+                       $newV = array();
+                       $newVv = array();
+                       foreach($v as $vv) {
+                               $newVC = $vv['total'];
+                               $newVV = $vv['version'];
+                               $posDash = strpos($newVV, '-');
+                               if($posDash) 
+                                       $newVV = substr($newVV, 0, $posDash);
+                               if(isset($newV[$newVV]))
+                                       $newV[$newVV] += $newVC; 
+                               else
+                                       $newV[$newVV] = $newVC; 
+                       }
+                       foreach ($newV as $key => $value) {
+                               array_push($newVv, array('total'=>$value, 'version'=>$key));
+                       }
+                       $v = $newVv;
+               }
+
+               // early friendica versions have the format x.x.xxxx where xxxx is the
+               // DB version stamp; those should be operated out and versions be
+               // conbined
+               if($p=='Friendica') {
+                       $newV = array();
+                       $newVv = array();
+                       foreach ($v as $vv) {
+                               $newVC = $vv['total'];
+                               $newVV = $vv['version'];
+                               $lastDot = strrpos($newVV,'.');
+                               $len = strlen($newVV)-1;
+                               if(($lastDot == $len-4) && (!strrpos($newVV,'-rc')==$len-3))
+                                       $newVV = substr($newVV, 0, $lastDot);
+                               if(isset($newV[$newVV]))
+                                       $newV[$newVV] += $newVC;
+                               else
+                                       $newV[$newVV] = $newVC;
+                       }
+                       foreach ($newV as $key => $value) {
+                               array_push($newVv, array('total'=>$value, 'version'=>$key));
+                       }
+                       $v = $newVv;
+               }
+
+               // the 3rd array item is needed for the JavaScript graphs as JS does
+               // not like some characters in the names of variables...
+               $counts[$p]=array($c[0], $v, str_replace(array(' ','%'),'',$p));
+       }
+
+       // some helpful text
+       $intro = t('This page offers you some numbers to the known part of the federated social network your Friendica node is part of. These numbers are not complete but only reflect the part of the network your node is aware of.');
+       $hint = t('The <em>Auto Discovered Contact Directory</em> feature is not enabled, it will improve the data displayed here.');
+
+       // load the template, replace the macros and return the page content
+       $t = get_markup_template("admin_federation.tpl");
+       return replace_macros($t, array(
+               '$title' => t('Administration'),
+               '$page' => t('Federation Statistics'),
+               '$intro' => $intro,
+               '$hint' => $hint,
+               '$autoactive' => get_config('system', 'poco_completion'),
+               '$counts' => $counts,
+               '$version' => FRIENDICA_VERSION,
+               '$legendtext' => sprintf(t('Currently this node is aware of %d nodes from the following platforms:'), $total),
+               '$baseurl' => $a->get_baseurl(),
+       ));
+}
+
+/**
+ * @brief Admin Inspect Queue Page
+ *
+ * Generates a page for the admin to have a look into the current queue of
+ * postings that are not deliverabke. Shown are the name and url of the
+ * recipient, the delivery network and the dates when the posting was generated
+ * and the last time tried to deliver the posting.
+ *
+ * The returned string holds the content of the page.
+ *
+ * @param App $a
+ * @return string
  */
 function admin_page_queue(&$a) {
-       // get content from the queue table
-       $r = q("SELECT c.name,c.nurl,q.id,q.network,q.created,q.last from queue as q, contact as c where c.id=q.cid order by q.cid, q.created;");
+       // get content from the queue table
+       $r = q("SELECT c.name,c.nurl,q.id,q.network,q.created,q.last from queue as q, contact as c where c.id=q.cid order by q.cid, q.created;");
 
        $t = get_markup_template("admin_queue.tpl");
        return replace_macros($t, array(
@@ -232,20 +394,27 @@ function admin_page_queue(&$a) {
                '$entries' => $r,
        ));
 }
+
 /**
- * Admin Summary Page
+ * @brief Admin Summary Page
+ *
+ * The summary page is the "start page" of the admin panel. It gives the admin
+ * a first overview of the open adminastrative tasks.
+ *
+ * The returned string contains the HTML content of the generated page.
+ *
  * @param App $a
  * @return string
  */
 function admin_page_summary(&$a) {
        $r = q("SELECT `page-flags`, COUNT(uid) as `count` FROM `user` GROUP BY `page-flags`");
-       $accounts = Array(
-               Array( t('Normal Account'), 0),
-               Array( t('Soapbox Account'), 0),
-               Array( t('Community/Celebrity Account'), 0),
-               Array( t('Automatic Friend Account'), 0),
-               Array( t('Blog Account'), 0),
-               Array( t('Private Forum'), 0)
+       $accounts = array(
+               array( t('Normal Account'), 0),
+               array( t('Soapbox Account'), 0),
+               array( t('Community/Celebrity Account'), 0),
+               array( t('Automatic Friend Account'), 0),
+               array( t('Blog Account'), 0),
+               array( t('Private Forum'), 0)
        );
 
        $users=0;
@@ -272,37 +441,37 @@ function admin_page_summary(&$a) {
                '$title' => t('Administration'),
                '$page' => t('Summary'),
                '$queues' => $queues,
-               '$users' => Array( t('Registered users'), $users),
+               '$users' => array( t('Registered users'), $users),
                '$accounts' => $accounts,
-               '$pending' => Array( t('Pending registrations'), $pending),
-               '$version' => Array( t('Version'), FRIENDICA_VERSION),
+               '$pending' => array( t('Pending registrations'), $pending),
+               '$version' => array( t('Version'), FRIENDICA_VERSION),
                '$baseurl' => $a->get_baseurl(),
                '$platform' => FRIENDICA_PLATFORM,
                '$codename' => FRIENDICA_CODENAME,
                '$build' =>  get_config('system','build'),
-               '$plugins' => Array( t('Active plugins'), $a->plugins )
+               '$plugins' => array( t('Active plugins'), $a->plugins )
        ));
 }
 
-
 /**
- * Admin Site Page
- *  @param App $a
+ * @brief Process send data from Admin Site Page
+ * 
+ * @param App $a
  */
-function admin_page_site_post(&$a){
-       if (!x($_POST,"page_site")){
+function admin_page_site_post(&$a) {
+       if(!x($_POST,"page_site")) {
                return;
        }
 
        check_form_security_token_redirectOnErr('/admin/site', 'admin_site');
 
        // relocate
-       if (x($_POST,'relocate') && x($_POST,'relocate_url') && $_POST['relocate_url']!=""){
+       if(x($_POST,'relocate') && x($_POST,'relocate_url') && $_POST['relocate_url']!="") {
                $new_url = $_POST['relocate_url'];
                $new_url = rtrim($new_url,"/");
 
                $parsed = @parse_url($new_url);
-               if (!$parsed || (!x($parsed,'host') || !x($parsed,'scheme'))) {
+               if(!$parsed || (!x($parsed,'host') || !x($parsed,'scheme'))) {
                        notice(t("Can not parse base url. Must have at least <scheme>://<domain>"));
                        goaway($a->get_baseurl(true) . '/admin/site' );
                }
@@ -331,7 +500,7 @@ function admin_page_site_post(&$a){
 
                        $q = sprintf("UPDATE %s SET %s;", $table_name, $upds);
                        $r = q($q);
-                       if (!$r) {
+                       if(!$r) {
                                notice( "Failed updating '$table_name': " . $db->error );
                                goaway($a->get_baseurl(true) . '/admin/site' );
                        }
@@ -341,7 +510,7 @@ function admin_page_site_post(&$a){
                update_table("profile", array('photo', 'thumb'), $old_url, $new_url);
                update_table("term", array('url'), $old_url, $new_url);
                update_table("contact", array('photo','thumb','micro','url','nurl','request','notify','poll','confirm','poco'), $old_url, $new_url);
-               update_table("unique_contacts", array('url'), $old_url, $new_url);
+               update_table("gcontact", array('photo','url','nurl','server_url'), $old_url, $new_url);
                update_table("item", array('owner-link','owner-avatar','author-name','author-link','author-avatar','body','plink','tag'), $old_url, $new_url);
 
                // update config
@@ -367,7 +536,7 @@ function admin_page_site_post(&$a){
        $banner                 =       ((x($_POST,'banner'))                   ? trim($_POST['banner'])                        : false);
        $shortcut_icon          =       ((x($_POST,'shortcut_icon'))            ? notags(trim($_POST['shortcut_icon']))         : '');
        $touch_icon             =       ((x($_POST,'touch_icon'))               ? notags(trim($_POST['touch_icon']))            : '');
-       $info                   =       ((x($_POST,'info'))                     ? trim($_POST['info'])                  : false);
+       $info                   =       ((x($_POST,'info'))                     ? trim($_POST['info'])                          : false);
        $language               =       ((x($_POST,'language'))                 ? notags(trim($_POST['language']))              : '');
        $theme                  =       ((x($_POST,'theme'))                    ? notags(trim($_POST['theme']))                 : '');
        $theme_mobile           =       ((x($_POST,'theme_mobile'))             ? notags(trim($_POST['theme_mobile']))          : '');
@@ -386,13 +555,13 @@ function admin_page_site_post(&$a){
        $allowed_email          =       ((x($_POST,'allowed_email'))            ? notags(trim($_POST['allowed_email']))         : '');
        $block_public           =       ((x($_POST,'block_public'))             ? True                                          : False);
        $force_publish          =       ((x($_POST,'publish_all'))              ? True                                          : False);
-       $global_directory       =       ((x($_POST,'directory'))                ? notags(trim($_POST['directory']))     : '');
+       $global_directory       =       ((x($_POST,'directory'))                ? notags(trim($_POST['directory']))             : '');
        $thread_allow           =       ((x($_POST,'thread_allow'))             ? True                                          : False);
-       $newuser_private                =       ((x($_POST,'newuser_private'))          ? True                                          : False);
-       $enotify_no_content             =       ((x($_POST,'enotify_no_content'))       ? True                                          : False);
-       $private_addons                 =       ((x($_POST,'private_addons'))           ? True                                          : False);
-       $disable_embedded               =       ((x($_POST,'disable_embedded'))         ? True                                          : False);
-       $allow_users_remote_self        =       ((x($_POST,'allow_users_remote_self'))          ? True                                          : False);
+       $newuser_private                =       ((x($_POST,'newuser_private'))          ? True                                  : False);
+       $enotify_no_content             =       ((x($_POST,'enotify_no_content'))       ? True                                  : False);
+       $private_addons                 =       ((x($_POST,'private_addons'))           ? True                                  : False);
+       $disable_embedded               =       ((x($_POST,'disable_embedded'))         ? True                                  : False);
+       $allow_users_remote_self        =       ((x($_POST,'allow_users_remote_self'))  ? True                                  : False);
 
        $no_multi_reg           =       ((x($_POST,'no_multi_reg'))             ? True                                          : False);
        $no_openid              =       !((x($_POST,'no_openid'))               ? True                                          : False);
@@ -438,13 +607,13 @@ function admin_page_site_post(&$a){
        $proxy_disabled         =       ((x($_POST,'proxy_disabled'))           ? True                                          : False);
        $old_pager              =       ((x($_POST,'old_pager'))                ? True                                          : False);
        $only_tag_search        =       ((x($_POST,'only_tag_search'))          ? True                                          : False);
-       $rino                   =       ((x($_POST,'rino'))                             ? intval($_POST['rino'])                                : 0);
+       $rino                   =       ((x($_POST,'rino'))                     ? intval($_POST['rino'])                        : 0);
        $embedly                =       ((x($_POST,'embedly'))                  ? notags(trim($_POST['embedly']))               : '');
 
-       if ($a->get_path() != "")
+       if($a->get_path() != "")
                $diaspora_enabled = false;
 
-       if (!$thread_allow)
+       if(!$thread_allow)
                $ostatus_disabled = true;
 
        if($ssl_policy != intval(get_config('system','ssl_policy'))) {
@@ -508,7 +677,7 @@ function admin_page_site_post(&$a){
        set_config('system','shortcut_icon',$shortcut_icon);
        set_config('system','touch_icon',$touch_icon);
 
-       if ($banner==""){
+       if($banner=="") {
                // don't know why, but del_config doesn't work...
                q("DELETE FROM `config` WHERE `cat` = '%s' AND `k` = '%s' LIMIT 1",
                        dbesc("system"),
@@ -517,19 +686,21 @@ function admin_page_site_post(&$a){
        } else {
                set_config('system','banner', $banner);
        }
-       if ($info=="") {
+
+       if($info=="") {
                del_config('config','info');
        } else {
                set_config('config','info',$info);
        }
        set_config('system','language', $language);
        set_config('system','theme', $theme);
-       if ( $theme_mobile === '---' ) {
+
+       if( $theme_mobile === '---' ) {
                del_config('system','mobile-theme');
        } else {
                set_config('system','mobile-theme', $theme_mobile);
                }
-               if ( $singleuser === '---' ) {
+               if( $singleuser === '---' ) {
                        del_config('system','singleuser');
                } else {
                        set_config('system','singleuser', $singleuser);
@@ -585,7 +756,7 @@ function admin_page_site_post(&$a){
        set_config('system','only_tag_search', $only_tag_search);
 
 
-       if ($rino==2 and !function_exists('mcrypt_create_iv')){
+       if($rino==2 and !function_exists('mcrypt_create_iv')) {
                notice(t("RINO2 needs mcrypt php extension to work."));
        } else {
                set_config('system','rino_encrypt', $rino);
@@ -601,6 +772,10 @@ function admin_page_site_post(&$a){
 }
 
 /**
+ * @brief Generate Admin Site subpage
+ *
+ * This function generates the main configuration page of the admin panel.
+ *
  * @param  App $a
  * @return string
  */
@@ -609,25 +784,25 @@ function admin_page_site(&$a) {
        /* Installed langs */
        $lang_choices = get_avaiable_languages();
 
-       if (strlen(get_config('system','directory_submit_url')) AND
+       if(strlen(get_config('system','directory_submit_url')) AND
                !strlen(get_config('system','directory'))) {
-               set_config('system','directory', dirname(get_config('system','directory_submit_url')));
-               del_config('system','directory_submit_url');
+                       set_config('system','directory', dirname(get_config('system','directory_submit_url')));
+                       del_config('system','directory_submit_url');
        }
 
        /* Installed themes */
        $theme_choices = array();
        $theme_choices_mobile = array();
        $theme_choices_mobile["---"] = t("No special theme for mobile devices");
-       $files = glob('view/theme/*'); /**/
+       $files = glob('view/theme/*');
        if($files) {
                foreach($files as $file) {
-                       if (intval(file_exists($file . '/unsupported')))
+                       if(intval(file_exists($file . '/unsupported')))
                                continue;
 
                        $f = basename($file);
                        $theme_name = ((file_exists($file . '/experimental')) ?  sprintf("%s - \x28Experimental\x29", $f) : $f);
-                       if (file_exists($file . '/mobile')) {
+                       if(file_exists($file . '/mobile')) {
                                $theme_choices_mobile[$f] = $theme_name;
                        } else {
                                $theme_choices[$f] = $theme_name;
@@ -690,7 +865,7 @@ function admin_page_site(&$a) {
        //echo "<pre>"; var_dump($lang_choices); die("</pre>");
 
        /* Register policy */
-       $register_choices = Array(
+       $register_choices = array(
                REGISTER_CLOSED => t("Closed"),
                REGISTER_APPROVE => t("Requires approval"),
                REGISTER_OPEN => t("Open")
@@ -702,7 +877,7 @@ function admin_page_site(&$a) {
                SSL_POLICY_SELFSIGN => t("Self-signed certificate, use SSL for local links only (discouraged)")
        );
 
-       if ($a->config['hostname'] == "")
+       if($a->config['hostname'] == "")
                $a->config['hostname'] = $a->get_hostname();
 
        $diaspora_able = ($a->get_path() == "");
@@ -727,7 +902,7 @@ function admin_page_site(&$a) {
                '$banner'               => array('banner', t("Banner/Logo"), $banner, ""),
                '$shortcut_icon'        => array('shortcut_icon', t("Shortcut icon"), get_config('system','shortcut_icon'),  t("Link to an icon that will be used for browsers.")),
                '$touch_icon'           => array('touch_icon', t("Touch icon"), get_config('system','touch_icon'),  t("Link to an icon that will be used for tablets and mobiles.")),
-               '$info' => array('info',t('Additional Info'), $info, sprintf(t('For public servers: you can add additional information here that will be listed at %s/siteinfo.'), get_server())),
+               '$info'                 => array('info',t('Additional Info'), $info, sprintf(t('For public servers: you can add additional information here that will be listed at %s/siteinfo.'), get_server())),
                '$language'             => array('language', t("System language"), get_config('system','language'), "", $lang_choices),
                '$theme'                => array('theme', t("System theme"), get_config('system','theme'), t("Default system theme - may be over-ridden by user profiles - <a href='#' id='cnftheme'>change theme settings</a>"), $theme_choices),
                '$theme_mobile'         => array('theme_mobile', t("Mobile system theme"), get_config('system','mobile-theme'), t("Theme for mobile devices"), $theme_choices_mobile),
@@ -737,8 +912,8 @@ function admin_page_site(&$a) {
                '$hide_help'            => array('hide_help', t("Hide help entry from navigation menu"), get_config('system','hide_help'), t("Hides the menu entry for the Help pages from the navigation menu. You can still access it calling /help directly.")),
                '$singleuser'           => array('singleuser', t("Single user instance"), get_config('system','singleuser'), t("Make this instance multi-user or single-user for the named user"), $user_names),
                '$maximagesize'         => array('maximagesize', t("Maximum image size"), get_config('system','maximagesize'), t("Maximum size in bytes of uploaded images. Default is 0, which means no limits.")),
-               '$maximagelength'               => array('maximagelength', t("Maximum image length"), get_config('system','max_image_length'), t("Maximum length in pixels of the longest side of uploaded images. Default is -1, which means no limits.")),
-               '$jpegimagequality'             => array('jpegimagequality', t("JPEG image quality"), get_config('system','jpeg_quality'), t("Uploaded JPEGS will be saved at this quality setting [0-100]. Default is 100, which is full quality.")),
+               '$maximagelength'       => array('maximagelength', t("Maximum image length"), get_config('system','max_image_length'), t("Maximum length in pixels of the longest side of uploaded images. Default is -1, which means no limits.")),
+               '$jpegimagequality'     => array('jpegimagequality', t("JPEG image quality"), get_config('system','jpeg_quality'), t("Uploaded JPEGS will be saved at this quality setting [0-100]. Default is 100, which is full quality.")),
 
                '$register_policy'      => array('register_policy', t("Register policy"), $a->config['register_policy'], "", $register_choices),
                '$daily_registrations'  => array('max_daily_registrations', t("Maximum Daily Registrations"), get_config('system', 'max_daily_registrations'), t("If registration is permitted above, this sets the maximum number of new user registrations to accept per day.  If register is set to closed, this setting has no effect.")),
@@ -754,7 +929,7 @@ function admin_page_site(&$a) {
                '$enotify_no_content'   => array('enotify_no_content', t("Don't include post content in email notifications"), get_config('system','enotify_no_content'), t("Don't include the content of a post/comment/private message/etc. in the email notifications that are sent out from this site, as a privacy measure.")),
                '$private_addons'       => array('private_addons', t("Disallow public access to addons listed in the apps menu."), get_config('config','private_addons'), t("Checking this box will restrict addons listed in the apps menu to members only.")),
                '$disable_embedded'     => array('disable_embedded', t("Don't embed private images in posts"), get_config('system','disable_embedded'), t("Don't replace locally-hosted private photos in posts with an embedded copy of the image. This means that contacts who receive posts containing private photos will have to authenticate and load each image, which may take a while.")),
-               '$allow_users_remote_self'      => array('allow_users_remote_self', t('Allow Users to set remote_self'), get_config('system','allow_users_remote_self'), t('With checking this, every user is allowed to mark every contact as a remote_self in the repair contact dialog. Setting this flag on a contact causes mirroring every posting of that contact in the users stream.')),
+               '$allow_users_remote_self' => array('allow_users_remote_self', t('Allow Users to set remote_self'), get_config('system','allow_users_remote_self'), t('With checking this, every user is allowed to mark every contact as a remote_self in the repair contact dialog. Setting this flag on a contact causes mirroring every posting of that contact in the users stream.')),
                '$no_multi_reg'         => array('no_multi_reg', t("Block multiple registrations"),  get_config('system','block_extended_register'), t("Disallow users to register additional accounts for use as pages.")),
                '$no_openid'            => array('no_openid', t("OpenID support"), !get_config('system','no_openid'), t("OpenID support for registration and logins.")),
                '$no_regfullname'       => array('no_regfullname', t("Fullname check"), !get_config('system','no_regfullname'), t("Force users to register with a space between firstname and lastname in Full name, as an antispam measure")),
@@ -762,12 +937,12 @@ function admin_page_site(&$a) {
                '$community_page_style' => array('community_page_style', t("Community Page Style"), get_config('system','community_page_style'), t("Type of community page to show. 'Global community' shows every public posting from an open distributed network that arrived on this server."), $community_page_style_choices),
                '$max_author_posts_community_page' => array('max_author_posts_community_page', t("Posts per user on community page"), get_config('system','max_author_posts_community_page'), t("The maximum number of posts per user on the community page. (Not valid for 'Global Community')")),
                '$ostatus_disabled'     => array('ostatus_disabled', t("Enable OStatus support"), !get_config('system','ostatus_disabled'), t("Provide built-in OStatus \x28StatusNet, GNU Social etc.\x29 compatibility. All communications in OStatus are public, so privacy warnings will be occasionally displayed.")),
-               '$ostatus_poll_interval'        => array('ostatus_poll_interval', t("OStatus conversation completion interval"), (string) intval(get_config('system','ostatus_poll_interval')), t("How often shall the poller check for new entries in OStatus conversations? This can be a very ressource task."), $ostatus_poll_choices),
+               '$ostatus_poll_interval' => array('ostatus_poll_interval', t("OStatus conversation completion interval"), (string) intval(get_config('system','ostatus_poll_interval')), t("How often shall the poller check for new entries in OStatus conversations? This can be a very ressource task."), $ostatus_poll_choices),
                '$ostatus_not_able'     => t("OStatus support can only be enabled if threading is enabled."),
                '$diaspora_able'        => $diaspora_able,
                '$diaspora_not_able'    => t("Diaspora support can't be enabled because Friendica was installed into a sub directory."),
-               '$diaspora_enabled'     => array('diaspora_enabled', t("Enable Diaspora support"), get_config('system','diaspora_enabled'), t("Provide built-in Diaspora network compatibility.")),
-               '$dfrn_only'            => array('dfrn_only', t('Only allow Friendica contacts'), get_config('system','dfrn_only'), t("All contacts must use Friendica protocols. All other built-in communication protocols disabled.")),
+               '$diaspora_enabled'     => array('diaspora_enabled', t("Enable Diaspora support"), get_config('system','diaspora_enabled'), t("Provide built-in Diaspora network compatibility.")),
+               '$dfrn_only'            => array('dfrn_only', t('Only allow Friendica contacts'), get_config('system','dfrn_only'), t("All contacts must use Friendica protocols. All other built-in communication protocols disabled.")),
                '$verifyssl'            => array('verifyssl', t("Verify SSL"), get_config('system','verifyssl'), t("If you wish, you can turn on strict certificate checking. This will mean you cannot connect (at all) to self-signed SSL sites.")),
                '$proxyuser'            => array('proxyuser', t("Proxy user"), get_config('system','proxyuser'), ""),
                '$proxy'                => array('proxy', t("Proxy URL"), get_config('system','proxy'), ""),
@@ -805,13 +980,24 @@ function admin_page_site(&$a) {
                '$rino'                 => array('rino', t("RINO Encryption"), intval(get_config('system','rino_encrypt')), t("Encryption layer between nodes."), array("Disabled", "RINO1 (deprecated)", "RINO2")),
                '$embedly'              => array('embedly', t("Embedly API key"), get_config('system','embedly'), t("<a href='http://embed.ly'>Embedly</a> is used to fetch additional data for web pages. This is an optional parameter.")),
 
-               '$form_security_token' => get_form_security_token("admin_site")
+               '$form_security_token'  => get_form_security_token("admin_site")
 
        ));
 
 }
 
-
+/**
+ * @brief Generates admin panel subpage for DB syncronization
+ *
+ * This page checks if the database of friendica is in sync with the specs.
+ * Should this not be the case, it attemps to sync the structure and notifies
+ * the admin if the automatic process was failing.
+ *
+ * The returned string holds the HTML code of the page.
+ *
+ * @param App $a
+ * @return string
+ **/
 function admin_page_dbsync(&$a) {
 
        $o = '';
@@ -828,17 +1014,17 @@ function admin_page_dbsync(&$a) {
        if(($a->argc > 2) AND (intval($a->argv[2]) OR ($a->argv[2] === 'check'))) {
                require_once("include/dbstructure.php");
                $retval = update_structure(false, true);
-               if (!$retval) {
+               if(!$retval) {
                        $o .= sprintf(t("Database structure update %s was successfully applied."), DB_UPDATE_VERSION)."<br />";
                        set_config('database', 'dbupdate_'.DB_UPDATE_VERSION, 'success');
                } else
                        $o .= sprintf(t("Executing of database structure update %s failed with error: %s"),
                                        DB_UPDATE_VERSION, $retval)."<br />";
-               if ($a->argv[2] === 'check')
+               if($a->argv[2] === 'check')
                        return $o;
        }
 
-       if ($a->argc > 2 && intval($a->argv[2])) {
+       if($a->argc > 2 && intval($a->argv[2])) {
                require_once('update.php');
                $func = 'update_' . intval($a->argv[2]);
                if(function_exists($func)) {
@@ -891,20 +1077,20 @@ function admin_page_dbsync(&$a) {
 }
 
 /**
- * Users admin page
- *
+ * @brief Process data send by Users admin page
+ * 
  * @param App $a
  */
 function admin_page_users_post(&$a){
-       $pending = ( x($_POST, 'pending') ? $_POST['pending'] : Array() );
-       $users = ( x($_POST, 'user') ? $_POST['user'] : Array() );
-       $nu_name = ( x($_POST, 'new_user_name') ? $_POST['new_user_name'] : '');
-       $nu_nickname = ( x($_POST, 'new_user_nickname') ? $_POST['new_user_nickname'] : '');
-       $nu_email = ( x($_POST, 'new_user_email') ? $_POST['new_user_email'] : '');
+       $pending        =       ( x($_POST, 'pending')                  ? $_POST['pending']             : array() );
+       $users          =       ( x($_POST, 'user')                     ? $_POST['user']                : array() );
+       $nu_name        =       ( x($_POST, 'new_user_name')            ? $_POST['new_user_name']       : '');
+       $nu_nickname    =       ( x($_POST, 'new_user_nickname')        ? $_POST['new_user_nickname']   : '');
+       $nu_email       =       ( x($_POST, 'new_user_email')           ? $_POST['new_user_email']      : '');
 
        check_form_security_token_redirectOnErr('/admin/users', 'admin_users');
 
-       if (!($nu_name==="") && !($nu_email==="") && !($nu_nickname==="")) {
+       if(!($nu_name==="") && !($nu_email==="") && !($nu_nickname==="")) {
                require_once('include/user.php');
 
                $result = create_user( array('username'=>$nu_name, 'email'=>$nu_email, 'nickname'=>$nu_nickname, 'verified'=>1)  );
@@ -954,7 +1140,7 @@ function admin_page_users_post(&$a){
 
        }
 
-       if (x($_POST,'page_users_block')){
+       if(x($_POST,'page_users_block')) {
                foreach($users as $uid){
                        q("UPDATE `user` SET `blocked`=1-`blocked` WHERE `uid`=%s",
                                intval( $uid )
@@ -962,7 +1148,7 @@ function admin_page_users_post(&$a){
                }
                notice( sprintf( tt("%s user blocked/unblocked", "%s users blocked/unblocked", count($users)), count($users)) );
        }
-       if (x($_POST,'page_users_delete')){
+       if(x($_POST,'page_users_delete')) {
                require_once("include/Contact.php");
                foreach($users as $uid){
                        user_remove($uid);
@@ -970,13 +1156,13 @@ function admin_page_users_post(&$a){
                notice( sprintf( tt("%s user deleted", "%s users deleted", count($users)), count($users)) );
        }
 
-       if (x($_POST,'page_users_approve')){
+       if(x($_POST,'page_users_approve')) {
                require_once("mod/regmod.php");
                foreach($pending as $hash){
                        user_allow($hash);
                }
        }
-       if (x($_POST,'page_users_deny')){
+       if(x($_POST,'page_users_deny')) {
                require_once("mod/regmod.php");
                foreach($pending as $hash){
                        user_deny($hash);
@@ -987,14 +1173,22 @@ function admin_page_users_post(&$a){
 }
 
 /**
+ * @brief Admin panel subpage for User management
+ *
+ * This function generates the admin panel page for user management of the
+ * node. It offers functionality to add/block/delete users and offers some
+ * statistics about the userbase.
+ *
+ * The returned string holds the HTML code of the page.
+ *
  * @param App $a
  * @return string
  */
 function admin_page_users(&$a){
-       if ($a->argc>2) {
+       if($a->argc>2) {
                $uid = $a->argv[3];
                $user = q("SELECT username, blocked FROM `user` WHERE `uid`=%d", intval($uid));
-               if (count($user)==0){
+               if(count($user)==0) {
                        notice( 'User not found' . EOL);
                        goaway($a->get_baseurl(true) . '/admin/users' );
                        return ''; // NOTREACHED
@@ -1030,14 +1224,12 @@ function admin_page_users(&$a){
 
 
        /* get users */
-
        $total = q("SELECT count(*) as total FROM `user` where 1");
        if(count($total)) {
                $a->set_pager_total($total[0]['total']);
                $a->set_pager_itemspage(100);
        }
 
-
        $users = q("SELECT `user` . * , `contact`.`name` , `contact`.`url` , `contact`.`micro`, `lastitem`.`lastitem_date`, `user`.`account_expired`
                                FROM
                                        (SELECT MAX(`item`.`changed`) as `lastitem_date`, `item`.`uid`
@@ -1058,7 +1250,7 @@ function admin_page_users(&$a){
 
        $adminlist = explode(",", str_replace(" ", "", $a->config['admin_email']));
        $_setup_users = function ($e) use ($adminlist){
-               $accounts = Array(
+               $accounts = array(
                        t('Normal Account'),
                        t('Soapbox Account'),
                        t('Community/Celebrity Account'),
@@ -1080,11 +1272,11 @@ function admin_page_users(&$a){
        // Get rid of dashes in key names, Smarty3 can't handle them
        // and extracting deleted users
 
-       $tmp_users = Array();
-       $deleted = Array();
+       $tmp_users = array();
+       $deleted = array();
 
        while(count($users)) {
-               $new_user = Array();
+               $new_user = array();
                foreach( array_pop($users) as $k => $v) {
                        $k = str_replace('-','_',$k);
                        $new_user[$k] = $v;
@@ -1137,9 +1329,9 @@ function admin_page_users(&$a){
                '$pending' => $pending,
                'deleted' => $deleted,
                '$users' => $users,
-               '$newusername'  => array('new_user_name', t("Name"), '', t("Name of the new user.")),
-               '$newusernickname'  => array('new_user_nickname', t("Nickname"), '', t("Nickname of the new user.")),
-               '$newuseremail'  => array('new_user_email', t("Email"), '', t("Email address of the new user."), '', '', 'email'),
+               '$newusername' => array('new_user_name', t("Name"), '', t("Name of the new user.")),
+               '$newusernickname' => array('new_user_nickname', t("Nickname"), '', t("Nickname of the new user.")),
+               '$newuseremail' => array('new_user_email', t("Email"), '', t("Email address of the new user."), '', '', 'email'),
        ));
        $o .= paginate($a);
        return $o;
@@ -1147,29 +1339,39 @@ function admin_page_users(&$a){
 
 
 /**
- * Plugins admin page
+ * @brief Plugins admin page
+ *
+ * This function generates the admin panel page for managing plugins on the
+ * friendica node. If a plugin name is given a single page showing the details
+ * for this addon is generated. If no name is given, a list of available
+ * plugins is shown.
+ *
+ * The template used for displaying the list of plugins and the details of the
+ * plugin are the same as used for the templates.
+ *
+ * The returned string returned hulds the HTML code of the page.
  *
  * @param App $a
  * @return string
  */
 function admin_page_plugins(&$a){
 
-       /**
+       /*
         * Single plugin
         */
-       if ($a->argc == 3){
+       if($a->argc == 3) {
                $plugin = $a->argv[2];
-               if (!is_file("addon/$plugin/$plugin.php")){
+               if(!is_file("addon/$plugin/$plugin.php")) {
                        notice( t("Item not found.") );
                        return '';
                }
 
-               if (x($_GET,"a") && $_GET['a']=="t"){
+               if(x($_GET,"a") && $_GET['a']=="t") {
                        check_form_security_token_redirectOnErr('/admin/plugins', 'admin_themes', 't');
 
                        // Toggle plugin status
                        $idx = array_search($plugin, $a->plugins);
-                       if ($idx !== false){
+                       if($idx !== false) {
                                unset($a->plugins[$idx]);
                                uninstall_plugin($plugin);
                                info( sprintf( t("Plugin %s disabled."), $plugin ) );
@@ -1182,25 +1384,26 @@ function admin_page_plugins(&$a){
                        goaway($a->get_baseurl(true) . '/admin/plugins' );
                        return ''; // NOTREACHED
                }
+
                // display plugin details
                require_once('library/markdown.php');
 
-               if (in_array($plugin, $a->plugins)){
+               if(in_array($plugin, $a->plugins)) {
                        $status="on"; $action= t("Disable");
                } else {
                        $status="off"; $action= t("Enable");
                }
 
                $readme=Null;
-               if (is_file("addon/$plugin/README.md")){
+               if(is_file("addon/$plugin/README.md")) {
                        $readme = file_get_contents("addon/$plugin/README.md");
                        $readme = Markdown($readme);
-               } else if (is_file("addon/$plugin/README")){
+               } elseif(is_file("addon/$plugin/README")) {
                        $readme = "<pre>". file_get_contents("addon/$plugin/README") ."</pre>";
                }
 
                $admin_form="";
-               if (is_array($a->plugins_admin) && in_array($plugin, $a->plugins_admin)){
+               if(is_array($a->plugins_admin) && in_array($plugin, $a->plugins_admin)) {
                        @require_once("addon/$plugin/$plugin.php");
                        $func = $plugin.'_plugin_admin';
                        $func($a, $admin_form);
@@ -1233,11 +1436,11 @@ function admin_page_plugins(&$a){
 
 
 
-       /**
+       /*
         * List plugins
         */
 
-       if (x($_GET,"a") && $_GET['a']=="r"){
+       if(x($_GET,"a") && $_GET['a']=="r") {
                check_form_security_token_redirectOnErr($a->get_baseurl().'/admin/plugins', 'admin_themes', 't');
                reload_plugins();
                info("Plugins reloaded");
@@ -1245,23 +1448,23 @@ function admin_page_plugins(&$a){
        }
 
        $plugins = array();
-       $files = glob("addon/*/"); /* */
+       $files = glob("addon/*/");
        if($files) {
                foreach($files as $file) {
-                       if (is_dir($file)){
+                       if(is_dir($file)) {
                                list($tmp, $id)=array_map("trim", explode("/",$file));
                                $info = get_plugin_info($id);
                                $show_plugin = true;
 
                                // If the addon is unsupported, then only show it, when it is enabled
-                               if ((strtolower($info["status"]) == "unsupported") AND !in_array($id,  $a->plugins))
+                               if((strtolower($info["status"]) == "unsupported") AND !in_array($id,  $a->plugins))
                                        $show_plugin = false;
 
                                // Override the above szenario, when the admin really wants to see outdated stuff
-                               if (get_config("system", "show_unsupported_addons"))
+                               if(get_config("system", "show_unsupported_addons"))
                                        $show_plugin = true;
 
-                               if ($show_plugin)
+                               if($show_plugin)
                                        $plugins[] = array($id, (in_array($id,  $a->plugins)?"on":"off") , $info);
                        }
                }
@@ -1276,6 +1479,8 @@ function admin_page_plugins(&$a){
                '$baseurl' => $a->get_baseurl(true),
                '$function' => 'plugins',
                '$plugins' => $plugins,
+               '$pcount' => count($plugins), 
+               '$noplugshint' => sprintf( t('There are currently no plugins available on your node. You can find the official plugin repository at %1$s and might find other interesting plugins in the open plugin registry at %2$s'), 'https://github.com/friendica/friendica-addons', 'http://addons.friendi.ca'),
                '$form_security_token' => get_form_security_token("admin_themes"),
        ));
 }
@@ -1340,7 +1545,17 @@ function rebuild_theme_table($themes) {
 
 
 /**
- * Themes admin page
+ * @brief Themes admin page
+ *
+ * This function generates the admin panel page to control the themes available
+ * on the friendica node. If the name of a theme is given as parameter a page
+ * with the details for the theme is shown. Otherwise a list of available
+ * themes is generated.
+ *
+ * The template used for displaying the list of themes and the details of the
+ * themes are the same as used for the plugins.
+ *
+ * The returned string contains the HTML code of the admin panel page.
  *
  * @param App $a
  * @return string
@@ -1356,7 +1571,7 @@ function admin_page_themes(&$a){
                                $allowed_themes[] = trim($x);
 
        $themes = array();
-       $files = glob('view/theme/*'); /* */
+       $files = glob('view/theme/*');
        if($files) {
                foreach($files as $file) {
                        $f = basename($file);
@@ -1364,7 +1579,7 @@ function admin_page_themes(&$a){
                        $is_supported = 1-(intval(file_exists($file . '/unsupported')));
                        $is_allowed = intval(in_array($f,$allowed_themes));
 
-                       if ($is_allowed OR $is_supported OR get_config("system", "show_unsupported_themes"))
+                       if($is_allowed OR $is_supported OR get_config("system", "show_unsupported_themes"))
                                $themes[] = array('name' => $f, 'experimental' => $is_experimental, 'supported' => $is_supported, 'allowed' => $is_allowed);
                }
        }
@@ -1374,18 +1589,18 @@ function admin_page_themes(&$a){
                return '';
        }
 
-       /**
+       /*
         * Single theme
         */
 
-       if ($a->argc == 3){
+       if($a->argc == 3) {
                $theme = $a->argv[2];
-               if(! is_dir("view/theme/$theme")){
+               if(! is_dir("view/theme/$theme")) {
                        notice( t("Item not found.") );
                        return '';
                }
 
-               if (x($_GET,"a") && $_GET['a']=="t"){
+               if(x($_GET,"a") && $_GET['a']=="t") {
                        check_form_security_token_redirectOnErr('/admin/themes', 'admin_themes', 't');
 
                        // Toggle theme status
@@ -1409,22 +1624,22 @@ function admin_page_themes(&$a){
                // display theme details
                require_once('library/markdown.php');
 
-               if (theme_status($themes,$theme)) {
+               if(theme_status($themes,$theme)) {
                        $status="on"; $action= t("Disable");
                } else {
                        $status="off"; $action= t("Enable");
                }
 
                $readme=Null;
-               if (is_file("view/theme/$theme/README.md")){
+               if(is_file("view/theme/$theme/README.md")) {
                        $readme = file_get_contents("view/theme/$theme/README.md");
                        $readme = Markdown($readme);
-               } else if (is_file("view/theme/$theme/README")){
+               } elseif(is_file("view/theme/$theme/README")) {
                        $readme = "<pre>". file_get_contents("view/theme/$theme/README") ."</pre>";
                }
 
                $admin_form="";
-               if (is_file("view/theme/$theme/config.php")){
+               if(is_file("view/theme/$theme/config.php")) {
                        function __get_theme_admin_form(&$a, $theme) {
                                $orig_theme = $a->theme;
                                $orig_page = $a->page;
@@ -1436,7 +1651,7 @@ function admin_page_themes(&$a){
 
                                $init = $theme."_init";
                                if(function_exists($init)) $init($a);
-                               if(function_exists("theme_admin")){
+                               if(function_exists("theme_admin")) {
                                        $admin_form = theme_admin($a);
                                }
 
@@ -1452,6 +1667,7 @@ function admin_page_themes(&$a){
                if(! stristr($screenshot[0],$theme))
                        $screenshot = null;
 
+
                $t = get_markup_template("admin_plugins_details.tpl");
                return replace_macros($t, array(
                        '$title' => t('Administration'),
@@ -1459,7 +1675,6 @@ function admin_page_themes(&$a){
                        '$toggle' => t('Toggle'),
                        '$settings' => t('Settings'),
                        '$baseurl' => $a->get_baseurl(true),
-
                        '$plugin' => $theme,
                        '$status' => $status,
                        '$action' => $action,
@@ -1477,11 +1692,11 @@ function admin_page_themes(&$a){
 
 
        // reload active themes
-       if (x($_GET,"a") && $_GET['a']=="r"){
+       if(x($_GET,"a") && $_GET['a']=="r") {
                check_form_security_token_redirectOnErr($a->get_baseurl().'/admin/themes', 'admin_themes', 't');
-               if ($themes) {
+               if($themes) {
                        foreach($themes as $th) {
-                               if ($th['allowed']) {
+                               if($th['allowed']) {
                                        uninstall_theme($th['name']);
                                        install_theme($th['name']);
                                }
@@ -1491,7 +1706,7 @@ function admin_page_themes(&$a){
                goaway($a->get_baseurl().'/admin/themes');
        }
 
-       /**
+       /*
         * List themes
         */
 
@@ -1512,6 +1727,8 @@ function admin_page_themes(&$a){
                '$baseurl' => $a->get_baseurl(true),
                '$function' => 'themes',
                '$plugins' => $xthemes,
+               '$pcount' => count($themes),
+               '$noplugshint' => sprintf(t('No themes found on the system. They should be paced in %1$s'),'<code>/view/themes</code>'),
                '$experimental' => t('[Experimental]'),
                '$unsupported' => t('[Unsupported]'),
                '$form_security_token' => get_form_security_token("admin_themes"),
@@ -1520,24 +1737,21 @@ function admin_page_themes(&$a){
 
 
 /**
- * Logs admin page
- *
+ * @brief Prosesses data send by Logs admin page
+ * 
  * @param App $a
  */
-
 function admin_page_logs_post(&$a) {
-       if (x($_POST,"page_logs")) {
+       if(x($_POST,"page_logs")) {
                check_form_security_token_redirectOnErr('/admin/logs', 'admin_logs');
 
-               $logfile                =       ((x($_POST,'logfile'))          ? notags(trim($_POST['logfile']))       : '');
-               $debugging              =       ((x($_POST,'debugging'))        ? true                                                          : false);
-               $loglevel               =       ((x($_POST,'loglevel'))         ? intval(trim($_POST['loglevel']))      : 0);
+               $logfile        =       ((x($_POST,'logfile'))          ? notags(trim($_POST['logfile']))       : '');
+               $debugging      =       ((x($_POST,'debugging'))        ? true                                  : false);
+               $loglevel       =       ((x($_POST,'loglevel'))         ? intval(trim($_POST['loglevel']))      : 0);
 
                set_config('system','logfile', $logfile);
                set_config('system','debugging',  $debugging);
                set_config('system','loglevel', $loglevel);
-
-
        }
 
        info( t("Log settings updated.") );
@@ -1546,12 +1760,24 @@ function admin_page_logs_post(&$a) {
 }
 
 /**
+ * @brief Generates admin panel subpage for configuration of the logs
+ *
+ * This function take the view/templates/admin_logs.tpl file and generates a
+ * page where admin can configure the logging of friendica.
+ *
+ * Displaying the log is separated from the log config as the logfile can get
+ * big depending on the settings and changing settings regarding the logs can
+ * thus waste bandwidth.
+ *
+ * The string returned contains the content of the template file with replaced
+ * macros.
+ *
  * @param App $a
  * @return string
  */
 function admin_page_logs(&$a){
 
-       $log_choices = Array(
+       $log_choices = array(
                LOGGER_NORMAL => 'Normal',
                LOGGER_TRACE => 'Trace',
                LOGGER_DEBUG => 'Debug',
@@ -1561,13 +1787,51 @@ function admin_page_logs(&$a){
 
        $t = get_markup_template("admin_logs.tpl");
 
-       $f = get_config('system','logfile');
+       return replace_macros($t, array(
+               '$title' => t('Administration'),
+               '$page' => t('Logs'),
+               '$submit' => t('Save Settings'),
+               '$clear' => t('Clear'),
+               '$baseurl' => $a->get_baseurl(true),
+               '$logname' =>  get_config('system','logfile'),
+
+               // name, label, value, help string, extra data...
+               '$debugging' => array('debugging', t("Enable Debugging"),get_config('system','debugging'), ""),
+               '$logfile' => array('logfile', t("Log file"), get_config('system','logfile'), t("Must be writable by web server. Relative to your Friendica top-level directory.")),
+               '$loglevel' => array('loglevel', t("Log level"), get_config('system','loglevel'), "", $log_choices),
+
+               '$form_security_token' => get_form_security_token("admin_logs"),
+               '$phpheader' => t("PHP logging"),
+               '$phphint' => t("To enable logging of PHP errors and warnings you can add the following to the .htconfig.php file of your installation. The filename set in the 'error_log' line is relative to the friendica top-level directory and must be writeable by the web server. The option '1' for 'log_errors' and 'display_errors' is to enable these options, set to '0' to disable them."),
+               '$phplogcode' => "error_reporting(E_ERROR | E_WARNING | E_PARSE );\nini_set('error_log','php.out');\nini_set('log_errors','1');\nini_set('display_errors', '1');",
+       ));
+}
 
+/**
+ * @brief Generates admin panel subpage to view the Friendica log
+ *
+ * This function loads the template view/templates/admin_viewlogs.tpl to
+ * display the systemlog content. The filename for the systemlog of friendica
+ * is relative to the base directory and taken from the config entry 'logfile'
+ * in the 'system' category.
+ *
+ * Displaying the log is separated from the log config as the logfile can get
+ * big depending on the settings and changing settings regarding the logs can
+ * thus waste bandwidth.
+ *
+ * The string returned contains the content of the template file with replaced
+ * macros.
+ *
+ * @param App $a
+ * @return string
+ */
+function admin_page_viewlogs(&$a){
+       $t = get_markup_template("admin_viewlogs.tpl");
+       $f = get_config('system','logfile');
        $data = '';
 
        if(!file_exists($f)) {
-               $data = t("Error trying to open <strong>$f</strong> log file.\r\n<br/>Check to see if file $f exist and is
-readable.");
+               $data = t("Error trying to open <strong>$f</strong> log file.\r\n<br/>Check to see if file $f exist and is readable.");
        }
        else {
                $fp = fopen($f, 'r');
@@ -1577,8 +1841,7 @@ readable.");
                else {
                        $fstat = fstat($fp);
                        $size = $fstat['size'];
-                       if($size != 0)
-                       {
+                       if($size != 0) {
                                if($size > 5000000 || $size < 0)
                                        $size = 5000000;
                                $seek = fseek($fp,0-$size,SEEK_END);
@@ -1591,80 +1854,94 @@ readable.");
                        fclose($fp);
                }
        }
-
        return replace_macros($t, array(
                '$title' => t('Administration'),
-               '$page' => t('Logs'),
-               '$submit' => t('Save Settings'),
-               '$clear' => t('Clear'),
+               '$page' => t('View Logs'),
                '$data' => $data,
-               '$baseurl' => $a->get_baseurl(true),
-               '$logname' =>  get_config('system','logfile'),
-
-                                                                       // name, label, value, help string, extra data...
-               '$debugging'            => array('debugging', t("Enable Debugging"),get_config('system','debugging'), ""),
-               '$logfile'                      => array('logfile', t("Log file"), get_config('system','logfile'), t("Must be writable by web server. Relative to your Friendica top-level directory.")),
-               '$loglevel'             => array('loglevel', t("Log level"), get_config('system','loglevel'), "", $log_choices),
-
-               '$form_security_token' => get_form_security_token("admin_logs"),
+               '$logname' =>  get_config('system','logfile')
        ));
 }
 
 /**
+ * @brief Prosesses data send by the features admin page
+ * 
  * @param App $a
  */
-function admin_page_remoteupdate_post(&$a) {
-       // this function should be called via ajax post
-       if(!is_site_admin()) {
-               return;
-       }
+function admin_page_features_post(&$a) {
 
+       check_form_security_token_redirectOnErr('/admin/features', 'admin_manage_features');
 
-       if (x($_POST,'remotefile') && $_POST['remotefile']!=""){
-               $remotefile = $_POST['remotefile'];
-               $ftpdata = (x($_POST['ftphost'])?$_POST:false);
-               doUpdate($remotefile, $ftpdata);
-       } else {
-               echo "No remote file to download. Abort!";
+       logger('postvars: ' . print_r($_POST,true),LOGGER_DATA);
+
+       $arr = array();
+       $features = get_features(false);
+
+       foreach($features as $fname => $fdata) {
+               foreach(array_slice($fdata,1) as $f) {
+                       $feature = $f[0];
+                       $feature_state = 'feature_' . $feature;
+                       $featurelock = 'featurelock_' . $feature;
+
+                       if(x($_POST[$feature_state]))
+                               $val = intval($_POST['feature_' . $feature]);
+                       else
+                               $val = 0;
+                       set_config('feature',$feature,$val);
+
+                       if(x($_POST[$featurelock]))
+                               set_config('feature_lock',$feature,$val);
+                       else
+                               del_config('feature_lock',$feature);
+               }
        }
 
-       killme();
+       goaway($a->get_baseurl(true) . '/admin/features' );
+       return; // NOTREACHED
 }
 
 /**
+ * @brief Subpage for global additional feature management
+ * 
+ * This functin generates the subpage 'Manage Additional Features'
+ * for the admin panel. At this page the admin can set preferences
+ * for the user settings of the 'additional features'. If needed this 
+ * preferences can be locked through the admin.
+ * 
+ * The returned string contains the HTML code of the subpage 'Manage
+ * Additional Features'
+ * 
  * @param App $a
  * @return string
  */
-function admin_page_remoteupdate(&$a) {
-       if(!is_site_admin()) {
-               return login(false);
-       }
-
-       $canwrite = canWeWrite();
-       $canftp = function_exists('ftp_connect');
+function admin_page_features(&$a) {
+       
+       if((argc() > 1) && (argv(1) === 'features')) {
+               $arr = array();
+               $features = get_features(false);
+
+               foreach($features as $fname => $fdata) {
+                       $arr[$fname] = array();
+                       $arr[$fname][0] = $fdata[0];
+                       foreach(array_slice($fdata,1) as $f) {
+
+                               $set = get_config('feature',$f[0]);
+                               if($set === false)
+                                       $set = $f[3];
+                               $arr[$fname][1][] = array(
+                                       array('feature_' .$f[0],$f[1],$set,$f[2],array(t('Off'),t('On'))),
+                                       array('featurelock_' .$f[0],sprintf( t('Lock feature %s'),$f[1]),(($f[4] !== false) ? "1" : ''),'',array(t('Off'),t('On')))
+                               );
+                       }
+               }
+               
+               $tpl = get_markup_template("admin_settings_features.tpl");
+               $o .= replace_macros($tpl, array(
+                       '$form_security_token' => get_form_security_token("admin_manage_features"),
+                       '$title' => t('Manage Additional Features'),
+                       '$features' => $arr,
+                       '$submit' => t('Save Settings'),
+               ));
 
-       $needupdate = true;
-       $u = checkUpdate();
-       if (!is_array($u)){
-               $needupdate = false;
-               $u = array('','','');
+               return $o;
        }
-
-       $tpl = get_markup_template("admin_remoteupdate.tpl");
-       return replace_macros($tpl, array(
-               '$baseurl' => $a->get_baseurl(true),
-               '$submit' => t("Update now"),
-               '$close' => t("Close"),
-               '$localversion' => FRIENDICA_VERSION,
-               '$remoteversion' => $u[1],
-               '$needupdate' => $needupdate,
-               '$canwrite' => $canwrite,
-               '$canftp'       => $canftp,
-               '$ftphost'      => array('ftphost', t("FTP Host"), '',''),
-               '$ftppath'      => array('ftppath', t("FTP Path"), '/',''),
-               '$ftpuser'      => array('ftpuser', t("FTP User"), '',''),
-               '$ftppwd'       => array('ftppwd', t("FTP Password"), '',''),
-               '$remotefile'=>array('remotefile','', $u['2'],''),
-       ));
-
 }
index d6f87762bdefefda35ecdb15ec387c7711a439a9..b6d72a35555a64f375b1402fc3188c93357c3579 100644 (file)
@@ -14,6 +14,10 @@ function community_content(&$a, $update = 0) {
 
        $o = '';
 
+       // Currently the community page isn't able to handle update requests
+       if ($update)
+               return;
+
        if((get_config('system','block_public')) && (! local_user()) && (! remote_user())) {
                notice( t('Public access denied.') . EOL);
                return;
@@ -143,14 +147,13 @@ function community_getpublicitems($start, $itemspage) {
        $r = q("SELECT `item`.`uri`, `item`.*, `item`.`id` AS `item_id`,
                        `author-name` AS `name`, `owner-avatar` AS `photo`,
                        `owner-link` AS `url`, `owner-avatar` AS `thumb`
-               FROM `item` WHERE `item`.`uid` = 0 AND `item`.`id` = `item`.`parent`
-               AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
-               AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
-               ORDER BY `item`.`received` DESC LIMIT %d, %d",
+               FROM `thread`
+               INNER JOIN `item` ON `item`.`id` = `thread`.`iid`
+               WHERE `thread`.`uid` = 0
+               ORDER BY `thread`.`created` DESC LIMIT %d, %d",
                intval($start),
                intval($itemspage)
        );
 
        return($r);
-
 }
index 0a8aad9d722137493003a0b3cded94f5b450a473..0b421433e0bdd1d52ef32d212f94786c270d1393 100644 (file)
@@ -125,7 +125,7 @@ function contacts_batch_actions(&$a){
                }
        }
        if ($count_actions>0) {
-               info ( sprintf( tt("%d contact edited.", "%d contacts edited", $count_actions), $count_actions) );
+               info ( sprintf( tt("%d contact edited.", "%d contacts edited.", $count_actions), $count_actions) );
        }
 
        if(x($_SESSION,'return_url'))
@@ -293,24 +293,11 @@ function _contact_update_profile($contact_id) {
                intval(local_user())
        );
 
-       $photos = import_profile_photo($data['photo'], local_user(), $contact_id);
-
-       $r = q("UPDATE `contact` SET `photo` = '%s',
-                       `thumb` = '%s',
-                       `micro` = '%s',
-                       `name-date` = '%s',
-                       `uri-date` = '%s',
-                       `avatar-date` = '%s'
-                       WHERE `id` = %d",
-                       dbesc($photos[0]),
-                       dbesc($photos[1]),
-                       dbesc($photos[2]),
-                       dbesc(datetime_convert()),
-                       dbesc(datetime_convert()),
-                       dbesc(datetime_convert()),
-                       intval($contact_id)
-               );
+       // Update the entry in the contact table
+       update_contact_avatar($data['photo'], local_user(), $contact_id);
 
+       // Update the entry in the gcontact table
+       update_gcontact_from_probe($data["url"]);
 }
 
 function _contact_block($contact_id, $orig_record) {
@@ -345,7 +332,6 @@ function _contact_archive($contact_id, $orig_record) {
        return $r;
 }
 function _contact_drop($contact_id, $orig_record) {
-       require_once('include/Contact.php');
        $a = get_app();
 
        terminate_friendship($a->user,$a->contact,$orig_record);
@@ -890,50 +876,27 @@ function contacts_tab($a, $contact_id, $active_tab) {
 
 function contact_posts($a, $contact_id) {
 
-       require_once('include/conversation.php');
-
-       $r = q("SELECT * FROM `contact` WHERE `id` = %d", intval($contact_id));
+       $r = q("SELECT `url` FROM `contact` WHERE `id` = %d", intval($contact_id));
        if ($r) {
                $contact = $r[0];
                $a->page['aside'] = "";
                profile_load($a, "", 0, get_contact_details_by_url($contact["url"]));
-       }
-
-       if(get_config('system', 'old_pager')) {
-               $r = q("SELECT COUNT(*) AS `total` FROM `item`
-                       WHERE `item`.`uid` = %d AND `author-link` IN ('%s', '%s')",
-                       intval(local_user()),
-                       dbesc(str_replace("https://", "http://", $contact["url"])),
-                       dbesc(str_replace("http://", "https://", $contact["url"])));
-
-               $a->set_pager_total($r[0]['total']);
-       }
-
-       $r = q("SELECT `item`.`uri`, `item`.*, `item`.`id` AS `item_id`,
-                       `author-name` AS `name`, `owner-avatar` AS `photo`,
-                       `owner-link` AS `url`, `owner-avatar` AS `thumb`
-               FROM `item` FORCE INDEX (uid_contactid_created)
-               WHERE `item`.`uid` = %d AND `contact-id` = %d
-                       AND `author-link` IN ('%s', '%s')
-               ORDER BY `item`.`created` DESC LIMIT %d, %d",
-               intval(local_user()),
-               intval($contact_id),
-               dbesc(str_replace("https://", "http://", $contact["url"])),
-               dbesc(str_replace("http://", "https://", $contact["url"])),
-               intval($a->pager['start']),
-               intval($a->pager['itemspage'])
-       );
+       } else
+               $profile = "";
 
        $tab_str = contacts_tab($a, $contact_id, 1);
 
        $o .= $tab_str;
 
-       $o .= conversation($a,$r,'community',false);
+       $r = q("SELECT `id` FROM `item` WHERE `contact-id` = %d LIMIT 1", intval($contact_id));
+       if ($r)
+               $o .= posts_from_contact($a, $contact_id);
+       elseif ($contact["url"]) {
+               $r = q("SELECT `id` FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1",
+                       dbesc(normalise_link($contact["url"])));
 
-       if(!get_config('system', 'old_pager')) {
-               $o .= alt_pager($a,count($r));
-       } else {
-               $o .= paginate($a);
+               if ($r[0]["id"] <> 0)
+                       $o .= posts_from_gcontact($a, $r[0]["id"]);
        }
 
        return $o;
index 6f951bdf6eade2dcd2321e63f250455892fe152c..5b4db09dac1f16ab7a0500ccf6b8e90e9cd90dc5 100644 (file)
@@ -80,24 +80,7 @@ function crepair_post(&$a) {
                logger('mod-crepair: updating photo from ' . $photo);
                require_once("include/Photo.php");
 
-               $photos = import_profile_photo($photo,local_user(),$contact['id']);
-
-               $x = q("UPDATE `contact` SET `photo` = '%s',
-                       `thumb` = '%s',
-                       `micro` = '%s',
-                       `name-date` = '%s',
-                       `uri-date` = '%s',
-                       `avatar-date` = '%s'
-                       WHERE `id` = %d
-                       ",
-                       dbesc($photos[0]),
-                       dbesc($photos[1]),
-                       dbesc($photos[2]),
-                       dbesc(datetime_convert()),
-                       dbesc(datetime_convert()),
-                       dbesc(datetime_convert()),
-                       intval($contact['id'])
-               );
+               update_contact_avatar($photo,local_user(),$contact['id']);
        }
 
        if($r)
index d5dbab951eabc0fc4065339a1710485780b2f802..27c04a908d297bb738e1c9ba9382d00ad01c2488 100644 (file)
@@ -315,7 +315,7 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
 
                require_once('include/Photo.php');
 
-               $photos = import_profile_photo($contact['photo'],$uid,$contact_id);
+               update_contact_avatar($contact['photo'],$uid,$contact_id);
 
                logger('dfrn_confirm: confirm - imported photos');
 
@@ -328,27 +328,18 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
                        if(($relation == CONTACT_IS_SHARING) && ($duplex))
                                $duplex = 0;
 
-                       $r = q("UPDATE `contact` SET
-                               `photo` = '%s',
-                               `thumb` = '%s',
-                               `micro` = '%s',
-                               `rel` = %d,
+                       $r = q("UPDATE `contact` SET `rel` = %d,
                                `name-date` = '%s',
                                `uri-date` = '%s',
-                               `avatar-date` = '%s',
                                `blocked` = 0,
                                `pending` = 0,
                                `duplex` = %d,
                                `hidden` = %d,
                                `network` = '%s' WHERE `id` = %d
                        ",
-                               dbesc($photos[0]),
-                               dbesc($photos[1]),
-                               dbesc($photos[2]),
                                intval($new_relation),
                                dbesc(datetime_convert()),
                                dbesc(datetime_convert()),
-                               dbesc(datetime_convert()),
                                intval($duplex),
                                intval($hidden),
                                dbesc(NETWORK_DFRN),
@@ -394,12 +385,8 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
                        );
 
 
-                       $r = q("UPDATE `contact` SET `photo` = '%s',
-                               `thumb` = '%s',
-                               `micro` = '%s',
-                               `name-date` = '%s',
+                       $r = q("UPDATE `contact` SET `name-date` = '%s',
                                `uri-date` = '%s',
-                               `avatar-date` = '%s',
                                `notify` = '%s',
                                `poll` = '%s',
                                `blocked` = 0,
@@ -410,10 +397,6 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
                                `rel` = %d
                                WHERE `id` = %d
                        ",
-                               dbesc($photos[0]),
-                               dbesc($photos[1]),
-                               dbesc($photos[2]),
-                               dbesc(datetime_convert()),
                                dbesc(datetime_convert()),
                                dbesc(datetime_convert()),
                                dbesc($notify),
@@ -683,7 +666,7 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
 
                require_once("include/Photo.php");
 
-               $photos = import_profile_photo($photo,$local_uid,$dfrn_record);
+               update_contact_avatar($photo,$local_uid,$dfrn_record);
 
                logger('dfrn_confirm: request - photos imported');
 
@@ -695,13 +678,9 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
                        $duplex = 0;
 
                $r = q("UPDATE `contact` SET
-                       `photo` = '%s',
-                       `thumb` = '%s',
-                       `micro` = '%s',
                        `rel` = %d,
                        `name-date` = '%s',
                        `uri-date` = '%s',
-                       `avatar-date` = '%s',
                        `blocked` = 0,
                        `pending` = 0,
                        `duplex` = %d,
@@ -709,13 +688,9 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
                        `prv` = %d,
                        `network` = '%s' WHERE `id` = %d
                ",
-                       dbesc($photos[0]),
-                       dbesc($photos[1]),
-                       dbesc($photos[2]),
                        intval($new_relation),
                        dbesc(datetime_convert()),
                        dbesc(datetime_convert()),
-                       dbesc(datetime_convert()),
                        intval($duplex),
                        intval($forum),
                        intval($prv),
index fcc5726548fea9ae944c204fbbd7362ff24c8034..780fb456f5cd33f43e7bc6783196fd15e55570f1 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
-require_once('library/simplepie/simplepie.inc');
 require_once('include/items.php');
+require_once('include/dfrn.php');
 require_once('include/event.php');
 
 require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
@@ -209,7 +209,7 @@ function dfrn_notify_post(&$a) {
                logger('rino: decrypted data: ' . $data, LOGGER_DATA);
        }
 
-       $ret = local_delivery($importer,$data);
+       $ret = dfrn::import($data, $importer);
        xml_status($ret);
 
        // NOTREACHED
@@ -311,7 +311,7 @@ function dfrn_notify_content(&$a) {
                $rino = intval($rino);
                // use RINO1 if mcrypt isn't installed and RINO2 was selected
                if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1;
-               
+
                logger("Local rino version: ". $rino, LOGGER_DEBUG);
 
                // if requested rino is lower than enabled local rino, lower local rino version
index d6a07186aca299e007e7a14ba9d436b42fa1affd..ab6637607e564b2d71eb2ba1353b6944a6216c65 100644 (file)
@@ -1,9 +1,7 @@
 <?php
-
-
-
 require_once('include/items.php');
 require_once('include/auth.php');
+require_once('include/dfrn.php');
 
 
 function dfrn_poll_init(&$a) {
@@ -46,7 +44,7 @@ function dfrn_poll_init(&$a) {
 
                logger('dfrn_poll: public feed request from ' . $_SERVER['REMOTE_ADDR'] . ' for ' . $user);
                header("Content-type: application/atom+xml");
-               echo get_feed_for($a, '', $user,$last_update);
+               echo dfrn::feed('', $user,$last_update);
                killme();
        }
 
@@ -373,7 +371,7 @@ function dfrn_poll_post(&$a) {
                }
 
                header("Content-type: application/atom+xml");
-               $o = get_feed_for($a,$dfrn_id, $a->argv[1], $last_update, $direction);
+               $o = dfrn::feed($dfrn_id, $a->argv[1], $last_update, $direction);
                echo $o;
                killme();
 
index 99b933625e541b4b861da8fe119845653e98ff33..2741ad59b4f2c1f3cc66eb4a064792df5120dd5c 100644 (file)
@@ -10,6 +10,7 @@
  */
 
 require_once('include/enotify.php');
+require_once('include/Scrape.php');
 
 if(! function_exists('dfrn_request_init')) {
 function dfrn_request_init(&$a) {
@@ -112,8 +113,6 @@ function dfrn_request_post(&$a) {
                                         * Scrape the other site's profile page to pick up the dfrn links, key, fn, and photo
                                         */
 
-                                       require_once('include/Scrape.php');
-
                                        $parms = scrape_dfrn($dfrn_url);
 
                                        if(! count($parms)) {
index 3f9c82576a3bf02845082e266ba1dfb0e66095cf..0dfe4d67a9b61960623d963f0f58718ccbabe2be 100644 (file)
@@ -94,9 +94,15 @@ function dirfind_content(&$a, $prefix = "") {
                        else
                                $ostatus = NETWORK_DFRN;
 
-                       $count = q("SELECT count(*) AS `total` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND
-                                       (`url` REGEXP '%s' OR `name` REGEXP '%s' OR `location` REGEXP '%s' OR
-                                               `about` REGEXP '%s' OR `keywords` REGEXP '%s')".$extra_sql,
+                       $count = q("SELECT count(*) AS `total` FROM `gcontact`
+                                       LEFT JOIN `contact` ON `contact`.`nurl` = `gcontact`.`nurl`
+                                               AND `contact`.`uid` = %d AND NOT `contact`.`blocked`
+                                               AND NOT `contact`.`pending` AND `contact`.`rel` IN ('%s', '%s')
+                                       WHERE (`contact`.`id` > 0 OR (NOT `gcontact`.`hide` AND `gcontact`.`network` IN ('%s', '%s', '%s') AND
+                                       ((`gcontact`.`last_contact` >= `gcontact`.`last_failure`) OR (`gcontact`.`updated` >= `gcontact`.`last_failure`)))) AND
+                                       (`gcontact`.`url` REGEXP '%s' OR `gcontact`.`name` REGEXP '%s' OR `gcontact`.`location` REGEXP '%s' OR
+                                               `gcontact`.`about` REGEXP '%s' OR `gcontact`.`keywords` REGEXP '%s') $extra_sql",
+                                       intval(local_user()), dbesc(CONTACT_IS_SHARING), dbesc(CONTACT_IS_FRIEND),
                                        dbesc(NETWORK_DFRN), dbesc($ostatus), dbesc($diaspora),
                                        dbesc(escape_tags($search)), dbesc(escape_tags($search)), dbesc(escape_tags($search)),
                                        dbesc(escape_tags($search)), dbesc(escape_tags($search)));
@@ -106,8 +112,8 @@ function dirfind_content(&$a, $prefix = "") {
                                        LEFT JOIN `contact` ON `contact`.`nurl` = `gcontact`.`nurl`
                                                AND `contact`.`uid` = %d AND NOT `contact`.`blocked`
                                                AND NOT `contact`.`pending` AND `contact`.`rel` IN ('%s', '%s')
-                                       WHERE `gcontact`.`network` IN ('%s', '%s', '%s') AND
-                                       ((`gcontact`.`last_contact` >= `gcontact`.`last_failure`) OR (`gcontact`.`updated` >= `gcontact`.`last_failure`)) AND
+                                       WHERE (`contact`.`id` > 0 OR (NOT `gcontact`.`hide` AND `gcontact`.`network` IN ('%s', '%s', '%s') AND
+                                       ((`gcontact`.`last_contact` >= `gcontact`.`last_failure`) OR (`gcontact`.`updated` >= `gcontact`.`last_failure`)))) AND
                                        (`gcontact`.`url` REGEXP '%s' OR `gcontact`.`name` REGEXP '%s' OR `gcontact`.`location` REGEXP '%s' OR
                                                `gcontact`.`about` REGEXP '%s' OR `gcontact`.`keywords` REGEXP '%s') $extra_sql
                                                GROUP BY `gcontact`.`nurl`
index 6d1f417e710ad8b861ebd9f99dda8f9633b4d92c..4e33927072bdf8123e17abc7a485a22dd768666a 100644 (file)
@@ -154,42 +154,60 @@ function display_fetchauthor($a, $item) {
                $profiledata["about"] = "";
        }
 
-       // Fetching further contact data from the contact table
-       $r = q("SELECT `uid`, `network`, `photo`, `nick`, `location`, `about` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `network` = '%s'",
-               dbesc(normalise_link($profiledata["url"])), intval($item["uid"]), dbesc($item["network"]));
-
-       if (!count($r))
-               $r = q("SELECT `uid`, `network`, `photo`, `nick`, `location`, `about` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d",
-                       dbesc(normalise_link($profiledata["url"])), intval($item["uid"]));
+       // Don't show details from Diaspora contacts if you don't follow the contact
+       $showdetails = ($profiledata["network"] != NETWORK_DIASPORA);
 
+       // Fetching further contact data from the contact table
+       $r = q("SELECT `uid`, `network`, `name`, `photo`, `nick`, `addr`, `location`, `about`, `gender`, `keywords`
+               FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `network` = '%s' AND `rel` IN (%d, %d)",
+               dbesc(normalise_link($profiledata["url"])), intval(local_user()), dbesc($item["network"]),
+               intval(CONTACT_IS_SHARING), intval(CONTACT_IS_FRIEND));
        if (!count($r))
-               $r = q("SELECT `uid`, `network`, `photo`, `nick`, `location`, `about` FROM `contact` WHERE `nurl` = '%s' AND `uid` = 0",
-                       dbesc(normalise_link($profiledata["url"])));
+               $r = q("SELECT `uid`, `network`, `name`, `photo`, `nick`, `addr`, `location`, `about`, `gender`, `keywords`
+                       FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `rel` IN (%d, %d)",
+                       dbesc(normalise_link($profiledata["url"])), intval(local_user()),
+                       intval(CONTACT_IS_SHARING), intval(CONTACT_IS_FRIEND));
 
        if (count($r)) {
-               if ((($r[0]["uid"] != local_user()) OR !local_user()) AND ($profiledata["network"] == NETWORK_DIASPORA)) {
-                       $r[0]["location"] = "";
-                       $r[0]["about"] = "";
-               }
-
+               $profiledata["name"] = $r[0]["name"];
                $profiledata["photo"] = $r[0]["photo"];
-               $profiledata["address"] = $r[0]["location"];
-               $profiledata["about"] = $r[0]["about"];
-               if ($r[0]["nick"] != "")
-                       $profiledata["nickname"] = $r[0]["nick"];
-       }
+               $profiledata["nickname"] = $r[0]["nick"];
+               $profiledata["addr"] = $r[0]["addr"];
+               $profiledata["keywords"] = $r[0]["keywords"];
+               $profiledata["network"] = $r[0]["network"];
 
-       // Fetching profile data from unique contacts
-       $r = q("SELECT `avatar`, `nick`, `location`, `about` FROM `unique_contacts` WHERE `url` = '%s'", dbesc(normalise_link($profiledata["url"])));
-       if (count($r)) {
-               if ($profiledata["photo"] == "")
-                       $profiledata["photo"] = $r[0]["avatar"];
-               if (($profiledata["address"] == "") AND ($profiledata["network"] != NETWORK_DIASPORA))
+               if (local_user() OR $showdetails) {
+                       $showdetails = true;
                        $profiledata["address"] = $r[0]["location"];
-               if (($profiledata["about"] == "") AND ($profiledata["network"] != NETWORK_DIASPORA))
                        $profiledata["about"] = $r[0]["about"];
-               if (($profiledata["nickname"] == "") AND ($r[0]["nick"] != ""))
+                       $profiledata["gender"] = $r[0]["gender"];
+               }
+       }
+
+       // Fetching profile data from global contacts
+       if ($profiledata["network"] != NETWORK_FEED) {
+               $r = q("SELECT `name`, `photo`, `nick`, `addr`, `location`, `about`, `gender`, `keywords`, `network` FROM `gcontact` WHERE `nurl` = '%s'", dbesc(normalise_link($profiledata["url"])));
+               if (count($r)) {
+                       $profiledata["name"] = $r[0]["name"];
+                       $profiledata["photo"] = $r[0]["photo"];
                        $profiledata["nickname"] = $r[0]["nick"];
+                       $profiledata["addr"] = $r[0]["addr"];
+                       $profiledata["network"] = $r[0]["network"];
+
+                       if ($r[0]["keywords"])
+                               $profiledata["keywords"] = $r[0]["keywords"];
+
+                       if ($showdetails) {
+                               if ($r[0]["location"])
+                                       $profiledata["address"] = $r[0]["location"];
+
+                               if ($r[0]["about"])
+                                       $profiledata["about"] = $r[0]["about"];
+
+                               if ($r[0]["gender"])
+                                       $profiledata["gender"] = $r[0]["gender"];
+                       }
+               }
        }
 
        if (local_user()) {
@@ -329,6 +347,10 @@ function display_content(&$a, $update = 0) {
                return;
        }
 
+       // Why do we need this on the display page? We don't have the possibility to write new content here.
+       // Ad editing of posts work without this as well.
+       // We should remove this completely for the 3.5.1 release.
+       /*
        if ($is_owner) {
                $x = array(
                        'is_owner' => true,
@@ -344,6 +366,7 @@ function display_content(&$a, $update = 0) {
                );
                $o .= status_editor($a,$x,0,true);
        }
+       */
 
        $sql_extra = item_permissions_sql($a->profile['uid'],$remote_contact,$groups);
 
index cc08831285e74fb69ed6aef54deafce7c5f67543..b92a0d980fb57e1150214c07aee7e5b590277c05 100644 (file)
@@ -2,6 +2,7 @@
 
 require_once('include/Scrape.php');
 require_once('include/follow.php');
+require_once('include/Contact.php');
 require_once('include/contact_selectors.php');
 
 function follow_content(&$a) {
@@ -75,15 +76,18 @@ function follow_content(&$a) {
        }
 
        $myaddr = $r[0]["url"];
+       $gcontact_id = 0;
 
        // Makes the connection request for friendica contacts easier
        $_SESSION["fastlane"] = $ret["url"];
 
-       $r = q("SELECT `location`, `about`, `keywords` FROM `gcontact` WHERE `nurl` = '%s'",
+       $r = q("SELECT `id`, `location`, `about`, `keywords` FROM `gcontact` WHERE `nurl` = '%s'",
                normalise_link($ret["url"]));
 
        if (!$r)
                $r = array(array("location" => "", "about" => "", "keywords" => ""));
+       else
+               $gcontact_id = $r[0]["id"];
 
        if($ret['network'] === NETWORK_DIASPORA) {
                $r[0]["location"] = "";
@@ -95,11 +99,12 @@ function follow_content(&$a) {
        if ($ret["addr"] != "")
                $header .= " <".$ret["addr"].">";
 
-       $header .= " (".network_to_name($ret['network'], $ret['url']).")";
+       //$header .= " (".network_to_name($ret['network'], $ret['url']).")";
+       $header = t("Connect/Follow");
 
        $o  = replace_macros($tpl,array(
                        '$header' => htmlentities($header),
-                       '$photo' => proxy_url($ret["photo"], false, PROXY_SIZE_SMALL),
+                       //'$photo' => proxy_url($ret["photo"], false, PROXY_SIZE_SMALL),
                        '$desc' => "",
                        '$pls_answer' => t('Please answer the following:'),
                        '$does_know_you' => array('knowyou', sprintf(t('Does %s know you?'),$ret["name"]), false, '', array(t('No'),t('Yes'))),
@@ -121,13 +126,26 @@ function follow_content(&$a) {
                        '$url_label' => t("Profile URL"),
                        '$myaddr' => $myaddr,
                        '$request' => $request,
-                       '$location' => bbcode($r[0]["location"]),
+                       /*'$location' => bbcode($r[0]["location"]),
                        '$location_label' => t("Location:"),
                        '$about' => bbcode($r[0]["about"], false, false),
-                       '$about_label' => t("About:"),
+                       '$about_label' => t("About:"), */
                        '$keywords' => $r[0]["keywords"],
                        '$keywords_label' => t("Tags:")
        ));
+
+       $a->page['aside'] = "";
+       profile_load($a, "", 0, get_contact_details_by_url($ret["url"]));
+
+       // Show last public posts
+       if ($gcontact_id <> 0) {
+               $o .= replace_macros(get_markup_template('section_title.tpl'),
+                                               array('$title' => t('Status Messages and Posts')
+               ));
+
+               $o .= posts_from_gcontact($a, $gcontact_id);
+       }
+
        return $o;
 }
 
index 9fa203f58d0baa47dfada5b283986a265c104da7..aad5964baf6e5d1ddf737e88f46b636cb255d6d7 100644 (file)
@@ -29,10 +29,21 @@ function friendica_init(&$a) {
                                        $visible_plugins[] = $rr['name'];
                }
 
+               load_config('feature_lock');
+               $locked_features = array();
+               if(is_array($a->config['feature_lock']) && count($a->config['feature_lock'])) {
+                       foreach($a->config['feature_lock'] as $k => $v) {
+                               if($k === 'config_loaded')
+                                       continue;
+                               $locked_features[$k] = intval($v);
+                       }
+               }
+
                $data = Array(
                        'version' => FRIENDICA_VERSION,
                        'url' => z_root(),
                        'plugins' => $visible_plugins,
+                       'locked_features' => $locked_features,
                        'register_policy' =>  $register_policy[$a->config['register_policy']],
                        'admin' => $admin,
                        'site_name' => $a->config['sitename'],
index 5465d3e900f9827438de3c60fbf492f920914c01..72225692793d5206d412d9e2a671f63ded4ab762 100644 (file)
@@ -62,7 +62,7 @@ function help_content(&$a) {
        if ($filename !== "Home") {
                // create TOC but not for home
                $lines = explode("\n", $html);
-               $toc="<style>aside ul {padding-left: 1em;}</style><h2>TOC</h2><ul id='toc'>";
+               $toc="<style>aside ul {padding-left: 1em;}aside h1{font-size:2em}</style><h2>TOC</h2><ul id='toc'>";
                $lastlevel=1;
                $idnum = array(0,0,0,0,0,0,0);
                foreach($lines as &$line){
@@ -84,7 +84,7 @@ function help_content(&$a) {
                                }
                        }
                }
-               for($k=1;$k<$lastlevel; $k++) $toc.="</ul>";
+               for($k=0;$k<$lastlevel; $k++) $toc.="</ul>";
                $html = implode("\n",$lines);
 
                $a->page['aside'] = $toc.$a->page['aside'];
index f25ea36b2c0c13ed9a08a24e968b8a3741bf4fcf..7e575a17e4f7e1d30c0949ddaf8ff9ae26f1fc3e 100644 (file)
@@ -137,14 +137,25 @@ function item_post(&$a) {
                                AND (normalise_link($parent_contact["url"]) != normalise_link($thrparent[0]["author-link"]))) {
                                $parent_contact = null;
 
-                               require_once("include/Scrape.php");
-                               $probed_contact = probe_url($thrparent[0]["author-link"]);
-                               if ($probed_contact["network"] != NETWORK_FEED) {
-                                       $parent_contact = $probed_contact;
-                                       $parent_contact["nurl"] = normalise_link($probed_contact["url"]);
-                                       $parent_contact["thumb"] = $probed_contact["photo"];
-                                       $parent_contact["micro"] = $probed_contact["photo"];
-                                       $parent_contact["addr"] = $probed_contact["addr"];
+                               $r = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1",
+                                       dbesc(normalise_link($thrparent[0]["author-link"])));
+                               if (count($r)) {
+                                       $parent_contact = $r[0];
+                                       $parent_contact["thumb"] = $parent_contact["photo"];
+                                       $parent_contact["micro"] = $parent_contact["photo"];
+                                       unset($parent_contact["id"]);
+                               }
+
+                               if (!isset($parent_contact["nick"])) {
+                                       require_once("include/Scrape.php");
+                                       $probed_contact = probe_url($thrparent[0]["author-link"]);
+                                       if ($probed_contact["network"] != NETWORK_FEED) {
+                                               $parent_contact = $probed_contact;
+                                               $parent_contact["nurl"] = normalise_link($probed_contact["url"]);
+                                               $parent_contact["thumb"] = $probed_contact["photo"];
+                                               $parent_contact["micro"] = $probed_contact["photo"];
+                                               $parent_contact["addr"] = $probed_contact["addr"];
+                                       }
                                }
                                logger('no contact found: '.print_r($thrparent, true), LOGGER_DEBUG);
                        } else
@@ -693,7 +704,8 @@ function item_post(&$a) {
        $datarray['postopts']      = $postopts;
        $datarray['origin']        = $origin;
        $datarray['moderated']     = $allow_moderated;
-
+       $datarray['gcontact-id']   = get_gcontact_id(array("url" => $datarray['author-link'], "network" => $datarray['network'],
+                                                       "photo" => $datarray['author-avatar'], "name" => $datarray['author-name']));
        /**
         * These fields are for the convenience of plugins...
         * 'self' if true indicates the owner is posting on their own wall
@@ -832,9 +844,6 @@ function item_post(&$a) {
                // NOTREACHED
        }
 
-       // Store the guid and other relevant data
-       add_guid($datarray);
-
        $post_id = $r[0]['id'];
        logger('mod_item: saved item ' . $post_id);
 
@@ -888,7 +897,7 @@ function item_post(&$a) {
 
 
                // Store the comment signature information in case we need to relay to Diaspora
-               store_diaspora_comment_sig($datarray, $author, ($self ? $a->user['prvkey'] : false), $parent_item, $post_id);
+               store_diaspora_comment_sig($datarray, $author, ($self ? $user['prvkey'] : false), $parent_item, $post_id);
 
        } else {
                $parent = $post_id;
@@ -1060,6 +1069,8 @@ function item_content(&$a) {
  * @return boolean true if replaced, false if not replaced
  */
 function handle_tag($a, &$body, &$inform, &$str_tags, $profile_uid, $tag, $network = "") {
+       require_once("include/Scrape.php");
+       require_once("include/socgraph.php");
 
        $replaced = false;
        $r = null;
@@ -1094,122 +1105,115 @@ function handle_tag($a, &$body, &$inform, &$str_tags, $profile_uid, $tag, $netwo
                $stat = false;
                //get the person's name
                $name = substr($tag,1);
-               //is it a link or a full dfrn address?
-               if((strpos($name,'@')) || (strpos($name,'http://'))) {
-                       $newname = $name;
-                       //get the profile links
-                       $links = @lrdd($name);
-                       if(count($links)) {
-                               //for all links, collect how is to inform and how's profile is to link
-                               foreach($links as $link) {
-                                       if($link['@attributes']['rel'] === 'http://webfinger.net/rel/profile-page')
-                                               $profile = $link['@attributes']['href'];
-                                       if($link['@attributes']['rel'] === 'salmon') {
-                                               if(strlen($inform))
-                                                       $inform .= ',';
-                                               $inform .= 'url:' . str_replace(',','%2c',$link['@attributes']['href']);
-                                       }
-                               }
-                       }
-               } elseif (($network != NETWORK_OSTATUS) AND ($network != NETWORK_TWITTER) AND
-                       ($network != NETWORK_STATUSNET) AND ($network != NETWORK_APPNET)) {
-                       //if it is a name rather than an address
-                       $newname = $name;
-                       $alias = '';
-                       $tagcid = 0;
-                       //is it some generated name?
-                       if(strrpos($newname,'+')) {
-                               //get the id
-                               $tagcid = intval(substr($newname,strrpos($newname,'+') + 1));
-                               //remove the next word from tag's name
-                               if(strpos($name,' ')) {
-                                       $name = substr($name,0,strpos($name,' '));
+
+               // Sometimes the tag detection doesn't seem to work right
+               // This is some workaround
+               $nameparts = explode(" ", $name);
+               $name = $nameparts[0];
+
+               // Try to detect the contact in various ways
+               if ((strpos($name,'@')) || (strpos($name,'http://'))) {
+                       // Is it in format @user@domain.tld or @http://domain.tld/...?
+
+                       // First check the contact table for the address
+                       $r = q("SELECT `id`, `url`, `nick`, `name`, `alias`, `network`, `notify` FROM `contact` WHERE `addr` = '%s' AND `uid` = %d LIMIT 1",
+                                       dbesc($name),
+                                       intval($profile_uid)
+                       );
+
+                       // Then check in the contact table for the url
+                       if (!$r)
+                               $r = q("SELECT `id`, `url`, `nick`, `name`, `alias`, `notify`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1",
+                                               dbesc(normalise_link($name)),
+                                               intval($profile_uid)
+                               );
+
+                       // Then check in the global contacts for the address
+                       if (!$r)
+                               $r = q("SELECT `url`, `name`, `nick`, `network`, `alias`, `notify` FROM `gcontact` WHERE `addr` = '%s' LIMIT 1", dbesc($name));
+
+                       // Then check in the global contacts for the url
+                       if (!$r)
+                               $r = q("SELECT `url`, `name`, `nick`, `network`, `alias`, `notify` FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1", dbesc(normalise_link($name)));
+
+                       // If the data isn't complete then refetch the data
+                       if ($r AND ($r[0]["network"] == NETWORK_OSTATUS) AND (($r[0]["notify"] == "") OR ($r[0]["alias"] == "")))
+                               $r = false;
+
+                       if (!$r) {
+                               $probed = probe_url($name);
+                               if (isset($probed["url"])) {
+                                       update_gcontact($probed);
+                                       $r = q("SELECT `url`, `name`, `nick`, `network`, `alias`, `notify` FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1",
+                                               dbesc(normalise_link($probed["url"])));
                                }
                        }
-                       if($tagcid) { //if there was an id
-                               //select contact with that id from the logged in user's contact list
-                               $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
+               } else {
+                       $r = false;
+                       if (strrpos($name,'+')) {
+                               // Is it in format @nick+number?
+                               $tagcid = intval(substr($name,strrpos($name,'+') + 1));
+
+                               $r = q("SELECT `id`, `url`, `nick`, `name`, `alias`, `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
                                                intval($tagcid),
                                                intval($profile_uid)
                                );
                        }
-                       else {
-                               $newname = str_replace('_',' ',$name);
-
-                               // At first try to fetch a contact according to the given network
-                               if ($network != "") {
-                                       //select someone from this user's contacts by name
-                                       $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
-                                                       dbesc($newname),
-                                                       dbesc($network),
-                                                       intval($profile_uid)
-                                       );
-                                       if(! $r) {
-                                               //select someone by attag or nick and the name passed in
-                                               $r = q("SELECT * FROM `contact` WHERE `attag` = '%s' OR `nick` = '%s' AND `network` = '%s' AND `uid` = %d ORDER BY `attag` DESC LIMIT 1",
-                                                               dbesc($name),
-                                                               dbesc($name),
-                                                               dbesc($network),
-                                                               intval($profile_uid)
-                                               );
-                                       }
-                               } else
-                                       $r = false;
-
-                               if(! $r) {
-                                       //select someone from this user's contacts by name
-                                       $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `uid` = %d LIMIT 1",
-                                                       dbesc($newname),
-                                                       intval($profile_uid)
-                                       );
-                               }
 
-                               if(! $r) {
-                                       //select someone by attag or nick and the name passed in
-                                       $r = q("SELECT * FROM `contact` WHERE `attag` = '%s' OR `nick` = '%s' AND `uid` = %d ORDER BY `attag` DESC LIMIT 1",
-                                                       dbesc($name),
-                                                       dbesc($name),
-                                                       intval($profile_uid)
-                                       );
-                               }
-                       }
-/*                     } elseif(strstr($name,'_') || strstr($name,' ')) { //no id
-                               //get the real name
-                               $newname = str_replace('_',' ',$name);
-                               //select someone from this user's contacts by name
-                               $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `uid` = %d LIMIT 1",
+                       //select someone by attag or nick and the name passed in the current network
+                       if(!$r AND ($network != ""))
+                               $r = q("SELECT `id`, `url`, `nick`, `name`, `alias`, `network` FROM `contact` WHERE `attag` = '%s' OR `nick` = '%s' AND `network` = '%s' AND `uid` = %d ORDER BY `attag` DESC LIMIT 1",
+                                               dbesc($name),
+                                               dbesc($name),
+                                               dbesc($network),
+                                               intval($profile_uid)
+                               );
+
+                       //select someone from this user's contacts by name in the current network
+                       if (!$r AND ($network != ""))
+                               $r = q("SELECT `id`, `url`, `nick`, `name`, `alias`, `network` FROM `contact` WHERE `name` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1",
                                                dbesc($newname),
+                                               dbesc($network),
                                                intval($profile_uid)
                                );
-                       } else {
-                               //select someone by attag or nick and the name passed in
-                               $r = q("SELECT * FROM `contact` WHERE `attag` = '%s' OR `nick` = '%s' AND `uid` = %d ORDER BY `attag` DESC LIMIT 1",
+
+                       //select someone by attag or nick and the name passed in
+                       if(!$r)
+                               $r = q("SELECT `id`, `url`, `nick`, `name`, `alias`, `network` FROM `contact` WHERE `attag` = '%s' OR `nick` = '%s' AND `uid` = %d ORDER BY `attag` DESC LIMIT 1",
                                                dbesc($name),
                                                dbesc($name),
                                                intval($profile_uid)
                                );
-                       }*/
-                       //$r is set, if someone could be selected
-                       if(count($r)) {
-                               $profile = $r[0]['url'];
-                               //set newname to nick, find alias
-                               if(($r[0]['network'] === NETWORK_OSTATUS) OR ($r[0]['network'] === NETWORK_TWITTER)
-                                       OR ($r[0]['network'] === NETWORK_STATUSNET) OR ($r[0]['network'] === NETWORK_APPNET)) {
-                                       $newname = $r[0]['nick'];
-                                       $stat = true;
-                                       if($r[0]['alias'])
-                                               $alias = $r[0]['alias'];
-                               }
-                               else
-                                       $newname = $r[0]['name'];
-                               //add person's id to $inform
-                               if(strlen($inform))
-                                       $inform .= ',';
-                               $inform .= 'cid:' . $r[0]['id'];
-                       }
+
+
+                       //select someone from this user's contacts by name
+                       if(!$r)
+                               $r = q("SELECT `id`, `url`, `nick`, `name`, `alias`, `network` FROM `contact` WHERE `name` = '%s' AND `uid` = %d LIMIT 1",
+                                               dbesc($newname),
+                                               intval($profile_uid)
+                               );
+               }
+
+               if ($r) {
+                       if(strlen($inform) AND (isset($r[0]["notify"]) OR isset($r[0]["id"])))
+                               $inform .= ',';
+
+                       if (isset($r[0]["id"]))
+                               $inform .= 'cid:' . $r[0]["id"];
+                       elseif (isset($r[0]["notify"]))
+                               $inform  .= $r[0]["notify"];
+
+                       $profile = $r[0]["url"];
+                       $alias   = $r[0]["alias"];
+                       $newname = $r[0]["nick"];
+                       if (($newname == "") OR (($r[0]["network"] != NETWORK_OSTATUS) AND ($r[0]["network"] != NETWORK_TWITTER)
+                               AND ($r[0]["network"] != NETWORK_STATUSNET) AND ($r[0]["network"] != NETWORK_APPNET)))
+                               $newname = $r[0]["name"];
                }
+
                //if there is an url for this persons profile
                if(isset($profile)) {
+
                        $replaced = true;
                        //create profile link
                        $profile = str_replace(',','%2c',$profile);
@@ -1264,7 +1268,7 @@ function store_diaspora_comment_sig($datarray, $author, $uprvkey, $parent_item,
        $signed_text = $datarray['guid'] . ';' . $parent_item['guid'] . ';' . $signed_body . ';' . $diaspora_handle;
 
        if( $uprvkey !== false )
-               $authorsig = base64_encode(rsa_sign($signed_text,$uprvkey,'sha256'));
+               $authorsig = rsa_sign($signed_text,$uprvkey,'sha256');
        else
                $authorsig = '';
 
index a85e498db2ce7ea037eb0c067e367ea3b0287055..0010a3d8246b9fb2d681c06e394df02638834d45 100644 (file)
@@ -114,7 +114,7 @@ function network_init(&$a) {
        require_once('include/group.php');
        require_once('include/contact_widgets.php');
        require_once('include/items.php');
-       require_once('include/forums.php');
+       require_once('include/ForumManager.php');
 
        if(! x($a->page,'aside'))
                $a->page['aside'] = '';
@@ -148,7 +148,7 @@ function network_init(&$a) {
        }
 
        $a->page['aside'] .= (feature_enabled(local_user(),'groups') ? group_side('network/0','network','standard',$group_id) : '');
-       $a->page['aside'] .= (feature_enabled(local_user(),'forumlist_widget') ? widget_forumlist(local_user(),$cid) : '');
+       $a->page['aside'] .= (feature_enabled(local_user(),'forumlist_widget') ? ForumManager::widget(local_user(),$cid) : '');
        $a->page['aside'] .= posted_date_widget($a->get_baseurl() . '/network',local_user(),false);
        $a->page['aside'] .= networks_widget($a->get_baseurl(true) . '/network',(x($_GET, 'nets') ? $_GET['nets'] : ''));
        $a->page['aside'] .= saved_searches($search);
@@ -312,6 +312,9 @@ function network_content(&$a, $update = 0) {
                return login(false);
        }
 
+       // Rawmode is used for fetching new content at the end of the page
+       $rawmode = (isset($_GET["mode"]) AND ($_GET["mode"] == "raw"));
+
        /// @TODO Is this really necessary? $a is already available to hooks
        $arr = array('query' => $a->query_string);
        call_hooks('network_content_init', $arr);
@@ -470,7 +473,7 @@ function network_content(&$a, $update = 0) {
        }
        set_pconfig(local_user(), 'network.view', 'net.selected', ($nets ? $nets : 'all'));
 
-       if(! $update) {
+       if(!$update AND !$rawmode) {
                if($group) {
                        if(($t = group_public_members($group)) && (! get_pconfig(local_user(),'system','nowarn_insecure'))) {
                                notice( sprintf( tt('Warning: This group contains %s member from an insecure network.',
@@ -549,27 +552,30 @@ function network_content(&$a, $update = 0) {
                }
 
                $contacts = expand_groups(array($group));
-
-               $contact_str_self = "";
+               $gcontacts = expand_groups(array($group), false, true);
 
                if((is_array($contacts)) && count($contacts)) {
+                       $contact_str_self = "";
+                       $gcontact_str_self = "";
+
                        $contact_str = implode(',',$contacts);
-                       $self = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `self`", intval($_SESSION['uid']));
-                       if (count($self))
-                               $contact_str_self = ",".$self[0]["id"];
-               }
-               else {
-                       $contact_str = ' 0 ';
+                       $gcontact_str = implode(',',$gcontacts);
+                       $self = q("SELECT `contact`.`id`, `gcontact`.`id` AS `gid` FROM `contact`
+                                       INNER JOIN `gcontact` ON `gcontact`.`nurl` = `contact`.`nurl`
+                                       WHERE `uid` = %d AND `self`", intval($_SESSION['uid']));
+                       if (count($self)) {
+                               $contact_str_self = $self[0]["id"];
+                               $gcontact_str_self = $self[0]["gid"];
+                       }
+
+                       $sql_post_table = " INNER JOIN `item` AS `temp1` ON `temp1`.`id` = ".$sql_table.".".$sql_parent;
+                       $sql_extra3 .= " AND ($sql_table.`contact-id` IN ($contact_str) ";
+                       $sql_extra3 .= " OR ($sql_table.`contact-id` = '$contact_str_self' AND `temp1`.`allow_gid` LIKE '".protect_sprintf('%<'.intval($group).'>%')."' AND `temp1`.`private`))";
+               } else {
+                       $sql_extra3 .= " AND false ";
                        info( t('Group is empty'));
                }
 
-               //$sql_post_table = " INNER JOIN (SELECT DISTINCT(`parent`) FROM `item` WHERE (`contact-id` IN ($contact_str) OR `allow_gid` like '".protect_sprintf('%<'.intval($group).'>%')."') and deleted = 0 ORDER BY `created` DESC) AS `temp1` ON $sql_table.$sql_parent = `temp1`.`parent` ";
-
-               $sql_extra3 .= " AND `contact-id` IN ($contact_str$contact_str_self) ";
-               $sql_extra3 .= " AND EXISTS (SELECT `id` FROM `item` WHERE (`contact-id` IN ($contact_str)
-                               OR `allow_gid` LIKE '".protect_sprintf('%<'.intval($group).'>%')."') AND `deleted` = 0
-                               AND `parent` = $sql_table.$sql_parent) ";
-
                $o = replace_macros(get_markup_template("section_title.tpl"),array(
                        '$title' => sprintf( t('Group: %s'), $r[0]['name'])
                )) . $o;
@@ -582,11 +588,7 @@ function network_content(&$a, $update = 0) {
                        intval($cid)
                );
                if(count($r)) {
-                       $sql_post_table = " INNER JOIN (SELECT DISTINCT(`parent`) FROM `item`
-                                           WHERE 1 $sql_options AND `contact-id` = ".intval($cid)." AND `deleted` = 0
-                                           ORDER BY `item`.`received` DESC) AS `temp1`
-                                           ON $sql_table.$sql_parent = `temp1`.`parent` ";
-                       $sql_extra = "";
+                       $sql_extra = " AND ".$sql_table.".`contact-id` = ".intval($cid);
 
                        $entries[0] = array(
                                'id' => 'network',
@@ -668,7 +670,7 @@ function network_content(&$a, $update = 0) {
        }
 
        if($conv)
-               $sql_extra3 .= " AND `mention`";
+               $sql_extra3 .= " AND $sql_table.`mention`";
 
        if($update) {
 
index 8028e7e08d9f263cfa3a40a274a8843f1294a14a..aa55c3a098804f7e77e696e07167ecf35c9c7ad1 100644 (file)
@@ -45,11 +45,6 @@ function newmember_content(&$a) {
 
        $o .= '<ul>';
 
-       if(in_array('facebook', $a->plugins))
-               $o .= '<li>' . '<a target="newmember" href="facebook">' . t('Facebook') . '</a><br />' . t("Authorise the Facebook Connector if you currently have a Facebook account and we will \x28optionally\x29 import all your Facebook friends and conversations.") . '</li>' . EOL;
-       else
-               $o .= '<li>' . '<a target="newmember" href="help/Installing-Connectors">' . t('Facebook') . '</a><br />' . t("<em>If</em> this is your own personal server, installing the Facebook addon may ease your transition to the free social web.") . '</li>' . EOL;
-
     $mail_disabled = ((function_exists('imap_open') && (! get_config('system','imap_disabled'))) ? 0 : 1);
        
        if(! $mail_disabled)
index 02260514af91493b287a06438fd799e016c224e5..938e2ffbe35a908a79162b2a4c692a1d9a0d228c 100644 (file)
@@ -1,43 +1,34 @@
 <?php
+require_once('include/NotificationsManager.php');
 
 
 function notify_init(&$a) {
-       if(! local_user())
-               return;
-
+       if(! local_user()) return;
+       $nm = new NotificationsManager();
+               
        if($a->argc > 2 && $a->argv[1] === 'view' && intval($a->argv[2])) {
-               $r = q("select * from notify where id = %d and uid = %d limit 1",
-                       intval($a->argv[2]),
-                       intval(local_user())
-               );
-               if(count($r)) {
-                       q("update notify set seen = 1 where ( link = '%s' or ( parent != 0 and parent = %d and otype = '%s' )) and uid = %d",
-                               dbesc($r[0]['link']),
-                               intval($r[0]['parent']),
-                               dbesc($r[0]['otype']),
-                               intval(local_user())
-                       );
-
+               $note = $nm->getByID($a->argv[2]);
+               if ($note) {
+                       $nm->setSeen($note);
+               
                        // The friendica client has problems with the GUID. this is some workaround
                        if ($a->is_friendica_app()) {
                                require_once("include/items.php");
-                               $urldata = parse_url($r[0]['link']);
+                               $urldata = parse_url($note['link']);
                                $guid = basename($urldata["path"]);
                                $itemdata = get_item_id($guid, local_user());
                                if ($itemdata["id"] != 0)
-                                       $r[0]['link'] = $a->get_baseurl().'/display/'.$itemdata["nick"].'/'.$itemdata["id"];
+                                       $note['link'] = $a->get_baseurl().'/display/'.$itemdata["nick"].'/'.$itemdata["id"];
                        }
 
-                       goaway($r[0]['link']);
+                       goaway($note['link']);
                }
 
                goaway($a->get_baseurl(true));
        }
 
        if($a->argc > 2 && $a->argv[1] === 'mark' && $a->argv[2] === 'all' ) {
-               $r = q("update notify set seen = 1 where uid = %d",
-                       intval(local_user())
-               );
+               $r = $nm->setAllSeen();
                $j = json_encode(array('result' => ($r) ? 'success' : 'fail'));
                echo $j;
                killme();
@@ -45,38 +36,35 @@ function notify_init(&$a) {
 
 }
 
-
 function notify_content(&$a) {
-       if(! local_user())
-               return login();
-
-               $notif_tpl = get_markup_template('notifications.tpl');
-
-               $not_tpl = get_markup_template('notify.tpl');
-               require_once('include/bbcode.php');
-
-               $r = q("SELECT * from notify where uid = %d and seen = 0 order by date desc",
-                       intval(local_user())
-               );
-
-               if (count($r) > 0) {
-                       foreach ($r as $it) {
-                               $notif_content .= replace_macros($not_tpl,array(
-                                       '$item_link' => $a->get_baseurl(true).'/notify/view/'. $it['id'],
-                                       '$item_image' => $it['photo'],
-                                       '$item_text' => strip_tags(bbcode($it['msg'])),
-                                       '$item_when' => relative_date($it['date'])
-                               ));
-                       }
-               } else {
-                       $notif_content .= t('No more system notifications.');
+       if(! local_user()) return login();
+
+       $nm = new NotificationsManager();
+       
+       $notif_tpl = get_markup_template('notifications.tpl');
+
+       $not_tpl = get_markup_template('notify.tpl');
+       require_once('include/bbcode.php');
+
+       $r = $nm->getAll(array('seen'=>0));
+       if ($r!==false && count($r) > 0) {
+               foreach ($r as $it) {
+                       $notif_content .= replace_macros($not_tpl,array(
+                               '$item_link' => $a->get_baseurl(true).'/notify/view/'. $it['id'],
+                               '$item_image' => $it['photo'],
+                               '$item_text' => strip_tags(bbcode($it['msg'])),
+                               '$item_when' => relative_date($it['date'])
+                       ));
                }
+       } else {
+               $notif_content .= t('No more system notifications.');
+       }
 
-               $o .= replace_macros($notif_tpl, array(
-                       '$notif_header' => t('System Notifications'),
-                       '$tabs' => '', // $tabs,
-                       '$notif_content' => $notif_content,
-               ));
+       $o .= replace_macros($notif_tpl, array(
+               '$notif_header' => t('System Notifications'),
+               '$tabs' => false, // $tabs,
+               '$notif_content' => $notif_content,
+       ));
 
        return $o;
 
index 28869b4c128d7ecd3d1f5b8436297b8b11aad5c5..a1ca5a3db5e2e1c831d35bada4b52a3502b54183 100644 (file)
@@ -56,7 +56,15 @@ function completeurl($url, $scheme) {
 
 function parseurl_getsiteinfo_cached($url, $no_guessing = false, $do_oembed = true) {
 
-       $data = Cache::get("parse_url:".$no_guessing.":".$do_oembed.":".$url);
+       if ($url == "")
+               return false;
+
+       $r = q("SELECT * FROM `parsed_url` WHERE `url` = '%s' AND `guessing` = %d AND `oembed` = %d",
+               dbesc(normalise_link($url)), intval(!$no_guessing), intval($do_oembed));
+
+       if ($r)
+               $data = $r[0]["content"];
+
        if (!is_null($data)) {
                $data = unserialize($data);
                return $data;
@@ -64,13 +72,15 @@ function parseurl_getsiteinfo_cached($url, $no_guessing = false, $do_oembed = tr
 
        $data = parseurl_getsiteinfo($url, $no_guessing, $do_oembed);
 
-       Cache::set("parse_url:".$no_guessing.":".$do_oembed.":".$url,serialize($data), CACHE_DAY);
+       q("INSERT INTO `parsed_url` (`url`, `guessing`, `oembed`, `content`, `created`) VALUES ('%s', %d, %d, '%s', '%s')",
+               dbesc(normalise_link($url)), intval(!$no_guessing), intval($do_oembed), dbesc(serialize($data)), dbesc(datetime_convert()));
 
        return $data;
 }
 
 function parseurl_getsiteinfo($url, $no_guessing = false, $do_oembed = true, $count = 1) {
        require_once("include/network.php");
+       require_once("include/Photo.php");
 
        $a = get_app();
 
@@ -321,7 +331,7 @@ function parseurl_getsiteinfo($url, $no_guessing = false, $do_oembed = true, $co
                        $attr[$attribute->name] = $attribute->value;
 
                        $src = completeurl($attr["src"], $url);
-                       $photodata = @getimagesize($src);
+                       $photodata = get_photo_info($src);
 
                        if (($photodata) && ($photodata[0] > 150) and ($photodata[1] > 150)) {
                                if ($photodata[0] > 300) {
@@ -338,12 +348,12 @@ function parseurl_getsiteinfo($url, $no_guessing = false, $do_oembed = true, $co
                        }
 
                }
-    } else {
+    } elseif ($siteinfo["image"] != "") {
                $src = completeurl($siteinfo["image"], $url);
 
                unset($siteinfo["image"]);
 
-               $photodata = @getimagesize($src);
+               $photodata = get_photo_info($src);
 
                if (($photodata) && ($photodata[0] > 10) and ($photodata[1] > 10))
                        $siteinfo["images"][] = array("src"=>$src,
index 57728d32949ef64aab036fbd6f8ffd10531366f3..2eb94576b375b47e13277a2f1cd1b7aa36795e9b 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 require_once("include/datetime.php");
 require_once('include/bbcode.php');
-require_once('include/forums.php');
+require_once('include/ForumManager.php');
 require_once('include/group.php');
 require_once("mod/proxy.php");
 
@@ -96,7 +96,7 @@ function ping_init(&$a) {
                        }
 
                        if(intval(feature_enabled(local_user(),'forumlist_widget'))) {
-                               $forums_unseen = forums_count_unseen();
+                               $forums_unseen = ForumManager::count_unseen_items();
                        }
                }
 
@@ -205,8 +205,8 @@ function ping_init(&$a) {
                        $local_time = datetime_convert('UTC',date_default_timezone_get(),$n['date']);
 
                        call_hooks('ping_xmlize', $n);
-                       $notsxml = '<note href="%s" name="%s" url="%s" photo="%s" date="%s" seen="%s" timestamp="%s" >%s</note>'."\n";
-                       return sprintf ( $notsxml,
+                       $notsxml = '<note id="%d" href="%s" name="%s" url="%s" photo="%s" date="%s" seen="%s" timestamp="%s" >%s</note>'."\n";
+                       return sprintf ( $notsxml, intval($n['id']),
                                xmlify($n['href']), xmlify($n['name']), xmlify($n['url']), xmlify($n['photo']),
                                xmlify(relative_date($n['date'])), xmlify($n['seen']), xmlify(strtotime($local_time)),
                                xmlify($n['message'])
@@ -219,19 +219,21 @@ function ping_init(&$a) {
                                <home>$home</home>\r\n";
                if ($register!=0) echo "<register>$register</register>";
 
-               if ( count($groups_unseen) ) {
+               if (count($groups_unseen)) {
                        echo '<groups>';
-                       foreach ($groups_unseen as $it) {
-                               echo '<group id="' . $it['id'] . '">' . $it['count'] . "</group>";
-                       }
+                       foreach ($groups_unseen as $it)
+                               if ($it['count'] > 0)
+                                       echo '<group id="'.$it['id'].'">'.$it['count']."</group>";
+
                        echo "</groups>";
                }
 
-               if ( count($forums_unseen) ) {
+               if (count($forums_unseen)) {
                        echo '<forums>';
-                       foreach ($forums_unseen as $it) {
-                               echo '<forum id="' . $it['id'] . '">' . $it['count'] . "</forum>";
-                       }
+                       foreach ($forums_unseen as $it)
+                               if ($it['count'] > 0)
+                                       echo '<forum id="'.$it['id'].'">'.$it['count']."</forum>";
+
                        echo "</forums>";
                }
 
@@ -389,7 +391,11 @@ function ping_get_notifications($uid) {
                        // Replace the name with {0} but ensure to make that only once
                        // The {0} is used later and prints the name in bold.
 
-                       $pos = strpos($notification["message"],$notification['name']);
+                       if ($notification['name'] != "")
+                               $pos = strpos($notification["message"],$notification['name']);
+                       else
+                               $pos = false;
+
                        if ($pos !== false)
                                $notification["message"] = substr_replace($notification["message"],"{0}",$pos,strlen($notification["name"]));
 
index ae03aef2cdba27e352834301bde64db12b1149dc..0a1b392169b22dbf3e5748bb4ebbbebc8aa65a25 100644 (file)
@@ -61,8 +61,7 @@ function poco_init(&$a) {
                $update_limit =  date("Y-m-d H:i:s",strtotime($_GET['updatedSince']));
 
        if ($global) {
-               //$r = q("SELECT count(*) AS `total` FROM `gcontact` WHERE `updated` >= '%s' AND ((`last_contact` >= `last_failure`) OR (`updated` >= `last_failure`))  AND `network` IN ('%s')",
-               $r = q("SELECT count(*) AS `total` FROM `gcontact` WHERE `updated` >= '%s' AND `updated` >= `last_failure`  AND `network` IN ('%s', '%s', '%s')",
+               $r = q("SELECT count(*) AS `total` FROM `gcontact` WHERE `updated` >= '%s' AND `updated` >= `last_failure` AND NOT `hide` AND `network` IN ('%s', '%s', '%s')",
                        dbesc($update_limit),
                        dbesc(NETWORK_DFRN),
                        dbesc(NETWORK_DIASPORA),
@@ -94,8 +93,7 @@ function poco_init(&$a) {
 
        if ($global) {
                logger("Start global query", LOGGER_DEBUG);
-               //$r = q("SELECT * FROM `gcontact` WHERE `updated` > '%s' AND `network` IN ('%s') AND ((`last_contact` >= `last_failure`) OR (`updated` > `last_failure`)) LIMIT %d, %d",
-               $r = q("SELECT * FROM `gcontact` WHERE `updated` > '%s' AND `network` IN ('%s', '%s', '%s') AND `updated` > `last_failure`
+               $r = q("SELECT * FROM `gcontact` WHERE `updated` > '%s' AND NOT `hide` AND `network` IN ('%s', '%s', '%s') AND `updated` > `last_failure`
                        ORDER BY `updated` DESC LIMIT %d, %d",
                        dbesc($update_limit),
                        dbesc(NETWORK_DFRN),
index 3ba57c8831962c92df38d197c67267ab535df777..5c372de8ee9243067c9c5e800a5e464022c573e6 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-
+require_once("include/Contact.php");
 
 function profiles_init(&$a) {
 
@@ -482,21 +482,7 @@ function profiles_post(&$a) {
                }
 
                if($is_default) {
-                       $location = $locality;
-
-                       if ($region != "") {
-                               if ($location != "")
-                                       $location .= ", ";
-
-                               $location .= $region;
-                       }
-
-                       if ($country_name != "") {
-                               if ($location != "")
-                                       $location .= ", ";
-
-                               $location .= $country_name;
-                       }
+                       $location = formatted_location(array("locality" => $locality, "region" => $region, "country-name" => $country_name));
 
                        $r = q("UPDATE `contact` SET `about` = '%s', `location` = '%s', `keywords` = '%s', `gender` = '%s' WHERE `self` = 1 AND `uid` = %d",
                                dbesc($about),
index c6bf6530218c55085330211c252fbca9073feee4..abcaf4912798830546409ef6d16876bea9f3db96 100644 (file)
@@ -238,6 +238,10 @@ function proxy_url($url, $writemode = false, $size = "") {
 
        $a = get_app();
 
+       if (substr($url, 0, strlen('http')) !== 'http') {
+               return($url);
+       }
+
        // Only continue if it isn't a local image and the isn't deactivated
        if (proxy_is_local_image($url)) {
                $url = str_replace(normalise_link($a->get_baseurl())."/", $a->get_baseurl()."/", $url);
index beb73b4e2c4ce53a9ed5a6fcaf3ea56108e2040c..6053ee2fbe1553e60d1fa182aff303fbd8e1dad0 100644 (file)
@@ -122,8 +122,8 @@ function pubsub_post(&$a) {
 
        $importer = $r[0];
 
-       $r = q("SELECT * FROM `contact` WHERE `subhub` = 1 AND `id` = %d AND `uid` = %d
-               AND ( `rel` = %d OR `rel` = %d OR network = '%s' ) AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
+       $r = q("SELECT * FROM `contact` WHERE `subhub` AND `id` = %d AND `uid` = %d
+               AND (`rel` = %d OR `rel` = %d OR network = '%s') AND NOT `blocked` LIMIT 1",
                intval($contact_id),
                intval($importer['uid']),
                intval(CONTACT_IS_SHARING),
index 02a6955a00ff9dcf59eeeaf917d4dae25a1d45c7..3efdbf6bde68fe192f07f9d458164ce9d783b292 100644 (file)
@@ -43,57 +43,67 @@ function settings_init(&$a) {
                        'selected'      =>  (($a->argc == 1) && ($a->argv[0] === 'settings')?'active':''),
                        'accesskey' => 'o',
                ),
-               array(
-                       'label' => t('Additional features'),
-                       'url'   => $a->get_baseurl(true).'/settings/features',
-                       'selected'      => (($a->argc > 1) && ($a->argv[1] === 'features') ? 'active' : ''),
-                       'accesskey' => 't',
-               ),
-               array(
-                       'label' => t('Display'),
-                       'url'   => $a->get_baseurl(true).'/settings/display',
-                       'selected'      => (($a->argc > 1) && ($a->argv[1] === 'display')?'active':''),
-                       'accesskey' => 'i',
-               ),
+       );
 
-               array(
-                       'label' => t('Social Networks'),
-                       'url'   => $a->get_baseurl(true).'/settings/connectors',
-                       'selected'      => (($a->argc > 1) && ($a->argv[1] === 'connectors')?'active':''),
-                       'accesskey' => 'w',
-               ),
-               array(
-                       'label' => t('Plugins'),
-                       'url'   => $a->get_baseurl(true).'/settings/addon',
-                       'selected'      => (($a->argc > 1) && ($a->argv[1] === 'addon')?'active':''),
-                       'accesskey' => 'l',
-               ),
-               array(
-                       'label' => t('Delegations'),
-                       'url'   => $a->get_baseurl(true).'/delegate',
-                       'selected'      => (($a->argc == 1) && ($a->argv[0] === 'delegate')?'active':''),
-                       'accesskey' => 'd',
-               ),
-               array(
-                       'label' => t('Connected apps'),
-                       'url' => $a->get_baseurl(true) . '/settings/oauth',
-                       'selected' => (($a->argc > 1) && ($a->argv[1] === 'oauth')?'active':''),
-                       'accesskey' => 'b',
-               ),
-               array(
-                       'label' => t('Export personal data'),
-                       'url' => $a->get_baseurl(true) . '/uexport',
-                       'selected' => (($a->argc == 1) && ($a->argv[0] === 'uexport')?'active':''),
-                       'accesskey' => 'e',
-               ),
-               array(
-                       'label' => t('Remove account'),
-                       'url' => $a->get_baseurl(true) . '/removeme',
-                       'selected' => (($a->argc == 1) && ($a->argv[0] === 'removeme')?'active':''),
-                       'accesskey' => 'r',
-               )
+       if(get_features()) {
+               $tabs[] =       array(
+                                       'label' => t('Additional features'),
+                                       'url'   => $a->get_baseurl(true).'/settings/features',
+                                       'selected'      => (($a->argc > 1) && ($a->argv[1] === 'features') ? 'active' : ''),
+                                       'accesskey' => 't',
+                               );
+       }
+
+       $tabs[] =       array(
+               'label' => t('Display'),
+               'url'   => $a->get_baseurl(true).'/settings/display',
+               'selected'      => (($a->argc > 1) && ($a->argv[1] === 'display')?'active':''),
+               'accesskey' => 'i',
+       );
+
+       $tabs[] =       array(
+               'label' => t('Social Networks'),
+               'url'   => $a->get_baseurl(true).'/settings/connectors',
+               'selected'      => (($a->argc > 1) && ($a->argv[1] === 'connectors')?'active':''),
+               'accesskey' => 'w',
+       );
+
+       $tabs[] =       array(
+               'label' => t('Plugins'),
+               'url'   => $a->get_baseurl(true).'/settings/addon',
+               'selected'      => (($a->argc > 1) && ($a->argv[1] === 'addon')?'active':''),
+               'accesskey' => 'l',
+       );
+
+       $tabs[] =       array(
+               'label' => t('Delegations'),
+               'url'   => $a->get_baseurl(true).'/delegate',
+               'selected'      => (($a->argc == 1) && ($a->argv[0] === 'delegate')?'active':''),
+               'accesskey' => 'd',
        );
 
+       $tabs[] =       array(
+               'label' => t('Connected apps'),
+               'url' => $a->get_baseurl(true) . '/settings/oauth',
+               'selected' => (($a->argc > 1) && ($a->argv[1] === 'oauth')?'active':''),
+               'accesskey' => 'b',
+       );
+
+       $tabs[] =       array(
+               'label' => t('Export personal data'),
+               'url' => $a->get_baseurl(true) . '/uexport',
+               'selected' => (($a->argc == 1) && ($a->argv[0] === 'uexport')?'active':''),
+               'accesskey' => 'e',
+       );
+
+       $tabs[] =       array(
+               'label' => t('Remove account'),
+               'url' => $a->get_baseurl(true) . '/removeme',
+               'selected' => (($a->argc == 1) && ($a->argv[0] === 'removeme')?'active':''),
+               'accesskey' => 'r',
+       );
+
+
        $tabtpl = get_markup_template("generic_links_widget.tpl");
        $a->page['aside'] = replace_macros($tabtpl, array(
                '$title' => t('Settings'),
@@ -622,7 +632,6 @@ function settings_post(&$a) {
 }
 
 
-if(! function_exists('settings_content')) {
 function settings_content(&$a) {
 
        $o = '';
@@ -747,7 +756,7 @@ function settings_content(&$a) {
                        $arr[$fname] = array();
                        $arr[$fname][0] = $fdata[0];
                        foreach(array_slice($fdata,1) as $f) {
-                               $arr[$fname][1][] = array('feature_' .$f[0],$f[1],((intval(get_pconfig(local_user(),'feature',$f[0]))) ? "1" : ''),$f[2],array(t('Off'),t('On')));
+                               $arr[$fname][1][] = array('feature_' .$f[0],$f[1],((intval(feature_enabled(local_user(),$f[0]))) ? "1" : ''),$f[2],array(t('Off'),t('On')));
                        }
                }
 
@@ -1287,5 +1296,5 @@ function settings_content(&$a) {
 
        return $o;
 
-}}
+}
 
index 8bf471d3d2ee70f17877c49ab6a38e7990650d7f..b815348c7010baddd103553dc653f83da416ce86 100644 (file)
@@ -158,13 +158,18 @@ function wall_upload_post(&$a, $desktopmode = true) {
                killme();
        }
 
-       $r = q("select sum(octet_length(data)) as total from photo where uid = %d and scale = 0 and album != 'Contact Photos' ",
-               intval($page_owner_uid)
-       );
 
        $limit = service_class_fetch($page_owner_uid,'photo_upload_limit');
 
-       if(($limit !== false) && (($r[0]['total'] + strlen($imagedata)) > $limit)) {
+       if ($limit) {
+               $r = q("select sum(octet_length(data)) as total from photo where uid = %d and scale = 0 and album != 'Contact Photos' ",
+                       intval($page_owner_uid)
+               );
+               $size = $r[0]['total'];
+       } else
+               $size = 0;
+
+       if(($limit !== false) && (($size + strlen($imagedata)) > $limit)) {
                $msg = upgrade_message(true);
                if ($r_json) {
                        echo json_encode(array('error'=>$msg));
@@ -266,6 +271,7 @@ function wall_upload_post(&$a, $desktopmode = true) {
                return $picture;
        }
 
+
        if ($r_json) {
            echo json_encode(array('ok'=>true));
            killme();
index caa167617cd7abf9a2f9c3cf8808484c69880f54..0689aa19b33f726cf970200c61ac2690ca7f5add 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 
-define( 'UPDATE_VERSION' , 1191 );
+define( 'UPDATE_VERSION' , 1194 );
 
 /**
  *
index 71148e8272fdd7916f7858e505564f222943c096..46a9913f45cf6236838dc099fe60d6808f7309cd 100644 (file)
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-12-27 08:40+0100\n"
+"POT-Creation-Date: 2016-01-24 06:49+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,26 +18,26 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 
 
-#: mod/contacts.php:50 include/identity.php:389
+#: mod/contacts.php:50 include/identity.php:395
 msgid "Network:"
 msgstr ""
 
-#: mod/contacts.php:51 mod/contacts.php:986 mod/videos.php:37
-#: mod/viewcontacts.php:105 mod/dirfind.php:208 mod/network.php:596
+#: mod/contacts.php:51 mod/contacts.php:961 mod/videos.php:37
+#: mod/viewcontacts.php:105 mod/dirfind.php:214 mod/network.php:598
 #: mod/allfriends.php:77 mod/match.php:82 mod/directory.php:172
 #: mod/common.php:123 mod/suggest.php:95 mod/photos.php:41
-#: include/identity.php:295
+#: include/identity.php:298
 msgid "Forum"
 msgstr ""
 
 #: mod/contacts.php:128
 #, php-format
 msgid "%d contact edited."
-msgid_plural "%d contacts edited"
+msgid_plural "%d contacts edited."
 msgstr[0] ""
 msgstr[1] ""
 
-#: mod/contacts.php:159 mod/contacts.php:382
+#: mod/contacts.php:159 mod/contacts.php:383
 msgid "Could not access contact record."
 msgstr ""
 
@@ -49,14 +49,14 @@ msgstr ""
 msgid "Contact updated."
 msgstr ""
 
-#: mod/contacts.php:208 mod/dfrn_request.php:578
+#: mod/contacts.php:208 mod/dfrn_request.php:575
 msgid "Failed to update contact record."
 msgstr ""
 
-#: mod/contacts.php:364 mod/manage.php:96 mod/display.php:493
+#: mod/contacts.php:365 mod/manage.php:96 mod/display.php:509
 #: mod/profile_photo.php:19 mod/profile_photo.php:175 mod/profile_photo.php:186
-#: mod/profile_photo.php:199 mod/ostatus_subscribe.php:9 mod/follow.php:10
-#: mod/follow.php:72 mod/follow.php:137 mod/item.php:169 mod/item.php:181
+#: mod/profile_photo.php:199 mod/ostatus_subscribe.php:9 mod/follow.php:11
+#: mod/follow.php:73 mod/follow.php:155 mod/item.php:180 mod/item.php:192
 #: mod/group.php:19 mod/dfrn_confirm.php:55 mod/fsuggest.php:78
 #: mod/wall_upload.php:77 mod/wall_upload.php:80 mod/viewcontacts.php:40
 #: mod/notifications.php:69 mod/message.php:45 mod/message.php:181
@@ -64,129 +64,129 @@ msgstr ""
 #: mod/allfriends.php:12 mod/events.php:165 mod/wallmessage.php:9
 #: mod/wallmessage.php:33 mod/wallmessage.php:79 mod/wallmessage.php:103
 #: mod/wall_attach.php:67 mod/wall_attach.php:70 mod/settings.php:20
-#: mod/settings.php:116 mod/settings.php:637 mod/register.php:42
+#: mod/settings.php:126 mod/settings.php:646 mod/register.php:42
 #: mod/delegate.php:12 mod/common.php:18 mod/mood.php:114 mod/suggest.php:58
 #: mod/profiles.php:165 mod/profiles.php:615 mod/editpost.php:10 mod/api.php:26
 #: mod/api.php:31 mod/notes.php:22 mod/poke.php:149 mod/repair_ostatus.php:9
 #: mod/invite.php:15 mod/invite.php:101 mod/photos.php:171 mod/photos.php:1105
 #: mod/regmod.php:110 mod/uimport.php:23 mod/attach.php:33
-#: include/items.php:5070 index.php:383
+#: include/items.php:5096 index.php:383
 msgid "Permission denied."
 msgstr ""
 
-#: mod/contacts.php:403
+#: mod/contacts.php:404
 msgid "Contact has been blocked"
 msgstr ""
 
-#: mod/contacts.php:403
+#: mod/contacts.php:404
 msgid "Contact has been unblocked"
 msgstr ""
 
-#: mod/contacts.php:414
+#: mod/contacts.php:415
 msgid "Contact has been ignored"
 msgstr ""
 
-#: mod/contacts.php:414
+#: mod/contacts.php:415
 msgid "Contact has been unignored"
 msgstr ""
 
-#: mod/contacts.php:426
+#: mod/contacts.php:427
 msgid "Contact has been archived"
 msgstr ""
 
-#: mod/contacts.php:426
+#: mod/contacts.php:427
 msgid "Contact has been unarchived"
 msgstr ""
 
-#: mod/contacts.php:453 mod/contacts.php:801
+#: mod/contacts.php:454 mod/contacts.php:802
 msgid "Do you really want to delete this contact?"
 msgstr ""
 
-#: mod/contacts.php:455 mod/follow.php:105 mod/message.php:216
-#: mod/settings.php:1094 mod/settings.php:1100 mod/settings.php:1108
-#: mod/settings.php:1112 mod/settings.php:1117 mod/settings.php:1123
-#: mod/settings.php:1129 mod/settings.php:1135 mod/settings.php:1161
-#: mod/settings.php:1162 mod/settings.php:1163 mod/settings.php:1164
-#: mod/settings.php:1165 mod/dfrn_request.php:850 mod/register.php:238
+#: mod/contacts.php:456 mod/follow.php:110 mod/message.php:216
+#: mod/settings.php:1103 mod/settings.php:1109 mod/settings.php:1117
+#: mod/settings.php:1121 mod/settings.php:1126 mod/settings.php:1132
+#: mod/settings.php:1138 mod/settings.php:1144 mod/settings.php:1170
+#: mod/settings.php:1171 mod/settings.php:1172 mod/settings.php:1173
+#: mod/settings.php:1174 mod/dfrn_request.php:857 mod/register.php:238
 #: mod/suggest.php:29 mod/profiles.php:658 mod/profiles.php:661
-#: mod/profiles.php:687 mod/api.php:105 include/items.php:4902
+#: mod/profiles.php:687 mod/api.php:105 include/items.php:4928
 msgid "Yes"
 msgstr ""
 
-#: mod/contacts.php:458 mod/tagrm.php:11 mod/tagrm.php:94 mod/follow.php:116
+#: mod/contacts.php:459 mod/tagrm.php:11 mod/tagrm.php:94 mod/follow.php:121
 #: mod/videos.php:131 mod/message.php:219 mod/fbrowser.php:93
-#: mod/fbrowser.php:128 mod/settings.php:651 mod/settings.php:677
-#: mod/dfrn_request.php:864 mod/suggest.php:32 mod/editpost.php:148
-#: mod/photos.php:247 mod/photos.php:336 include/conversation.php:1221
-#: include/items.php:4905
+#: mod/fbrowser.php:128 mod/settings.php:660 mod/settings.php:686
+#: mod/dfrn_request.php:871 mod/suggest.php:32 mod/editpost.php:148
+#: mod/photos.php:247 mod/photos.php:336 include/conversation.php:1220
+#: include/items.php:4931
 msgid "Cancel"
 msgstr ""
 
-#: mod/contacts.php:470
+#: mod/contacts.php:471
 msgid "Contact has been removed."
 msgstr ""
 
-#: mod/contacts.php:511
+#: mod/contacts.php:512
 #, php-format
 msgid "You are mutual friends with %s"
 msgstr ""
 
-#: mod/contacts.php:515
+#: mod/contacts.php:516
 #, php-format
 msgid "You are sharing with %s"
 msgstr ""
 
-#: mod/contacts.php:520
+#: mod/contacts.php:521
 #, php-format
 msgid "%s is sharing with you"
 msgstr ""
 
-#: mod/contacts.php:540
+#: mod/contacts.php:541
 msgid "Private communications are not available for this contact."
 msgstr ""
 
-#: mod/contacts.php:543 mod/admin.php:647
+#: mod/contacts.php:544 mod/admin.php:822
 msgid "Never"
 msgstr ""
 
-#: mod/contacts.php:547
+#: mod/contacts.php:548
 msgid "(Update was successful)"
 msgstr ""
 
-#: mod/contacts.php:547
+#: mod/contacts.php:548
 msgid "(Update was not successful)"
 msgstr ""
 
-#: mod/contacts.php:549
+#: mod/contacts.php:550
 msgid "Suggest friends"
 msgstr ""
 
-#: mod/contacts.php:553
+#: mod/contacts.php:554
 #, php-format
 msgid "Network type: %s"
 msgstr ""
 
-#: mod/contacts.php:566
+#: mod/contacts.php:567
 msgid "Communications lost with this contact!"
 msgstr ""
 
-#: mod/contacts.php:569
+#: mod/contacts.php:570
 msgid "Fetch further information for feeds"
 msgstr ""
 
-#: mod/contacts.php:570 mod/admin.php:656
+#: mod/contacts.php:571 mod/admin.php:831
 msgid "Disabled"
 msgstr ""
 
-#: mod/contacts.php:570
+#: mod/contacts.php:571
 msgid "Fetch information"
 msgstr ""
 
-#: mod/contacts.php:570
+#: mod/contacts.php:571
 msgid "Fetch information and keywords"
 msgstr ""
 
-#: mod/contacts.php:586 mod/manage.php:143 mod/fsuggest.php:107
+#: mod/contacts.php:587 mod/manage.php:143 mod/fsuggest.php:107
 #: mod/message.php:342 mod/message.php:525 mod/crepair.php:196
 #: mod/events.php:574 mod/content.php:712 mod/install.php:261
 #: mod/install.php:299 mod/mood.php:137 mod/profiles.php:696
@@ -195,307 +195,308 @@ msgstr ""
 #: mod/photos.php:1678 mod/photos.php:1766 object/Item.php:710
 #: view/theme/cleanzero/config.php:80 view/theme/dispy/config.php:70
 #: view/theme/quattro/config.php:64 view/theme/diabook/config.php:148
-#: view/theme/diabook/theme.php:633 view/theme/clean/config.php:83
-#: view/theme/vier/config.php:107 view/theme/duepuntozero/config.php:59
+#: view/theme/diabook/theme.php:633 view/theme/vier/config.php:107
+#: view/theme/duepuntozero/config.php:59
 msgid "Submit"
 msgstr ""
 
-#: mod/contacts.php:587
+#: mod/contacts.php:588
 msgid "Profile Visibility"
 msgstr ""
 
-#: mod/contacts.php:588
+#: mod/contacts.php:589
 #, php-format
 msgid ""
 "Please choose the profile you would like to display to %s when viewing your "
 "profile securely."
 msgstr ""
 
-#: mod/contacts.php:589
+#: mod/contacts.php:590
 msgid "Contact Information / Notes"
 msgstr ""
 
-#: mod/contacts.php:590
+#: mod/contacts.php:591
 msgid "Edit contact notes"
 msgstr ""
 
-#: mod/contacts.php:595 mod/contacts.php:977 mod/viewcontacts.php:97
+#: mod/contacts.php:596 mod/contacts.php:952 mod/viewcontacts.php:97
 #: mod/nogroup.php:41
 #, php-format
 msgid "Visit %s's profile [%s]"
 msgstr ""
 
-#: mod/contacts.php:596
+#: mod/contacts.php:597
 msgid "Block/Unblock contact"
 msgstr ""
 
-#: mod/contacts.php:597
+#: mod/contacts.php:598
 msgid "Ignore contact"
 msgstr ""
 
-#: mod/contacts.php:598
+#: mod/contacts.php:599
 msgid "Repair URL settings"
 msgstr ""
 
-#: mod/contacts.php:599
+#: mod/contacts.php:600
 msgid "View conversations"
 msgstr ""
 
-#: mod/contacts.php:601
+#: mod/contacts.php:602
 msgid "Delete contact"
 msgstr ""
 
-#: mod/contacts.php:605
+#: mod/contacts.php:606
 msgid "Last update:"
 msgstr ""
 
-#: mod/contacts.php:607
+#: mod/contacts.php:608
 msgid "Update public posts"
 msgstr ""
 
-#: mod/contacts.php:609 mod/admin.php:1656
+#: mod/contacts.php:610
 msgid "Update now"
 msgstr ""
 
-#: mod/contacts.php:611 mod/dirfind.php:190 mod/allfriends.php:65
-#: mod/match.php:71 mod/suggest.php:82 include/contact_widgets.php:32
-#: include/Contact.php:310 include/conversation.php:924
+#: mod/contacts.php:612 mod/follow.php:103 mod/dirfind.php:196
+#: mod/allfriends.php:65 mod/match.php:71 mod/suggest.php:82
+#: include/contact_widgets.php:32 include/Contact.php:297
+#: include/conversation.php:924
 msgid "Connect/Follow"
 msgstr ""
 
-#: mod/contacts.php:614 mod/contacts.php:805 mod/contacts.php:864
-#: mod/admin.php:1120
+#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:865
+#: mod/admin.php:1312
 msgid "Unblock"
 msgstr ""
 
-#: mod/contacts.php:614 mod/contacts.php:805 mod/contacts.php:864
-#: mod/admin.php:1119
+#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:865
+#: mod/admin.php:1311
 msgid "Block"
 msgstr ""
 
-#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:871
+#: mod/contacts.php:616 mod/contacts.php:807 mod/contacts.php:872
 msgid "Unignore"
 msgstr ""
 
-#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:871
+#: mod/contacts.php:616 mod/contacts.php:807 mod/contacts.php:872
 #: mod/notifications.php:54 mod/notifications.php:179 mod/notifications.php:259
 msgid "Ignore"
 msgstr ""
 
-#: mod/contacts.php:618
+#: mod/contacts.php:619
 msgid "Currently blocked"
 msgstr ""
 
-#: mod/contacts.php:619
+#: mod/contacts.php:620
 msgid "Currently ignored"
 msgstr ""
 
-#: mod/contacts.php:620
+#: mod/contacts.php:621
 msgid "Currently archived"
 msgstr ""
 
-#: mod/contacts.php:621 mod/notifications.php:172 mod/notifications.php:251
+#: mod/contacts.php:622 mod/notifications.php:172 mod/notifications.php:251
 msgid "Hide this contact from others"
 msgstr ""
 
-#: mod/contacts.php:621
+#: mod/contacts.php:622
 msgid ""
 "Replies/likes to your public posts <strong>may</strong> still be visible"
 msgstr ""
 
-#: mod/contacts.php:622
+#: mod/contacts.php:623
 msgid "Notification for new posts"
 msgstr ""
 
-#: mod/contacts.php:622
+#: mod/contacts.php:623
 msgid "Send a notification of every new post of this contact"
 msgstr ""
 
-#: mod/contacts.php:625
+#: mod/contacts.php:626
 msgid "Blacklisted keywords"
 msgstr ""
 
-#: mod/contacts.php:625
+#: mod/contacts.php:626
 msgid ""
 "Comma separated list of keywords that should not be converted to hashtags, "
 "when \"Fetch information and keywords\" is selected"
 msgstr ""
 
-#: mod/contacts.php:632 mod/follow.php:121 mod/notifications.php:255
+#: mod/contacts.php:633 mod/follow.php:126 mod/notifications.php:255
 msgid "Profile URL"
 msgstr ""
 
-#: mod/contacts.php:635 mod/follow.php:125 mod/notifications.php:244
-#: mod/events.php:566 mod/directory.php:145 include/identity.php:304
-#: include/bb2diaspora.php:170 include/event.php:36 include/event.php:60
+#: mod/contacts.php:636 mod/notifications.php:244 mod/events.php:566
+#: mod/directory.php:145 include/identity.php:308 include/bb2diaspora.php:170
+#: include/event.php:36 include/event.php:60
 msgid "Location:"
 msgstr ""
 
-#: mod/contacts.php:637 mod/follow.php:127 mod/notifications.php:246
-#: mod/directory.php:153 include/identity.php:313 include/identity.php:630
+#: mod/contacts.php:638 mod/notifications.php:246 mod/directory.php:153
+#: include/identity.php:317 include/identity.php:631
 msgid "About:"
 msgstr ""
 
-#: mod/contacts.php:639 mod/follow.php:129 mod/notifications.php:248
-#: include/identity.php:624
+#: mod/contacts.php:640 mod/follow.php:134 mod/notifications.php:248
+#: include/identity.php:625
 msgid "Tags:"
 msgstr ""
 
-#: mod/contacts.php:684
+#: mod/contacts.php:685
 msgid "Suggestions"
 msgstr ""
 
-#: mod/contacts.php:687
+#: mod/contacts.php:688
 msgid "Suggest potential friends"
 msgstr ""
 
-#: mod/contacts.php:692 mod/group.php:192
+#: mod/contacts.php:693 mod/group.php:192
 msgid "All Contacts"
 msgstr ""
 
-#: mod/contacts.php:695
+#: mod/contacts.php:696
 msgid "Show all contacts"
 msgstr ""
 
-#: mod/contacts.php:700
+#: mod/contacts.php:701
 msgid "Unblocked"
 msgstr ""
 
-#: mod/contacts.php:703
+#: mod/contacts.php:704
 msgid "Only show unblocked contacts"
 msgstr ""
 
-#: mod/contacts.php:709
+#: mod/contacts.php:710
 msgid "Blocked"
 msgstr ""
 
-#: mod/contacts.php:712
+#: mod/contacts.php:713
 msgid "Only show blocked contacts"
 msgstr ""
 
-#: mod/contacts.php:718
+#: mod/contacts.php:719
 msgid "Ignored"
 msgstr ""
 
-#: mod/contacts.php:721
+#: mod/contacts.php:722
 msgid "Only show ignored contacts"
 msgstr ""
 
-#: mod/contacts.php:727
+#: mod/contacts.php:728
 msgid "Archived"
 msgstr ""
 
-#: mod/contacts.php:730
+#: mod/contacts.php:731
 msgid "Only show archived contacts"
 msgstr ""
 
-#: mod/contacts.php:736
+#: mod/contacts.php:737
 msgid "Hidden"
 msgstr ""
 
-#: mod/contacts.php:739
+#: mod/contacts.php:740
 msgid "Only show hidden contacts"
 msgstr ""
 
-#: mod/contacts.php:792 mod/contacts.php:840 mod/viewcontacts.php:116
+#: mod/contacts.php:793 mod/contacts.php:841 mod/viewcontacts.php:116
 #: include/identity.php:741 include/identity.php:744 include/text.php:1012
 #: include/nav.php:123 include/nav.php:187 view/theme/diabook/theme.php:125
 msgid "Contacts"
 msgstr ""
 
-#: mod/contacts.php:796
+#: mod/contacts.php:797
 msgid "Search your contacts"
 msgstr ""
 
-#: mod/contacts.php:797
+#: mod/contacts.php:798
 msgid "Finding: "
 msgstr ""
 
-#: mod/contacts.php:798 mod/directory.php:210 include/contact_widgets.php:34
+#: mod/contacts.php:799 mod/directory.php:210 include/contact_widgets.php:34
 msgid "Find"
 msgstr ""
 
-#: mod/contacts.php:804 mod/settings.php:146 mod/settings.php:676
+#: mod/contacts.php:805 mod/settings.php:156 mod/settings.php:685
 msgid "Update"
 msgstr ""
 
-#: mod/contacts.php:807 mod/contacts.php:878
+#: mod/contacts.php:808 mod/contacts.php:879
 msgid "Archive"
 msgstr ""
 
-#: mod/contacts.php:807 mod/contacts.php:878
+#: mod/contacts.php:808 mod/contacts.php:879
 msgid "Unarchive"
 msgstr ""
 
-#: mod/contacts.php:808 mod/group.php:171 mod/admin.php:1118
-#: mod/content.php:440 mod/content.php:743 mod/settings.php:713
+#: mod/contacts.php:809 mod/group.php:171 mod/admin.php:1310
+#: mod/content.php:440 mod/content.php:743 mod/settings.php:722
 #: mod/photos.php:1723 object/Item.php:134 include/conversation.php:635
 msgid "Delete"
 msgstr ""
 
-#: mod/contacts.php:821 include/identity.php:686 include/nav.php:75
+#: mod/contacts.php:822 include/identity.php:686 include/nav.php:75
 msgid "Status"
 msgstr ""
 
-#: mod/contacts.php:824 include/identity.php:689
+#: mod/contacts.php:825 mod/follow.php:143 include/identity.php:689
 msgid "Status Messages and Posts"
 msgstr ""
 
-#: mod/contacts.php:829 mod/profperm.php:104 mod/newmember.php:32
-#: include/identity.php:578 include/identity.php:664 include/identity.php:694
+#: mod/contacts.php:830 mod/profperm.php:104 mod/newmember.php:32
+#: include/identity.php:579 include/identity.php:665 include/identity.php:694
 #: include/nav.php:76 view/theme/diabook/theme.php:124
 msgid "Profile"
 msgstr ""
 
-#: mod/contacts.php:832 include/identity.php:697
+#: mod/contacts.php:833 include/identity.php:697
 msgid "Profile Details"
 msgstr ""
 
-#: mod/contacts.php:843
+#: mod/contacts.php:844
 msgid "View all contacts"
 msgstr ""
 
-#: mod/contacts.php:849 mod/common.php:134
+#: mod/contacts.php:850 mod/common.php:134
 msgid "Common Friends"
 msgstr ""
 
-#: mod/contacts.php:852
+#: mod/contacts.php:853
 msgid "View all common friends"
 msgstr ""
 
-#: mod/contacts.php:856
+#: mod/contacts.php:857
 msgid "Repair"
 msgstr ""
 
-#: mod/contacts.php:859
+#: mod/contacts.php:860
 msgid "Advanced Contact Settings"
 msgstr ""
 
-#: mod/contacts.php:867
+#: mod/contacts.php:868
 msgid "Toggle Blocked status"
 msgstr ""
 
-#: mod/contacts.php:874
+#: mod/contacts.php:875
 msgid "Toggle Ignored status"
 msgstr ""
 
-#: mod/contacts.php:881
+#: mod/contacts.php:882
 msgid "Toggle Archive status"
 msgstr ""
 
-#: mod/contacts.php:949
+#: mod/contacts.php:924
 msgid "Mutual Friendship"
 msgstr ""
 
-#: mod/contacts.php:953
+#: mod/contacts.php:928
 msgid "is a fan of yours"
 msgstr ""
 
-#: mod/contacts.php:957
+#: mod/contacts.php:932
 msgid "you are a fan of"
 msgstr ""
 
-#: mod/contacts.php:978 mod/nogroup.php:42
+#: mod/contacts.php:953 mod/nogroup.php:42
 msgid "Edit contact"
 msgstr ""
 
@@ -545,23 +546,23 @@ msgstr ""
 msgid "All Contacts (with secure profile access)"
 msgstr ""
 
-#: mod/display.php:82 mod/display.php:280 mod/display.php:497
-#: mod/viewsrc.php:15 mod/admin.php:196 mod/admin.php:1163 mod/admin.php:1384
-#: mod/notice.php:15 include/items.php:4861
+#: mod/display.php:82 mod/display.php:291 mod/display.php:513
+#: mod/viewsrc.php:15 mod/admin.php:234 mod/admin.php:1365 mod/admin.php:1599
+#: mod/notice.php:15 include/items.php:4887
 msgid "Item not found."
 msgstr ""
 
-#: mod/display.php:209 mod/videos.php:197 mod/viewcontacts.php:35
-#: mod/community.php:18 mod/dfrn_request.php:779 mod/search.php:93
+#: mod/display.php:220 mod/videos.php:197 mod/viewcontacts.php:35
+#: mod/community.php:22 mod/dfrn_request.php:786 mod/search.php:93
 #: mod/search.php:99 mod/directory.php:37 mod/photos.php:976
 msgid "Public access denied."
 msgstr ""
 
-#: mod/display.php:328 mod/profile.php:155
+#: mod/display.php:339 mod/profile.php:155
 msgid "Access to this profile has been restricted."
 msgstr ""
 
-#: mod/display.php:490
+#: mod/display.php:506
 msgid "Item has been removed."
 msgstr ""
 
@@ -596,8 +597,8 @@ msgid ""
 "join."
 msgstr ""
 
-#: mod/newmember.php:22 mod/admin.php:1215 mod/admin.php:1460
-#: mod/settings.php:99 include/nav.php:182 view/theme/diabook/theme.php:544
+#: mod/newmember.php:22 mod/admin.php:1418 mod/admin.php:1676
+#: mod/settings.php:109 include/nav.php:182 view/theme/diabook/theme.php:544
 #: view/theme/diabook/theme.php:648
 msgid "Settings"
 msgstr ""
@@ -658,60 +659,44 @@ msgstr ""
 msgid "Connecting"
 msgstr ""
 
-#: mod/newmember.php:49 mod/newmember.php:51 include/contact_selectors.php:81
-msgid "Facebook"
-msgstr ""
-
-#: mod/newmember.php:49
-msgid ""
-"Authorise the Facebook Connector if you currently have a Facebook account "
-"and we will (optionally) import all your Facebook friends and conversations."
-msgstr ""
-
 #: mod/newmember.php:51
-msgid ""
-"<em>If</em> this is your own personal server, installing the Facebook addon "
-"may ease your transition to the free social web."
-msgstr ""
-
-#: mod/newmember.php:56
 msgid "Importing Emails"
 msgstr ""
 
-#: mod/newmember.php:56
+#: mod/newmember.php:51
 msgid ""
 "Enter your email access information on your Connector Settings page if you "
 "wish to import and interact with friends or mailing lists from your email "
 "INBOX"
 msgstr ""
 
-#: mod/newmember.php:58
+#: mod/newmember.php:53
 msgid "Go to Your Contacts Page"
 msgstr ""
 
-#: mod/newmember.php:58
+#: mod/newmember.php:53
 msgid ""
 "Your Contacts page is your gateway to managing friendships and connecting "
 "with friends on other networks. Typically you enter their address or site "
 "URL in the <em>Add New Contact</em> dialog."
 msgstr ""
 
-#: mod/newmember.php:60
+#: mod/newmember.php:55
 msgid "Go to Your Site's Directory"
 msgstr ""
 
-#: mod/newmember.php:60
+#: mod/newmember.php:55
 msgid ""
 "The Directory page lets you find other people in this network or other "
 "federated sites. Look for a <em>Connect</em> or <em>Follow</em> link on "
 "their profile page. Provide your own Identity Address if requested."
 msgstr ""
 
-#: mod/newmember.php:62
+#: mod/newmember.php:57
 msgid "Finding New People"
 msgstr ""
 
-#: mod/newmember.php:62
+#: mod/newmember.php:57
 msgid ""
 "On the side panel of the Contacts page are several tools to find new "
 "friends. We can match people by interest, look up people by name or "
@@ -720,41 +705,41 @@ msgid ""
 "hours."
 msgstr ""
 
-#: mod/newmember.php:66 include/group.php:283
+#: mod/newmember.php:61 include/group.php:283
 msgid "Groups"
 msgstr ""
 
-#: mod/newmember.php:70
+#: mod/newmember.php:65
 msgid "Group Your Contacts"
 msgstr ""
 
-#: mod/newmember.php:70
+#: mod/newmember.php:65
 msgid ""
 "Once you have made some friends, organize them into private conversation "
 "groups from the sidebar of your Contacts page and then you can interact with "
 "each group privately on your Network page."
 msgstr ""
 
-#: mod/newmember.php:73
+#: mod/newmember.php:68
 msgid "Why Aren't My Posts Public?"
 msgstr ""
 
-#: mod/newmember.php:73
+#: mod/newmember.php:68
 msgid ""
 "Friendica respects your privacy. By default, your posts will only show up to "
 "people you've added as friends. For more information, see the help section "
 "from the link above."
 msgstr ""
 
-#: mod/newmember.php:78
+#: mod/newmember.php:73
 msgid "Getting Help"
 msgstr ""
 
-#: mod/newmember.php:82
+#: mod/newmember.php:77
 msgid "Go to the Help Section"
 msgstr ""
 
-#: mod/newmember.php:82
+#: mod/newmember.php:77
 msgid ""
 "Our <strong>help</strong> pages may be consulted for detail on other program "
 "features and resources."
@@ -769,7 +754,7 @@ msgid ""
 "Account not found and OpenID registration is not permitted on this site."
 msgstr ""
 
-#: mod/openid.php:93 include/auth.php:112 include/auth.php:175
+#: mod/openid.php:93 include/auth.php:118 include/auth.php:181
 msgid "Login failed."
 msgstr ""
 
@@ -855,18 +840,18 @@ msgstr ""
 msgid "Image upload failed."
 msgstr ""
 
-#: mod/subthread.php:87 mod/tagger.php:62 mod/like.php:168
+#: mod/subthread.php:87 mod/tagger.php:62 include/like.php:165
 #: include/conversation.php:130 include/conversation.php:266
-#: include/text.php:1993 include/diaspora.php:2147
+#: include/text.php:2000 include/diaspora.php:2169
 #: view/theme/diabook/theme.php:471
 msgid "photo"
 msgstr ""
 
-#: mod/subthread.php:87 mod/tagger.php:62 mod/like.php:168 mod/like.php:346
-#: include/conversation.php:125 include/conversation.php:134
-#: include/conversation.php:261 include/conversation.php:270
-#: include/diaspora.php:2147 view/theme/diabook/theme.php:466
-#: view/theme/diabook/theme.php:475
+#: mod/subthread.php:87 mod/tagger.php:62 include/like.php:165
+#: include/like.php:334 include/conversation.php:125
+#: include/conversation.php:134 include/conversation.php:261
+#: include/conversation.php:270 include/diaspora.php:2169
+#: view/theme/diabook/theme.php:466 view/theme/diabook/theme.php:475
 msgid "status"
 msgstr ""
 
@@ -927,7 +912,7 @@ msgstr ""
 msgid "Keep this window open until done."
 msgstr ""
 
-#: mod/filer.php:30 include/conversation.php:1133 include/conversation.php:1151
+#: mod/filer.php:30 include/conversation.php:1132 include/conversation.php:1150
 msgid "Save to Folder:"
 msgstr ""
 
@@ -939,54 +924,54 @@ msgstr ""
 msgid "Save"
 msgstr ""
 
-#: mod/follow.php:18 mod/dfrn_request.php:863
+#: mod/follow.php:19 mod/dfrn_request.php:870
 msgid "Submit Request"
 msgstr ""
 
-#: mod/follow.php:29
+#: mod/follow.php:30
 msgid "You already added this contact."
 msgstr ""
 
-#: mod/follow.php:38
+#: mod/follow.php:39
 msgid "Diaspora support isn't enabled. Contact can't be added."
 msgstr ""
 
-#: mod/follow.php:45
+#: mod/follow.php:46
 msgid "OStatus support is disabled. Contact can't be added."
 msgstr ""
 
-#: mod/follow.php:52
+#: mod/follow.php:53
 msgid "The network type couldn't be detected. Contact can't be added."
 msgstr ""
 
-#: mod/follow.php:104 mod/dfrn_request.php:849
+#: mod/follow.php:109 mod/dfrn_request.php:856
 msgid "Please answer the following:"
 msgstr ""
 
-#: mod/follow.php:105 mod/dfrn_request.php:850
+#: mod/follow.php:110 mod/dfrn_request.php:857
 #, php-format
 msgid "Does %s know you?"
 msgstr ""
 
-#: mod/follow.php:105 mod/settings.php:1094 mod/settings.php:1100
-#: mod/settings.php:1108 mod/settings.php:1112 mod/settings.php:1117
-#: mod/settings.php:1123 mod/settings.php:1129 mod/settings.php:1135
-#: mod/settings.php:1161 mod/settings.php:1162 mod/settings.php:1163
-#: mod/settings.php:1164 mod/settings.php:1165 mod/dfrn_request.php:850
+#: mod/follow.php:110 mod/settings.php:1103 mod/settings.php:1109
+#: mod/settings.php:1117 mod/settings.php:1121 mod/settings.php:1126
+#: mod/settings.php:1132 mod/settings.php:1138 mod/settings.php:1144
+#: mod/settings.php:1170 mod/settings.php:1171 mod/settings.php:1172
+#: mod/settings.php:1173 mod/settings.php:1174 mod/dfrn_request.php:857
 #: mod/register.php:239 mod/profiles.php:658 mod/profiles.php:662
 #: mod/profiles.php:687 mod/api.php:106
 msgid "No"
 msgstr ""
 
-#: mod/follow.php:106 mod/dfrn_request.php:854
+#: mod/follow.php:111 mod/dfrn_request.php:861
 msgid "Add a personal note:"
 msgstr ""
 
-#: mod/follow.php:112 mod/dfrn_request.php:860
+#: mod/follow.php:117 mod/dfrn_request.php:867
 msgid "Your Identity Address:"
 msgstr ""
 
-#: mod/follow.php:162
+#: mod/follow.php:180
 msgid "Contact added"
 msgstr ""
 
@@ -994,38 +979,38 @@ msgstr ""
 msgid "Unable to locate original post."
 msgstr ""
 
-#: mod/item.php:318
+#: mod/item.php:329
 msgid "Empty post discarded."
 msgstr ""
 
-#: mod/item.php:456 mod/wall_upload.php:213 mod/wall_upload.php:227
-#: mod/wall_upload.php:234 include/Photo.php:954 include/Photo.php:969
-#: include/Photo.php:976 include/Photo.php:998 include/message.php:145
+#: mod/item.php:467 mod/wall_upload.php:213 mod/wall_upload.php:227
+#: mod/wall_upload.php:234 include/Photo.php:958 include/Photo.php:973
+#: include/Photo.php:980 include/Photo.php:1002 include/message.php:145
 msgid "Wall Photos"
 msgstr ""
 
-#: mod/item.php:830
+#: mod/item.php:842
 msgid "System error. Post not saved."
 msgstr ""
 
-#: mod/item.php:959
+#: mod/item.php:971
 #, php-format
 msgid ""
 "This message was sent to you by %s, a member of the Friendica social network."
 msgstr ""
 
-#: mod/item.php:961
+#: mod/item.php:973
 #, php-format
 msgid "You may visit them online at %s"
 msgstr ""
 
-#: mod/item.php:962
+#: mod/item.php:974
 msgid ""
 "Please contact the sender by replying to this post if you do not wish to "
 "receive these messages."
 msgstr ""
 
-#: mod/item.php:966
+#: mod/item.php:978
 #, php-format
 msgid "%s posted an update."
 msgstr ""
@@ -1074,7 +1059,7 @@ msgstr ""
 msgid "Members"
 msgstr ""
 
-#: mod/group.php:193 mod/network.php:563 mod/content.php:130
+#: mod/group.php:193 mod/network.php:576 mod/content.php:130
 msgid "Group is empty"
 msgstr ""
 
@@ -1176,7 +1161,7 @@ msgstr ""
 msgid "Unable to update your contact profile details on our system"
 msgstr ""
 
-#: mod/dfrn_confirm.php:753 mod/dfrn_request.php:734 include/items.php:4273
+#: mod/dfrn_confirm.php:753 mod/dfrn_request.php:741 include/items.php:4299
 msgid "[Name Withheld]"
 msgstr ""
 
@@ -1185,7 +1170,7 @@ msgstr ""
 msgid "%1$s has joined %2$s"
 msgstr ""
 
-#: mod/profile.php:21 include/identity.php:53
+#: mod/profile.php:21 include/identity.php:51
 msgid "Requested profile is not available."
 msgstr ""
 
@@ -1209,7 +1194,7 @@ msgstr ""
 msgid "Access to this item is restricted."
 msgstr ""
 
-#: mod/videos.php:383 include/text.php:1465
+#: mod/videos.php:383 include/text.php:1472
 msgid "View Video"
 msgstr ""
 
@@ -1245,7 +1230,7 @@ msgstr ""
 
 #: mod/wall_upload.php:20 mod/wall_upload.php:33 mod/wall_upload.php:86
 #: mod/wall_upload.php:122 mod/wall_upload.php:125 mod/wall_attach.php:17
-#: mod/wall_attach.php:25 mod/wall_attach.php:76 include/api.php:1735
+#: mod/wall_attach.php:25 mod/wall_attach.php:76 include/api.php:1781
 msgid "Invalid request."
 msgstr ""
 
@@ -1303,7 +1288,7 @@ msgid ""
 "Password reset failed."
 msgstr ""
 
-#: mod/lostpass.php:109 boot.php:1310
+#: mod/lostpass.php:109 boot.php:1444
 msgid "Password Reset"
 msgstr ""
 
@@ -1379,37 +1364,6 @@ msgstr ""
 msgid "Reset"
 msgstr ""
 
-#: mod/like.php:170 include/conversation.php:122 include/conversation.php:258
-#: include/text.php:1991 view/theme/diabook/theme.php:463
-msgid "event"
-msgstr ""
-
-#: mod/like.php:187 include/conversation.php:141 include/diaspora.php:2163
-#: view/theme/diabook/theme.php:480
-#, php-format
-msgid "%1$s likes %2$s's %3$s"
-msgstr ""
-
-#: mod/like.php:189 include/conversation.php:144
-#, php-format
-msgid "%1$s doesn't like %2$s's %3$s"
-msgstr ""
-
-#: mod/like.php:191
-#, php-format
-msgid "%1$s is attending %2$s's %3$s"
-msgstr ""
-
-#: mod/like.php:193
-#, php-format
-msgid "%1$s is not attending %2$s's %3$s"
-msgstr ""
-
-#: mod/like.php:195
-#, php-format
-msgid "%1$s may attend %2$s's %3$s"
-msgstr ""
-
 #: mod/ping.php:265
 msgid "{0} wants to be your friend"
 msgstr ""
@@ -1438,11 +1392,11 @@ msgstr ""
 msgid "System"
 msgstr ""
 
-#: mod/notifications.php:87 mod/admin.php:228 include/nav.php:154
+#: mod/notifications.php:87 mod/admin.php:390 include/nav.php:154
 msgid "Network"
 msgstr ""
 
-#: mod/notifications.php:93 mod/network.php:381
+#: mod/notifications.php:93 mod/network.php:384
 msgid "Personal"
 msgstr ""
 
@@ -1484,7 +1438,7 @@ msgstr ""
 msgid "if applicable"
 msgstr ""
 
-#: mod/notifications.php:176 mod/notifications.php:257 mod/admin.php:1116
+#: mod/notifications.php:176 mod/notifications.php:257 mod/admin.php:1308
 msgid "Approve"
 msgstr ""
 
@@ -1534,8 +1488,8 @@ msgstr ""
 msgid "New Follower"
 msgstr ""
 
-#: mod/notifications.php:250 mod/directory.php:147 include/identity.php:306
-#: include/identity.php:589
+#: mod/notifications.php:250 mod/directory.php:147 include/identity.php:310
+#: include/identity.php:590
 msgid "Gender:"
 msgstr ""
 
@@ -1706,7 +1660,7 @@ msgstr ""
 
 #: mod/message.php:290 mod/message.php:298 mod/message.php:427
 #: mod/message.php:435 mod/wallmessage.php:127 mod/wallmessage.php:135
-#: include/conversation.php:1129 include/conversation.php:1147
+#: include/conversation.php:1128 include/conversation.php:1146
 msgid "Please enter a link URL:"
 msgstr ""
 
@@ -1728,19 +1682,19 @@ msgid "Your message:"
 msgstr ""
 
 #: mod/message.php:339 mod/message.php:523 mod/wallmessage.php:154
-#: mod/editpost.php:110 include/conversation.php:1184
+#: mod/editpost.php:110 include/conversation.php:1183
 msgid "Upload photo"
 msgstr ""
 
 #: mod/message.php:340 mod/message.php:524 mod/wallmessage.php:155
-#: mod/editpost.php:114 include/conversation.php:1188
+#: mod/editpost.php:114 include/conversation.php:1187
 msgid "Insert web link"
 msgstr ""
 
 #: mod/message.php:341 mod/message.php:526 mod/content.php:501
 #: mod/content.php:885 mod/wallmessage.php:156 mod/editpost.php:124
 #: mod/photos.php:1610 object/Item.php:396 include/conversation.php:713
-#: include/conversation.php:1202
+#: include/conversation.php:1201
 msgid "Please wait"
 msgstr ""
 
@@ -1756,7 +1710,7 @@ msgstr ""
 msgid "Delete message"
 msgstr ""
 
-#: mod/message.php:507 mod/message.php:582
+#: mod/message.php:507 mod/message.php:584
 msgid "Delete conversation"
 msgstr ""
 
@@ -1770,26 +1724,26 @@ msgstr ""
 msgid "Send Reply"
 msgstr ""
 
-#: mod/message.php:555
+#: mod/message.php:557
 #, php-format
 msgid "Unknown sender - %s"
 msgstr ""
 
-#: mod/message.php:558
+#: mod/message.php:560
 #, php-format
 msgid "You and %s"
 msgstr ""
 
-#: mod/message.php:561
+#: mod/message.php:563
 #, php-format
 msgid "%s and You"
 msgstr ""
 
-#: mod/message.php:585
+#: mod/message.php:587
 msgid "D, d M Y - g:i A"
 msgstr ""
 
-#: mod/message.php:588
+#: mod/message.php:590
 #, php-format
 msgid "%d message"
 msgid_plural "%d messages"
@@ -1841,8 +1795,8 @@ msgstr ""
 msgid "Refetch contact data"
 msgstr ""
 
-#: mod/crepair.php:170 mod/admin.php:1114 mod/admin.php:1126 mod/admin.php:1127
-#: mod/admin.php:1140 mod/settings.php:652 mod/settings.php:678
+#: mod/crepair.php:170 mod/admin.php:1306 mod/admin.php:1318 mod/admin.php:1319
+#: mod/admin.php:1332 mod/settings.php:661 mod/settings.php:687
 msgid "Name"
 msgstr ""
 
@@ -1892,7 +1846,7 @@ msgid ""
 "entries from this contact."
 msgstr ""
 
-#: mod/bookmarklet.php:12 boot.php:1296 include/nav.php:91
+#: mod/bookmarklet.php:12 boot.php:1430 include/nav.php:91
 msgid "Login"
 msgstr ""
 
@@ -1904,24 +1858,24 @@ msgstr ""
 msgid "Access denied."
 msgstr ""
 
-#: mod/dirfind.php:188 mod/allfriends.php:80 mod/match.php:85
-#: mod/suggest.php:98 include/contact_widgets.php:10 include/identity.php:209
+#: mod/dirfind.php:194 mod/allfriends.php:80 mod/match.php:85
+#: mod/suggest.php:98 include/contact_widgets.php:10 include/identity.php:212
 msgid "Connect"
 msgstr ""
 
-#: mod/dirfind.php:189 mod/allfriends.php:64 mod/match.php:70
-#: mod/directory.php:162 mod/suggest.php:81 include/Contact.php:296
-#: include/Contact.php:309 include/Contact.php:351 include/conversation.php:912
+#: mod/dirfind.php:195 mod/allfriends.php:64 mod/match.php:70
+#: mod/directory.php:162 mod/suggest.php:81 include/Contact.php:283
+#: include/Contact.php:296 include/Contact.php:338 include/conversation.php:912
 #: include/conversation.php:926
 msgid "View Profile"
 msgstr ""
 
-#: mod/dirfind.php:218
+#: mod/dirfind.php:224
 #, php-format
 msgid "People Search - %s"
 msgstr ""
 
-#: mod/dirfind.php:225 mod/match.php:105
+#: mod/dirfind.php:231 mod/match.php:105
 msgid "No matches"
 msgstr ""
 
@@ -1944,776 +1898,807 @@ msgstr ""
 msgid "Contacts who are not members of a group"
 msgstr ""
 
-#: mod/admin.php:80
+#: mod/admin.php:92
 msgid "Theme settings updated."
 msgstr ""
 
-#: mod/admin.php:127 mod/admin.php:713
+#: mod/admin.php:156 mod/admin.php:888
 msgid "Site"
 msgstr ""
 
-#: mod/admin.php:128 mod/admin.php:657 mod/admin.php:1109 mod/admin.php:1124
+#: mod/admin.php:157 mod/admin.php:832 mod/admin.php:1301 mod/admin.php:1316
 msgid "Users"
 msgstr ""
 
-#: mod/admin.php:129 mod/admin.php:1213 mod/admin.php:1273 mod/settings.php:66
+#: mod/admin.php:158 mod/admin.php:1416 mod/admin.php:1476 mod/settings.php:72
 msgid "Plugins"
 msgstr ""
 
-#: mod/admin.php:130 mod/admin.php:1458 mod/admin.php:1509
+#: mod/admin.php:159 mod/admin.php:1674 mod/admin.php:1724
 msgid "Themes"
 msgstr ""
 
-#: mod/admin.php:131
+#: mod/admin.php:160 mod/settings.php:50
+msgid "Additional features"
+msgstr ""
+
+#: mod/admin.php:161
 msgid "DB updates"
 msgstr ""
 
-#: mod/admin.php:132 mod/admin.php:223
+#: mod/admin.php:162 mod/admin.php:385
 msgid "Inspect Queue"
 msgstr ""
 
-#: mod/admin.php:147 mod/admin.php:156 mod/admin.php:1597
+#: mod/admin.php:163 mod/admin.php:354
+msgid "Federation Statistics"
+msgstr ""
+
+#: mod/admin.php:177 mod/admin.php:188 mod/admin.php:1792
 msgid "Logs"
 msgstr ""
 
-#: mod/admin.php:148
+#: mod/admin.php:178 mod/admin.php:1859
+msgid "View Logs"
+msgstr ""
+
+#: mod/admin.php:179
 msgid "probe address"
 msgstr ""
 
-#: mod/admin.php:149
+#: mod/admin.php:180
 msgid "check webfinger"
 msgstr ""
 
-#: mod/admin.php:154 include/nav.php:194
+#: mod/admin.php:186 include/nav.php:194
 msgid "Admin"
 msgstr ""
 
-#: mod/admin.php:155
+#: mod/admin.php:187
 msgid "Plugin Features"
 msgstr ""
 
-#: mod/admin.php:157
+#: mod/admin.php:189
 msgid "diagnostics"
 msgstr ""
 
-#: mod/admin.php:158
+#: mod/admin.php:190
 msgid "User registrations waiting for confirmation"
 msgstr ""
 
-#: mod/admin.php:222 mod/admin.php:272 mod/admin.php:712 mod/admin.php:1108
-#: mod/admin.php:1212 mod/admin.php:1272 mod/admin.php:1457 mod/admin.php:1508
-#: mod/admin.php:1596
+#: mod/admin.php:347
+msgid ""
+"This page offers you some numbers to the known part of the federated social "
+"network your Friendica node is part of. These numbers are not complete but "
+"only reflect the part of the network your node is aware of."
+msgstr ""
+
+#: mod/admin.php:348
+msgid ""
+"The <em>Auto Discovered Contact Directory</em> feature is not enabled, it "
+"will improve the data displayed here."
+msgstr ""
+
+#: mod/admin.php:353 mod/admin.php:384 mod/admin.php:441 mod/admin.php:887
+#: mod/admin.php:1300 mod/admin.php:1415 mod/admin.php:1475 mod/admin.php:1673
+#: mod/admin.php:1723 mod/admin.php:1791 mod/admin.php:1858
 msgid "Administration"
 msgstr ""
 
-#: mod/admin.php:225
+#: mod/admin.php:360
+#, php-format
+msgid "Currently this node is aware of %d nodes from the following platforms:"
+msgstr ""
+
+#: mod/admin.php:387
 msgid "ID"
 msgstr ""
 
-#: mod/admin.php:226
+#: mod/admin.php:388
 msgid "Recipient Name"
 msgstr ""
 
-#: mod/admin.php:227
+#: mod/admin.php:389
 msgid "Recipient Profile"
 msgstr ""
 
-#: mod/admin.php:229
+#: mod/admin.php:391
 msgid "Created"
 msgstr ""
 
-#: mod/admin.php:230
+#: mod/admin.php:392
 msgid "Last Tried"
 msgstr ""
 
-#: mod/admin.php:231
+#: mod/admin.php:393
 msgid ""
 "This page lists the content of the queue for outgoing postings. These are "
 "postings the initial delivery failed for. They will be resend later and "
 "eventually deleted if the delivery fails permanently."
 msgstr ""
 
-#: mod/admin.php:243 mod/admin.php:1062
+#: mod/admin.php:412 mod/admin.php:1254
 msgid "Normal Account"
 msgstr ""
 
-#: mod/admin.php:244 mod/admin.php:1063
+#: mod/admin.php:413 mod/admin.php:1255
 msgid "Soapbox Account"
 msgstr ""
 
-#: mod/admin.php:245 mod/admin.php:1064
+#: mod/admin.php:414 mod/admin.php:1256
 msgid "Community/Celebrity Account"
 msgstr ""
 
-#: mod/admin.php:246 mod/admin.php:1065
+#: mod/admin.php:415 mod/admin.php:1257
 msgid "Automatic Friend Account"
 msgstr ""
 
-#: mod/admin.php:247
+#: mod/admin.php:416
 msgid "Blog Account"
 msgstr ""
 
-#: mod/admin.php:248
+#: mod/admin.php:417
 msgid "Private Forum"
 msgstr ""
 
-#: mod/admin.php:267
+#: mod/admin.php:436
 msgid "Message queues"
 msgstr ""
 
-#: mod/admin.php:273
+#: mod/admin.php:442
 msgid "Summary"
 msgstr ""
 
-#: mod/admin.php:275
+#: mod/admin.php:444
 msgid "Registered users"
 msgstr ""
 
-#: mod/admin.php:277
+#: mod/admin.php:446
 msgid "Pending registrations"
 msgstr ""
 
-#: mod/admin.php:278
+#: mod/admin.php:447
 msgid "Version"
 msgstr ""
 
-#: mod/admin.php:283
+#: mod/admin.php:452
 msgid "Active plugins"
 msgstr ""
 
-#: mod/admin.php:306
+#: mod/admin.php:475
 msgid "Can not parse base url. Must have at least <scheme>://<domain>"
 msgstr ""
 
-#: mod/admin.php:589
+#: mod/admin.php:760
 msgid "RINO2 needs mcrypt php extension to work."
 msgstr ""
 
-#: mod/admin.php:597
+#: mod/admin.php:768
 msgid "Site settings updated."
 msgstr ""
 
-#: mod/admin.php:621 mod/settings.php:903
+#: mod/admin.php:796 mod/settings.php:912
 msgid "No special theme for mobile devices"
 msgstr ""
 
-#: mod/admin.php:640
+#: mod/admin.php:815
 msgid "No community page"
 msgstr ""
 
-#: mod/admin.php:641
+#: mod/admin.php:816
 msgid "Public postings from users of this site"
 msgstr ""
 
-#: mod/admin.php:642
+#: mod/admin.php:817
 msgid "Global community page"
 msgstr ""
 
-#: mod/admin.php:648
+#: mod/admin.php:823
 msgid "At post arrival"
 msgstr ""
 
-#: mod/admin.php:649 include/contact_selectors.php:56
+#: mod/admin.php:824 include/contact_selectors.php:56
 msgid "Frequently"
 msgstr ""
 
-#: mod/admin.php:650 include/contact_selectors.php:57
+#: mod/admin.php:825 include/contact_selectors.php:57
 msgid "Hourly"
 msgstr ""
 
-#: mod/admin.php:651 include/contact_selectors.php:58
+#: mod/admin.php:826 include/contact_selectors.php:58
 msgid "Twice daily"
 msgstr ""
 
-#: mod/admin.php:652 include/contact_selectors.php:59
+#: mod/admin.php:827 include/contact_selectors.php:59
 msgid "Daily"
 msgstr ""
 
-#: mod/admin.php:658
+#: mod/admin.php:833
 msgid "Users, Global Contacts"
 msgstr ""
 
-#: mod/admin.php:659
+#: mod/admin.php:834
 msgid "Users, Global Contacts/fallback"
 msgstr ""
 
-#: mod/admin.php:663
+#: mod/admin.php:838
 msgid "One month"
 msgstr ""
 
-#: mod/admin.php:664
+#: mod/admin.php:839
 msgid "Three months"
 msgstr ""
 
-#: mod/admin.php:665
+#: mod/admin.php:840
 msgid "Half a year"
 msgstr ""
 
-#: mod/admin.php:666
+#: mod/admin.php:841
 msgid "One year"
 msgstr ""
 
-#: mod/admin.php:671
+#: mod/admin.php:846
 msgid "Multi user instance"
 msgstr ""
 
-#: mod/admin.php:694
+#: mod/admin.php:869
 msgid "Closed"
 msgstr ""
 
-#: mod/admin.php:695
+#: mod/admin.php:870
 msgid "Requires approval"
 msgstr ""
 
-#: mod/admin.php:696
+#: mod/admin.php:871
 msgid "Open"
 msgstr ""
 
-#: mod/admin.php:700
+#: mod/admin.php:875
 msgid "No SSL policy, links will track page SSL state"
 msgstr ""
 
-#: mod/admin.php:701
+#: mod/admin.php:876
 msgid "Force all links to use SSL"
 msgstr ""
 
-#: mod/admin.php:702
+#: mod/admin.php:877
 msgid "Self-signed certificate, use SSL for local links only (discouraged)"
 msgstr ""
 
-#: mod/admin.php:714 mod/admin.php:1274 mod/admin.php:1510 mod/admin.php:1598
-#: mod/settings.php:650 mod/settings.php:760 mod/settings.php:804
-#: mod/settings.php:873 mod/settings.php:960 mod/settings.php:1195
+#: mod/admin.php:889 mod/admin.php:1477 mod/admin.php:1725 mod/admin.php:1793
+#: mod/admin.php:1942 mod/settings.php:659 mod/settings.php:769
+#: mod/settings.php:813 mod/settings.php:882 mod/settings.php:969
+#: mod/settings.php:1204
 msgid "Save Settings"
 msgstr ""
 
-#: mod/admin.php:715 mod/register.php:263
+#: mod/admin.php:890 mod/register.php:263
 msgid "Registration"
 msgstr ""
 
-#: mod/admin.php:716
+#: mod/admin.php:891
 msgid "File upload"
 msgstr ""
 
-#: mod/admin.php:717
+#: mod/admin.php:892
 msgid "Policies"
 msgstr ""
 
-#: mod/admin.php:718
+#: mod/admin.php:893
 msgid "Advanced"
 msgstr ""
 
-#: mod/admin.php:719
+#: mod/admin.php:894
 msgid "Auto Discovered Contact Directory"
 msgstr ""
 
-#: mod/admin.php:720
+#: mod/admin.php:895
 msgid "Performance"
 msgstr ""
 
-#: mod/admin.php:721
+#: mod/admin.php:896
 msgid ""
 "Relocate - WARNING: advanced function. Could make this server unreachable."
 msgstr ""
 
-#: mod/admin.php:724
+#: mod/admin.php:899
 msgid "Site name"
 msgstr ""
 
-#: mod/admin.php:725
+#: mod/admin.php:900
 msgid "Host name"
 msgstr ""
 
-#: mod/admin.php:726
+#: mod/admin.php:901
 msgid "Sender Email"
 msgstr ""
 
-#: mod/admin.php:726
+#: mod/admin.php:901
 msgid ""
 "The email address your server shall use to send notification emails from."
 msgstr ""
 
-#: mod/admin.php:727
+#: mod/admin.php:902
 msgid "Banner/Logo"
 msgstr ""
 
-#: mod/admin.php:728
+#: mod/admin.php:903
 msgid "Shortcut icon"
 msgstr ""
 
-#: mod/admin.php:728
+#: mod/admin.php:903
 msgid "Link to an icon that will be used for browsers."
 msgstr ""
 
-#: mod/admin.php:729
+#: mod/admin.php:904
 msgid "Touch icon"
 msgstr ""
 
-#: mod/admin.php:729
+#: mod/admin.php:904
 msgid "Link to an icon that will be used for tablets and mobiles."
 msgstr ""
 
-#: mod/admin.php:730
+#: mod/admin.php:905
 msgid "Additional Info"
 msgstr ""
 
-#: mod/admin.php:730
+#: mod/admin.php:905
 #, php-format
 msgid ""
 "For public servers: you can add additional information here that will be "
 "listed at %s/siteinfo."
 msgstr ""
 
-#: mod/admin.php:731
+#: mod/admin.php:906
 msgid "System language"
 msgstr ""
 
-#: mod/admin.php:732
+#: mod/admin.php:907
 msgid "System theme"
 msgstr ""
 
-#: mod/admin.php:732
+#: mod/admin.php:907
 msgid ""
 "Default system theme - may be over-ridden by user profiles - <a href='#' "
 "id='cnftheme'>change theme settings</a>"
 msgstr ""
 
-#: mod/admin.php:733
+#: mod/admin.php:908
 msgid "Mobile system theme"
 msgstr ""
 
-#: mod/admin.php:733
+#: mod/admin.php:908
 msgid "Theme for mobile devices"
 msgstr ""
 
-#: mod/admin.php:734
+#: mod/admin.php:909
 msgid "SSL link policy"
 msgstr ""
 
-#: mod/admin.php:734
+#: mod/admin.php:909
 msgid "Determines whether generated links should be forced to use SSL"
 msgstr ""
 
-#: mod/admin.php:735
+#: mod/admin.php:910
 msgid "Force SSL"
 msgstr ""
 
-#: mod/admin.php:735
+#: mod/admin.php:910
 msgid ""
 "Force all Non-SSL requests to SSL - Attention: on some systems it could lead "
 "to endless loops."
 msgstr ""
 
-#: mod/admin.php:736
+#: mod/admin.php:911
 msgid "Old style 'Share'"
 msgstr ""
 
-#: mod/admin.php:736
+#: mod/admin.php:911
 msgid "Deactivates the bbcode element 'share' for repeating items."
 msgstr ""
 
-#: mod/admin.php:737
+#: mod/admin.php:912
 msgid "Hide help entry from navigation menu"
 msgstr ""
 
-#: mod/admin.php:737
+#: mod/admin.php:912
 msgid ""
 "Hides the menu entry for the Help pages from the navigation menu. You can "
 "still access it calling /help directly."
 msgstr ""
 
-#: mod/admin.php:738
+#: mod/admin.php:913
 msgid "Single user instance"
 msgstr ""
 
-#: mod/admin.php:738
+#: mod/admin.php:913
 msgid "Make this instance multi-user or single-user for the named user"
 msgstr ""
 
-#: mod/admin.php:739
+#: mod/admin.php:914
 msgid "Maximum image size"
 msgstr ""
 
-#: mod/admin.php:739
+#: mod/admin.php:914
 msgid ""
 "Maximum size in bytes of uploaded images. Default is 0, which means no "
 "limits."
 msgstr ""
 
-#: mod/admin.php:740
+#: mod/admin.php:915
 msgid "Maximum image length"
 msgstr ""
 
-#: mod/admin.php:740
+#: mod/admin.php:915
 msgid ""
 "Maximum length in pixels of the longest side of uploaded images. Default is "
 "-1, which means no limits."
 msgstr ""
 
-#: mod/admin.php:741
+#: mod/admin.php:916
 msgid "JPEG image quality"
 msgstr ""
 
-#: mod/admin.php:741
+#: mod/admin.php:916
 msgid ""
 "Uploaded JPEGS will be saved at this quality setting [0-100]. Default is "
 "100, which is full quality."
 msgstr ""
 
-#: mod/admin.php:743
+#: mod/admin.php:918
 msgid "Register policy"
 msgstr ""
 
-#: mod/admin.php:744
+#: mod/admin.php:919
 msgid "Maximum Daily Registrations"
 msgstr ""
 
-#: mod/admin.php:744
+#: mod/admin.php:919
 msgid ""
 "If registration is permitted above, this sets the maximum number of new user "
 "registrations to accept per day.  If register is set to closed, this setting "
 "has no effect."
 msgstr ""
 
-#: mod/admin.php:745
+#: mod/admin.php:920
 msgid "Register text"
 msgstr ""
 
-#: mod/admin.php:745
+#: mod/admin.php:920
 msgid "Will be displayed prominently on the registration page."
 msgstr ""
 
-#: mod/admin.php:746
+#: mod/admin.php:921
 msgid "Accounts abandoned after x days"
 msgstr ""
 
-#: mod/admin.php:746
+#: mod/admin.php:921
 msgid ""
 "Will not waste system resources polling external sites for abandonded "
 "accounts. Enter 0 for no time limit."
 msgstr ""
 
-#: mod/admin.php:747
+#: mod/admin.php:922
 msgid "Allowed friend domains"
 msgstr ""
 
-#: mod/admin.php:747
+#: mod/admin.php:922
 msgid ""
 "Comma separated list of domains which are allowed to establish friendships "
 "with this site. Wildcards are accepted. Empty to allow any domains"
 msgstr ""
 
-#: mod/admin.php:748
+#: mod/admin.php:923
 msgid "Allowed email domains"
 msgstr ""
 
-#: mod/admin.php:748
+#: mod/admin.php:923
 msgid ""
 "Comma separated list of domains which are allowed in email addresses for "
 "registrations to this site. Wildcards are accepted. Empty to allow any "
 "domains"
 msgstr ""
 
-#: mod/admin.php:749
+#: mod/admin.php:924
 msgid "Block public"
 msgstr ""
 
-#: mod/admin.php:749
+#: mod/admin.php:924
 msgid ""
 "Check to block public access to all otherwise public personal pages on this "
 "site unless you are currently logged in."
 msgstr ""
 
-#: mod/admin.php:750
+#: mod/admin.php:925
 msgid "Force publish"
 msgstr ""
 
-#: mod/admin.php:750
+#: mod/admin.php:925
 msgid ""
 "Check to force all profiles on this site to be listed in the site directory."
 msgstr ""
 
-#: mod/admin.php:751
+#: mod/admin.php:926
 msgid "Global directory URL"
 msgstr ""
 
-#: mod/admin.php:751
+#: mod/admin.php:926
 msgid ""
 "URL to the global directory. If this is not set, the global directory is "
 "completely unavailable to the application."
 msgstr ""
 
-#: mod/admin.php:752
+#: mod/admin.php:927
 msgid "Allow threaded items"
 msgstr ""
 
-#: mod/admin.php:752
+#: mod/admin.php:927
 msgid "Allow infinite level threading for items on this site."
 msgstr ""
 
-#: mod/admin.php:753
+#: mod/admin.php:928
 msgid "Private posts by default for new users"
 msgstr ""
 
-#: mod/admin.php:753
+#: mod/admin.php:928
 msgid ""
 "Set default post permissions for all new members to the default privacy "
 "group rather than public."
 msgstr ""
 
-#: mod/admin.php:754
+#: mod/admin.php:929
 msgid "Don't include post content in email notifications"
 msgstr ""
 
-#: mod/admin.php:754
+#: mod/admin.php:929
 msgid ""
 "Don't include the content of a post/comment/private message/etc. in the "
 "email notifications that are sent out from this site, as a privacy measure."
 msgstr ""
 
-#: mod/admin.php:755
+#: mod/admin.php:930
 msgid "Disallow public access to addons listed in the apps menu."
 msgstr ""
 
-#: mod/admin.php:755
+#: mod/admin.php:930
 msgid ""
 "Checking this box will restrict addons listed in the apps menu to members "
 "only."
 msgstr ""
 
-#: mod/admin.php:756
+#: mod/admin.php:931
 msgid "Don't embed private images in posts"
 msgstr ""
 
-#: mod/admin.php:756
+#: mod/admin.php:931
 msgid ""
 "Don't replace locally-hosted private photos in posts with an embedded copy "
 "of the image. This means that contacts who receive posts containing private "
 "photos will have to authenticate and load each image, which may take a while."
 msgstr ""
 
-#: mod/admin.php:757
+#: mod/admin.php:932
 msgid "Allow Users to set remote_self"
 msgstr ""
 
-#: mod/admin.php:757
+#: mod/admin.php:932
 msgid ""
 "With checking this, every user is allowed to mark every contact as a "
 "remote_self in the repair contact dialog. Setting this flag on a contact "
 "causes mirroring every posting of that contact in the users stream."
 msgstr ""
 
-#: mod/admin.php:758
+#: mod/admin.php:933
 msgid "Block multiple registrations"
 msgstr ""
 
-#: mod/admin.php:758
+#: mod/admin.php:933
 msgid "Disallow users to register additional accounts for use as pages."
 msgstr ""
 
-#: mod/admin.php:759
+#: mod/admin.php:934
 msgid "OpenID support"
 msgstr ""
 
-#: mod/admin.php:759
+#: mod/admin.php:934
 msgid "OpenID support for registration and logins."
 msgstr ""
 
-#: mod/admin.php:760
+#: mod/admin.php:935
 msgid "Fullname check"
 msgstr ""
 
-#: mod/admin.php:760
+#: mod/admin.php:935
 msgid ""
 "Force users to register with a space between firstname and lastname in Full "
 "name, as an antispam measure"
 msgstr ""
 
-#: mod/admin.php:761
+#: mod/admin.php:936
 msgid "UTF-8 Regular expressions"
 msgstr ""
 
-#: mod/admin.php:761
+#: mod/admin.php:936
 msgid "Use PHP UTF8 regular expressions"
 msgstr ""
 
-#: mod/admin.php:762
+#: mod/admin.php:937
 msgid "Community Page Style"
 msgstr ""
 
-#: mod/admin.php:762
+#: mod/admin.php:937
 msgid ""
 "Type of community page to show. 'Global community' shows every public "
 "posting from an open distributed network that arrived on this server."
 msgstr ""
 
-#: mod/admin.php:763
+#: mod/admin.php:938
 msgid "Posts per user on community page"
 msgstr ""
 
-#: mod/admin.php:763
+#: mod/admin.php:938
 msgid ""
 "The maximum number of posts per user on the community page. (Not valid for "
 "'Global Community')"
 msgstr ""
 
-#: mod/admin.php:764
+#: mod/admin.php:939
 msgid "Enable OStatus support"
 msgstr ""
 
-#: mod/admin.php:764
+#: mod/admin.php:939
 msgid ""
 "Provide built-in OStatus (StatusNet, GNU Social etc.) compatibility. All "
 "communications in OStatus are public, so privacy warnings will be "
 "occasionally displayed."
 msgstr ""
 
-#: mod/admin.php:765
+#: mod/admin.php:940
 msgid "OStatus conversation completion interval"
 msgstr ""
 
-#: mod/admin.php:765
+#: mod/admin.php:940
 msgid ""
 "How often shall the poller check for new entries in OStatus conversations? "
 "This can be a very ressource task."
 msgstr ""
 
-#: mod/admin.php:766
+#: mod/admin.php:941
 msgid "OStatus support can only be enabled if threading is enabled."
 msgstr ""
 
-#: mod/admin.php:768
+#: mod/admin.php:943
 msgid ""
 "Diaspora support can't be enabled because Friendica was installed into a sub "
 "directory."
 msgstr ""
 
-#: mod/admin.php:769
+#: mod/admin.php:944
 msgid "Enable Diaspora support"
 msgstr ""
 
-#: mod/admin.php:769
+#: mod/admin.php:944
 msgid "Provide built-in Diaspora network compatibility."
 msgstr ""
 
-#: mod/admin.php:770
+#: mod/admin.php:945
 msgid "Only allow Friendica contacts"
 msgstr ""
 
-#: mod/admin.php:770
+#: mod/admin.php:945
 msgid ""
 "All contacts must use Friendica protocols. All other built-in communication "
 "protocols disabled."
 msgstr ""
 
-#: mod/admin.php:771
+#: mod/admin.php:946
 msgid "Verify SSL"
 msgstr ""
 
-#: mod/admin.php:771
+#: mod/admin.php:946
 msgid ""
 "If you wish, you can turn on strict certificate checking. This will mean you "
 "cannot connect (at all) to self-signed SSL sites."
 msgstr ""
 
-#: mod/admin.php:772
+#: mod/admin.php:947
 msgid "Proxy user"
 msgstr ""
 
-#: mod/admin.php:773
+#: mod/admin.php:948
 msgid "Proxy URL"
 msgstr ""
 
-#: mod/admin.php:774
+#: mod/admin.php:949
 msgid "Network timeout"
 msgstr ""
 
-#: mod/admin.php:774
+#: mod/admin.php:949
 msgid "Value is in seconds. Set to 0 for unlimited (not recommended)."
 msgstr ""
 
-#: mod/admin.php:775
+#: mod/admin.php:950
 msgid "Delivery interval"
 msgstr ""
 
-#: mod/admin.php:775
+#: mod/admin.php:950
 msgid ""
 "Delay background delivery processes by this many seconds to reduce system "
 "load. Recommend: 4-5 for shared hosts, 2-3 for virtual private servers. 0-1 "
 "for large dedicated servers."
 msgstr ""
 
-#: mod/admin.php:776
+#: mod/admin.php:951
 msgid "Poll interval"
 msgstr ""
 
-#: mod/admin.php:776
+#: mod/admin.php:951
 msgid ""
 "Delay background polling processes by this many seconds to reduce system "
 "load. If 0, use delivery interval."
 msgstr ""
 
-#: mod/admin.php:777
+#: mod/admin.php:952
 msgid "Maximum Load Average"
 msgstr ""
 
-#: mod/admin.php:777
+#: mod/admin.php:952
 msgid ""
 "Maximum system load before delivery and poll processes are deferred - "
 "default 50."
 msgstr ""
 
-#: mod/admin.php:778
+#: mod/admin.php:953
 msgid "Maximum Load Average (Frontend)"
 msgstr ""
 
-#: mod/admin.php:778
+#: mod/admin.php:953
 msgid "Maximum system load before the frontend quits service - default 50."
 msgstr ""
 
-#: mod/admin.php:779
+#: mod/admin.php:954
 msgid "Maximum table size for optimization"
 msgstr ""
 
-#: mod/admin.php:779
+#: mod/admin.php:954
 msgid ""
 "Maximum table size (in MB) for the automatic optimization - default 100 MB. "
 "Enter -1 to disable it."
 msgstr ""
 
-#: mod/admin.php:780
+#: mod/admin.php:955
 msgid "Minimum level of fragmentation"
 msgstr ""
 
-#: mod/admin.php:780
+#: mod/admin.php:955
 msgid ""
 "Minimum fragmenation level to start the automatic optimization - default "
 "value is 30%."
 msgstr ""
 
-#: mod/admin.php:782
+#: mod/admin.php:957
 msgid "Periodical check of global contacts"
 msgstr ""
 
-#: mod/admin.php:782
+#: mod/admin.php:957
 msgid ""
 "If enabled, the global contacts are checked periodically for missing or "
 "outdated data and the vitality of the contacts and servers."
 msgstr ""
 
-#: mod/admin.php:783
+#: mod/admin.php:958
 msgid "Days between requery"
 msgstr ""
 
-#: mod/admin.php:783
+#: mod/admin.php:958
 msgid "Number of days after which a server is requeried for his contacts."
 msgstr ""
 
-#: mod/admin.php:784
+#: mod/admin.php:959
 msgid "Discover contacts from other servers"
 msgstr ""
 
-#: mod/admin.php:784
+#: mod/admin.php:959
 msgid ""
 "Periodically query other servers for contacts. You can choose between "
 "'users': the users on the remote system, 'Global Contacts': active contacts "
@@ -2723,32 +2708,32 @@ msgid ""
 "Global Contacts'."
 msgstr ""
 
-#: mod/admin.php:785
+#: mod/admin.php:960
 msgid "Timeframe for fetching global contacts"
 msgstr ""
 
-#: mod/admin.php:785
+#: mod/admin.php:960
 msgid ""
 "When the discovery is activated, this value defines the timeframe for the "
 "activity of the global contacts that are fetched from other servers."
 msgstr ""
 
-#: mod/admin.php:786
+#: mod/admin.php:961
 msgid "Search the local directory"
 msgstr ""
 
-#: mod/admin.php:786
+#: mod/admin.php:961
 msgid ""
 "Search the local directory instead of the global directory. When searching "
 "locally, every search will be executed on the global directory in the "
 "background. This improves the search results when the search is repeated."
 msgstr ""
 
-#: mod/admin.php:788
+#: mod/admin.php:963
 msgid "Publish server information"
 msgstr ""
 
-#: mod/admin.php:788
+#: mod/admin.php:963
 msgid ""
 "If enabled, general server and usage data will be published. The data "
 "contains the name and version of the server, number of users with public "
@@ -2756,204 +2741,204 @@ msgid ""
 "href='http://the-federation.info/'>the-federation.info</a> for details."
 msgstr ""
 
-#: mod/admin.php:790
+#: mod/admin.php:965
 msgid "Use MySQL full text engine"
 msgstr ""
 
-#: mod/admin.php:790
+#: mod/admin.php:965
 msgid ""
 "Activates the full text engine. Speeds up search - but can only search for "
 "four and more characters."
 msgstr ""
 
-#: mod/admin.php:791
+#: mod/admin.php:966
 msgid "Suppress Language"
 msgstr ""
 
-#: mod/admin.php:791
+#: mod/admin.php:966
 msgid "Suppress language information in meta information about a posting."
 msgstr ""
 
-#: mod/admin.php:792
+#: mod/admin.php:967
 msgid "Suppress Tags"
 msgstr ""
 
-#: mod/admin.php:792
+#: mod/admin.php:967
 msgid "Suppress showing a list of hashtags at the end of the posting."
 msgstr ""
 
-#: mod/admin.php:793
+#: mod/admin.php:968
 msgid "Path to item cache"
 msgstr ""
 
-#: mod/admin.php:793
+#: mod/admin.php:968
 msgid "The item caches buffers generated bbcode and external images."
 msgstr ""
 
-#: mod/admin.php:794
+#: mod/admin.php:969
 msgid "Cache duration in seconds"
 msgstr ""
 
-#: mod/admin.php:794
+#: mod/admin.php:969
 msgid ""
 "How long should the cache files be hold? Default value is 86400 seconds (One "
 "day). To disable the item cache, set the value to -1."
 msgstr ""
 
-#: mod/admin.php:795
+#: mod/admin.php:970
 msgid "Maximum numbers of comments per post"
 msgstr ""
 
-#: mod/admin.php:795
+#: mod/admin.php:970
 msgid "How much comments should be shown for each post? Default value is 100."
 msgstr ""
 
-#: mod/admin.php:796
+#: mod/admin.php:971
 msgid "Path for lock file"
 msgstr ""
 
-#: mod/admin.php:796
+#: mod/admin.php:971
 msgid ""
 "The lock file is used to avoid multiple pollers at one time. Only define a "
 "folder here."
 msgstr ""
 
-#: mod/admin.php:797
+#: mod/admin.php:972
 msgid "Temp path"
 msgstr ""
 
-#: mod/admin.php:797
+#: mod/admin.php:972
 msgid ""
 "If you have a restricted system where the webserver can't access the system "
 "temp path, enter another path here."
 msgstr ""
 
-#: mod/admin.php:798
+#: mod/admin.php:973
 msgid "Base path to installation"
 msgstr ""
 
-#: mod/admin.php:798
+#: mod/admin.php:973
 msgid ""
 "If the system cannot detect the correct path to your installation, enter the "
 "correct path here. This setting should only be set if you are using a "
 "restricted system and symbolic links to your webroot."
 msgstr ""
 
-#: mod/admin.php:799
+#: mod/admin.php:974
 msgid "Disable picture proxy"
 msgstr ""
 
-#: mod/admin.php:799
+#: mod/admin.php:974
 msgid ""
 "The picture proxy increases performance and privacy. It shouldn't be used on "
 "systems with very low bandwith."
 msgstr ""
 
-#: mod/admin.php:800
+#: mod/admin.php:975
 msgid "Enable old style pager"
 msgstr ""
 
-#: mod/admin.php:800
+#: mod/admin.php:975
 msgid ""
 "The old style pager has page numbers but slows down massively the page speed."
 msgstr ""
 
-#: mod/admin.php:801
+#: mod/admin.php:976
 msgid "Only search in tags"
 msgstr ""
 
-#: mod/admin.php:801
+#: mod/admin.php:976
 msgid "On large systems the text search can slow down the system extremely."
 msgstr ""
 
-#: mod/admin.php:803
+#: mod/admin.php:978
 msgid "New base url"
 msgstr ""
 
-#: mod/admin.php:803
+#: mod/admin.php:978
 msgid ""
 "Change base url for this server. Sends relocate message to all DFRN contacts "
 "of all users."
 msgstr ""
 
-#: mod/admin.php:805
+#: mod/admin.php:980
 msgid "RINO Encryption"
 msgstr ""
 
-#: mod/admin.php:805
+#: mod/admin.php:980
 msgid "Encryption layer between nodes."
 msgstr ""
 
-#: mod/admin.php:806
+#: mod/admin.php:981
 msgid "Embedly API key"
 msgstr ""
 
-#: mod/admin.php:806
+#: mod/admin.php:981
 msgid ""
 "<a href='http://embed.ly'>Embedly</a> is used to fetch additional data for "
 "web pages. This is an optional parameter."
 msgstr ""
 
-#: mod/admin.php:824
+#: mod/admin.php:1010
 msgid "Update has been marked successful"
 msgstr ""
 
-#: mod/admin.php:832
+#: mod/admin.php:1018
 #, php-format
 msgid "Database structure update %s was successfully applied."
 msgstr ""
 
-#: mod/admin.php:835
+#: mod/admin.php:1021
 #, php-format
 msgid "Executing of database structure update %s failed with error: %s"
 msgstr ""
 
-#: mod/admin.php:847
+#: mod/admin.php:1033
 #, php-format
 msgid "Executing %s failed with error: %s"
 msgstr ""
 
-#: mod/admin.php:850
+#: mod/admin.php:1036
 #, php-format
 msgid "Update %s was successfully applied."
 msgstr ""
 
-#: mod/admin.php:854
+#: mod/admin.php:1040
 #, php-format
 msgid "Update %s did not return a status. Unknown if it succeeded."
 msgstr ""
 
-#: mod/admin.php:856
+#: mod/admin.php:1042
 #, php-format
 msgid "There was no additional update function %s that needed to be called."
 msgstr ""
 
-#: mod/admin.php:875
+#: mod/admin.php:1061
 msgid "No failed updates."
 msgstr ""
 
-#: mod/admin.php:876
+#: mod/admin.php:1062
 msgid "Check database structure"
 msgstr ""
 
-#: mod/admin.php:881
+#: mod/admin.php:1067
 msgid "Failed Updates"
 msgstr ""
 
-#: mod/admin.php:882
+#: mod/admin.php:1068
 msgid ""
 "This does not include updates prior to 1139, which did not return a status."
 msgstr ""
 
-#: mod/admin.php:883
+#: mod/admin.php:1069
 msgid "Mark success (if update was manually applied)"
 msgstr ""
 
-#: mod/admin.php:884
+#: mod/admin.php:1070
 msgid "Attempt to execute this update step automatically"
 msgstr ""
 
-#: mod/admin.php:916
+#: mod/admin.php:1102
 #, php-format
 msgid ""
 "\n"
@@ -2961,7 +2946,7 @@ msgid ""
 "\t\t\t\tthe administrator of %2$s has set up an account for you."
 msgstr ""
 
-#: mod/admin.php:919
+#: mod/admin.php:1105
 #, php-format
 msgid ""
 "\n"
@@ -2997,232 +2982,254 @@ msgid ""
 "\t\t\tThank you and welcome to %4$s."
 msgstr ""
 
-#: mod/admin.php:951 include/user.php:423
+#: mod/admin.php:1137 include/user.php:423
 #, php-format
 msgid "Registration details for %s"
 msgstr ""
 
-#: mod/admin.php:963
+#: mod/admin.php:1149
 #, php-format
 msgid "%s user blocked/unblocked"
 msgid_plural "%s users blocked/unblocked"
 msgstr[0] ""
 msgstr[1] ""
 
-#: mod/admin.php:970
+#: mod/admin.php:1156
 #, php-format
 msgid "%s user deleted"
 msgid_plural "%s users deleted"
 msgstr[0] ""
 msgstr[1] ""
 
-#: mod/admin.php:1009
+#: mod/admin.php:1203
 #, php-format
 msgid "User '%s' deleted"
 msgstr ""
 
-#: mod/admin.php:1017
+#: mod/admin.php:1211
 #, php-format
 msgid "User '%s' unblocked"
 msgstr ""
 
-#: mod/admin.php:1017
+#: mod/admin.php:1211
 #, php-format
 msgid "User '%s' blocked"
 msgstr ""
 
-#: mod/admin.php:1110
+#: mod/admin.php:1302
 msgid "Add User"
 msgstr ""
 
-#: mod/admin.php:1111
+#: mod/admin.php:1303
 msgid "select all"
 msgstr ""
 
-#: mod/admin.php:1112
+#: mod/admin.php:1304
 msgid "User registrations waiting for confirm"
 msgstr ""
 
-#: mod/admin.php:1113
+#: mod/admin.php:1305
 msgid "User waiting for permanent deletion"
 msgstr ""
 
-#: mod/admin.php:1114
+#: mod/admin.php:1306
 msgid "Request date"
 msgstr ""
 
-#: mod/admin.php:1114 mod/admin.php:1126 mod/admin.php:1127 mod/admin.php:1142
+#: mod/admin.php:1306 mod/admin.php:1318 mod/admin.php:1319 mod/admin.php:1334
 #: include/contact_selectors.php:79 include/contact_selectors.php:86
 msgid "Email"
 msgstr ""
 
-#: mod/admin.php:1115
+#: mod/admin.php:1307
 msgid "No registrations."
 msgstr ""
 
-#: mod/admin.php:1117
+#: mod/admin.php:1309
 msgid "Deny"
 msgstr ""
 
-#: mod/admin.php:1121
+#: mod/admin.php:1313
 msgid "Site admin"
 msgstr ""
 
-#: mod/admin.php:1122
+#: mod/admin.php:1314
 msgid "Account expired"
 msgstr ""
 
-#: mod/admin.php:1125
+#: mod/admin.php:1317
 msgid "New User"
 msgstr ""
 
-#: mod/admin.php:1126 mod/admin.php:1127
+#: mod/admin.php:1318 mod/admin.php:1319
 msgid "Register date"
 msgstr ""
 
-#: mod/admin.php:1126 mod/admin.php:1127
+#: mod/admin.php:1318 mod/admin.php:1319
 msgid "Last login"
 msgstr ""
 
-#: mod/admin.php:1126 mod/admin.php:1127
+#: mod/admin.php:1318 mod/admin.php:1319
 msgid "Last item"
 msgstr ""
 
-#: mod/admin.php:1126
+#: mod/admin.php:1318
 msgid "Deleted since"
 msgstr ""
 
-#: mod/admin.php:1127 mod/settings.php:41
+#: mod/admin.php:1319 mod/settings.php:41
 msgid "Account"
 msgstr ""
 
-#: mod/admin.php:1129
+#: mod/admin.php:1321
 msgid ""
 "Selected users will be deleted!\\n\\nEverything these users had posted on "
 "this site will be permanently deleted!\\n\\nAre you sure?"
 msgstr ""
 
-#: mod/admin.php:1130
+#: mod/admin.php:1322
 msgid ""
 "The user {0} will be deleted!\\n\\nEverything this user has posted on this "
 "site will be permanently deleted!\\n\\nAre you sure?"
 msgstr ""
 
-#: mod/admin.php:1140
+#: mod/admin.php:1332
 msgid "Name of the new user."
 msgstr ""
 
-#: mod/admin.php:1141
+#: mod/admin.php:1333
 msgid "Nickname"
 msgstr ""
 
-#: mod/admin.php:1141
+#: mod/admin.php:1333
 msgid "Nickname of the new user."
 msgstr ""
 
-#: mod/admin.php:1142
+#: mod/admin.php:1334
 msgid "Email address of the new user."
 msgstr ""
 
-#: mod/admin.php:1175
+#: mod/admin.php:1377
 #, php-format
 msgid "Plugin %s disabled."
 msgstr ""
 
-#: mod/admin.php:1179
+#: mod/admin.php:1381
 #, php-format
 msgid "Plugin %s enabled."
 msgstr ""
 
-#: mod/admin.php:1189 mod/admin.php:1413
+#: mod/admin.php:1392 mod/admin.php:1628
 msgid "Disable"
 msgstr ""
 
-#: mod/admin.php:1191 mod/admin.php:1415
+#: mod/admin.php:1394 mod/admin.php:1630
 msgid "Enable"
 msgstr ""
 
-#: mod/admin.php:1214 mod/admin.php:1459
+#: mod/admin.php:1417 mod/admin.php:1675
 msgid "Toggle"
 msgstr ""
 
-#: mod/admin.php:1222 mod/admin.php:1469
+#: mod/admin.php:1425 mod/admin.php:1684
 msgid "Author: "
 msgstr ""
 
-#: mod/admin.php:1223 mod/admin.php:1470
+#: mod/admin.php:1426 mod/admin.php:1685
 msgid "Maintainer: "
 msgstr ""
 
-#: mod/admin.php:1275
-#: view/smarty3/compiled/f835364006028b1061f37be121c9bd9db5fa50a9.file.admin_plugins.tpl.php:42
+#: mod/admin.php:1478
 msgid "Reload active plugins"
 msgstr ""
 
-#: mod/admin.php:1373
+#: mod/admin.php:1483
+#, php-format
+msgid ""
+"There are currently no plugins available on your node. You can find the "
+"official plugin repository at %1$s and might find other interesting plugins "
+"in the open plugin registry at %2$s"
+msgstr ""
+
+#: mod/admin.php:1588
 msgid "No themes found."
 msgstr ""
 
-#: mod/admin.php:1451
+#: mod/admin.php:1666
 msgid "Screenshot"
 msgstr ""
 
-#: mod/admin.php:1511
+#: mod/admin.php:1726
 msgid "Reload active themes"
 msgstr ""
 
-#: mod/admin.php:1515
+#: mod/admin.php:1731
+#, php-format
+msgid "No themes found on the system. They should be paced in %1$s"
+msgstr ""
+
+#: mod/admin.php:1732
 msgid "[Experimental]"
 msgstr ""
 
-#: mod/admin.php:1516
+#: mod/admin.php:1733
 msgid "[Unsupported]"
 msgstr ""
 
-#: mod/admin.php:1543
+#: mod/admin.php:1757
 msgid "Log settings updated."
 msgstr ""
 
-#: mod/admin.php:1599
+#: mod/admin.php:1794
 msgid "Clear"
 msgstr ""
 
-#: mod/admin.php:1605
+#: mod/admin.php:1799
 msgid "Enable Debugging"
 msgstr ""
 
-#: mod/admin.php:1606
+#: mod/admin.php:1800
 msgid "Log file"
 msgstr ""
 
-#: mod/admin.php:1606
+#: mod/admin.php:1800
 msgid ""
 "Must be writable by web server. Relative to your Friendica top-level "
 "directory."
 msgstr ""
 
-#: mod/admin.php:1607
+#: mod/admin.php:1801
 msgid "Log level"
 msgstr ""
 
-#: mod/admin.php:1657 include/acl_selectors.php:348
-msgid "Close"
+#: mod/admin.php:1804
+msgid "PHP logging"
 msgstr ""
 
-#: mod/admin.php:1663
-msgid "FTP Host"
+#: mod/admin.php:1805
+msgid ""
+"To enable logging of PHP errors and warnings you can add the following to "
+"the .htconfig.php file of your installation. The filename set in the "
+"'error_log' line is relative to the friendica top-level directory and must "
+"be writeable by the web server. The option '1' for 'log_errors' and "
+"'display_errors' is to enable these options, set to '0' to disable them."
 msgstr ""
 
-#: mod/admin.php:1664
-msgid "FTP Path"
+#: mod/admin.php:1931 mod/admin.php:1932 mod/settings.php:759
+msgid "Off"
 msgstr ""
 
-#: mod/admin.php:1665
-msgid "FTP User"
+#: mod/admin.php:1931 mod/admin.php:1932 mod/settings.php:759
+msgid "On"
 msgstr ""
 
-#: mod/admin.php:1666
-msgid "FTP Password"
+#: mod/admin.php:1932
+#, php-format
+msgid "Lock feature %s"
+msgstr ""
+
+#: mod/admin.php:1940
+msgid "Manage Additional Features"
 msgstr ""
 
 #: mod/network.php:146
@@ -3234,7 +3241,7 @@ msgstr ""
 msgid "Remove term"
 msgstr ""
 
-#: mod/network.php:200 mod/search.php:34 include/features.php:79
+#: mod/network.php:200 mod/search.php:34 include/features.php:84
 msgid "Saved Searches"
 msgstr ""
 
@@ -3242,51 +3249,51 @@ msgstr ""
 msgid "add"
 msgstr ""
 
-#: mod/network.php:362
+#: mod/network.php:365
 msgid "Commented Order"
 msgstr ""
 
-#: mod/network.php:365
+#: mod/network.php:368
 msgid "Sort by Comment Date"
 msgstr ""
 
-#: mod/network.php:370
+#: mod/network.php:373
 msgid "Posted Order"
 msgstr ""
 
-#: mod/network.php:373
+#: mod/network.php:376
 msgid "Sort by Post Date"
 msgstr ""
 
-#: mod/network.php:384
+#: mod/network.php:387
 msgid "Posts that mention or involve you"
 msgstr ""
 
-#: mod/network.php:392
+#: mod/network.php:395
 msgid "New"
 msgstr ""
 
-#: mod/network.php:395
+#: mod/network.php:398
 msgid "Activity Stream - by date"
 msgstr ""
 
-#: mod/network.php:403
+#: mod/network.php:406
 msgid "Shared Links"
 msgstr ""
 
-#: mod/network.php:406
+#: mod/network.php:409
 msgid "Interesting Links"
 msgstr ""
 
-#: mod/network.php:414
+#: mod/network.php:417
 msgid "Starred"
 msgstr ""
 
-#: mod/network.php:417
+#: mod/network.php:420
 msgid "Favourite Posts"
 msgstr ""
 
-#: mod/network.php:476
+#: mod/network.php:479
 #, php-format
 msgid "Warning: This group contains %s member from an insecure network."
 msgid_plural ""
@@ -3294,24 +3301,24 @@ msgid_plural ""
 msgstr[0] ""
 msgstr[1] ""
 
-#: mod/network.php:479
+#: mod/network.php:482
 msgid "Private messages to this group are at risk of public disclosure."
 msgstr ""
 
-#: mod/network.php:546 mod/content.php:119
+#: mod/network.php:549 mod/content.php:119
 msgid "No such group"
 msgstr ""
 
-#: mod/network.php:574 mod/content.php:135
+#: mod/network.php:580 mod/content.php:135
 #, php-format
 msgid "Group: %s"
 msgstr ""
 
-#: mod/network.php:606
+#: mod/network.php:608
 msgid "Private messages to this person are at risk of public disclosure."
 msgstr ""
 
-#: mod/network.php:611
+#: mod/network.php:613
 msgid "Invalid contact."
 msgstr ""
 
@@ -3355,11 +3362,11 @@ msgstr ""
 msgid "Sat"
 msgstr ""
 
-#: mod/events.php:208 mod/settings.php:939 include/text.php:1274
+#: mod/events.php:208 mod/settings.php:948 include/text.php:1274
 msgid "Sunday"
 msgstr ""
 
-#: mod/events.php:209 mod/settings.php:939 include/text.php:1274
+#: mod/events.php:209 mod/settings.php:948 include/text.php:1274
 msgid "Monday"
 msgstr ""
 
@@ -3499,7 +3506,7 @@ msgstr ""
 msgid "Edit event"
 msgstr ""
 
-#: mod/events.php:421 include/text.php:1721 include/text.php:1728
+#: mod/events.php:421 include/text.php:1728 include/text.php:1735
 msgid "link to source"
 msgstr ""
 
@@ -3562,7 +3569,7 @@ msgstr ""
 
 #: mod/events.php:572 mod/content.php:721 mod/editpost.php:145
 #: mod/photos.php:1631 mod/photos.php:1679 mod/photos.php:1767
-#: object/Item.php:719 include/conversation.php:1217
+#: object/Item.php:719 include/conversation.php:1216
 msgid "Preview"
 msgstr ""
 
@@ -3606,15 +3613,15 @@ msgstr[0] ""
 msgstr[1] ""
 
 #: mod/content.php:607 object/Item.php:421 object/Item.php:434
-#: include/text.php:1997
+#: include/text.php:2004
 msgid "comment"
 msgid_plural "comments"
 msgstr[0] ""
 msgstr[1] ""
 
-#: mod/content.php:608 boot.php:788 object/Item.php:422
+#: mod/content.php:608 boot.php:870 object/Item.php:422
 #: include/contact_widgets.php:242 include/forums.php:110
-#: include/items.php:5181 view/theme/vier/theme.php:264
+#: include/items.php:5207 view/theme/vier/theme.php:264
 msgid "show more"
 msgstr ""
 
@@ -3652,7 +3659,7 @@ msgid "This is you"
 msgstr ""
 
 #: mod/content.php:711 mod/photos.php:1629 mod/photos.php:1677
-#: mod/photos.php:1765 boot.php:787 object/Item.php:393 object/Item.php:709
+#: mod/photos.php:1765 boot.php:869 object/Item.php:393 object/Item.php:709
 msgid "Comment"
 msgstr ""
 
@@ -3688,7 +3695,7 @@ msgstr ""
 msgid "Video"
 msgstr ""
 
-#: mod/content.php:730 mod/settings.php:712 object/Item.php:122
+#: mod/content.php:730 mod/settings.php:721 object/Item.php:122
 #: object/Item.php:124
 msgid "Edit"
 msgstr ""
@@ -4083,19 +4090,19 @@ msgid ""
 "your site allow private mail from unknown senders."
 msgstr ""
 
-#: mod/help.php:31
+#: mod/help.php:41
 msgid "Help:"
 msgstr ""
 
-#: mod/help.php:36 include/nav.php:113 view/theme/vier/theme.php:302
+#: mod/help.php:47 include/nav.php:113 view/theme/vier/theme.php:302
 msgid "Help"
 msgstr ""
 
-#: mod/help.php:42 mod/p.php:16 mod/p.php:25 index.php:270
+#: mod/help.php:53 mod/p.php:16 mod/p.php:25 index.php:270
 msgid "Not Found"
 msgstr ""
 
-#: mod/help.php:45 index.php:273
+#: mod/help.php:56 index.php:273
 msgid "Page not found."
 msgstr ""
 
@@ -4142,16 +4149,16 @@ msgstr ""
 msgid "link"
 msgstr ""
 
-#: mod/community.php:23
+#: mod/community.php:27
 msgid "Not available."
 msgstr ""
 
-#: mod/community.php:32 include/nav.php:136 include/nav.php:138
+#: mod/community.php:36 include/nav.php:136 include/nav.php:138
 #: view/theme/diabook/theme.php:129
 msgid "Community"
 msgstr ""
 
-#: mod/community.php:62 mod/community.php:71 mod/search.php:228
+#: mod/community.php:66 mod/community.php:75 mod/search.php:228
 msgid "No results."
 msgstr ""
 
@@ -4159,846 +4166,836 @@ msgstr ""
 msgid "everybody"
 msgstr ""
 
-#: mod/settings.php:47
-msgid "Additional features"
-msgstr ""
-
-#: mod/settings.php:53
+#: mod/settings.php:58
 msgid "Display"
 msgstr ""
 
-#: mod/settings.php:60 mod/settings.php:855
+#: mod/settings.php:65 mod/settings.php:864
 msgid "Social Networks"
 msgstr ""
 
-#: mod/settings.php:72 include/nav.php:180
+#: mod/settings.php:79 include/nav.php:180
 msgid "Delegations"
 msgstr ""
 
-#: mod/settings.php:78
+#: mod/settings.php:86
 msgid "Connected apps"
 msgstr ""
 
-#: mod/settings.php:84 mod/uexport.php:85
+#: mod/settings.php:93 mod/uexport.php:85
 msgid "Export personal data"
 msgstr ""
 
-#: mod/settings.php:90
+#: mod/settings.php:100
 msgid "Remove account"
 msgstr ""
 
-#: mod/settings.php:143
+#: mod/settings.php:153
 msgid "Missing some important data!"
 msgstr ""
 
-#: mod/settings.php:256
+#: mod/settings.php:266
 msgid "Failed to connect with email account using the settings provided."
 msgstr ""
 
-#: mod/settings.php:261
+#: mod/settings.php:271
 msgid "Email settings updated."
 msgstr ""
 
-#: mod/settings.php:276
+#: mod/settings.php:286
 msgid "Features updated"
 msgstr ""
 
-#: mod/settings.php:343
+#: mod/settings.php:353
 msgid "Relocate message has been send to your contacts"
 msgstr ""
 
-#: mod/settings.php:357 include/user.php:39
+#: mod/settings.php:367 include/user.php:39
 msgid "Passwords do not match. Password unchanged."
 msgstr ""
 
-#: mod/settings.php:362
+#: mod/settings.php:372
 msgid "Empty passwords are not allowed. Password unchanged."
 msgstr ""
 
-#: mod/settings.php:370
+#: mod/settings.php:380
 msgid "Wrong password."
 msgstr ""
 
-#: mod/settings.php:381
+#: mod/settings.php:391
 msgid "Password changed."
 msgstr ""
 
-#: mod/settings.php:383
+#: mod/settings.php:393
 msgid "Password update failed. Please try again."
 msgstr ""
 
-#: mod/settings.php:452
+#: mod/settings.php:462
 msgid " Please use a shorter name."
 msgstr ""
 
-#: mod/settings.php:454
+#: mod/settings.php:464
 msgid " Name too short."
 msgstr ""
 
-#: mod/settings.php:463
+#: mod/settings.php:473
 msgid "Wrong Password"
 msgstr ""
 
-#: mod/settings.php:468
+#: mod/settings.php:478
 msgid " Not valid email."
 msgstr ""
 
-#: mod/settings.php:474
+#: mod/settings.php:484
 msgid " Cannot change to that email."
 msgstr ""
 
-#: mod/settings.php:530
+#: mod/settings.php:540
 msgid "Private forum has no privacy permissions. Using default privacy group."
 msgstr ""
 
-#: mod/settings.php:534
+#: mod/settings.php:544
 msgid "Private forum has no privacy permissions and no default privacy group."
 msgstr ""
 
-#: mod/settings.php:573
+#: mod/settings.php:583
 msgid "Settings updated."
 msgstr ""
 
-#: mod/settings.php:649 mod/settings.php:675 mod/settings.php:711
+#: mod/settings.php:658 mod/settings.php:684 mod/settings.php:720
 msgid "Add application"
 msgstr ""
 
-#: mod/settings.php:653 mod/settings.php:679
+#: mod/settings.php:662 mod/settings.php:688
 msgid "Consumer Key"
 msgstr ""
 
-#: mod/settings.php:654 mod/settings.php:680
+#: mod/settings.php:663 mod/settings.php:689
 msgid "Consumer Secret"
 msgstr ""
 
-#: mod/settings.php:655 mod/settings.php:681
+#: mod/settings.php:664 mod/settings.php:690
 msgid "Redirect"
 msgstr ""
 
-#: mod/settings.php:656 mod/settings.php:682
+#: mod/settings.php:665 mod/settings.php:691
 msgid "Icon url"
 msgstr ""
 
-#: mod/settings.php:667
+#: mod/settings.php:676
 msgid "You can't edit this application."
 msgstr ""
 
-#: mod/settings.php:710
+#: mod/settings.php:719
 msgid "Connected Apps"
 msgstr ""
 
-#: mod/settings.php:714
+#: mod/settings.php:723
 msgid "Client key starts with"
 msgstr ""
 
-#: mod/settings.php:715
+#: mod/settings.php:724
 msgid "No name"
 msgstr ""
 
-#: mod/settings.php:716
+#: mod/settings.php:725
 msgid "Remove authorization"
 msgstr ""
 
-#: mod/settings.php:728
+#: mod/settings.php:737
 msgid "No Plugin settings configured"
 msgstr ""
 
-#: mod/settings.php:736
+#: mod/settings.php:745
 msgid "Plugin Settings"
 msgstr ""
 
-#: mod/settings.php:750
-msgid "Off"
-msgstr ""
-
-#: mod/settings.php:750
-msgid "On"
-msgstr ""
-
-#: mod/settings.php:758
+#: mod/settings.php:767
 msgid "Additional Features"
 msgstr ""
 
-#: mod/settings.php:768 mod/settings.php:772
+#: mod/settings.php:777 mod/settings.php:781
 msgid "General Social Media Settings"
 msgstr ""
 
-#: mod/settings.php:778
+#: mod/settings.php:787
 msgid "Disable intelligent shortening"
 msgstr ""
 
-#: mod/settings.php:780
+#: mod/settings.php:789
 msgid ""
 "Normally the system tries to find the best link to add to shortened posts. "
 "If this option is enabled then every shortened post will always point to the "
 "original friendica post."
 msgstr ""
 
-#: mod/settings.php:786
+#: mod/settings.php:795
 msgid "Automatically follow any GNU Social (OStatus) followers/mentioners"
 msgstr ""
 
-#: mod/settings.php:788
+#: mod/settings.php:797
 msgid ""
 "If you receive a message from an unknown OStatus user, this option decides "
 "what to do. If it is checked, a new contact will be created for every "
 "unknown user."
 msgstr ""
 
-#: mod/settings.php:797
+#: mod/settings.php:806
 msgid "Your legacy GNU Social account"
 msgstr ""
 
-#: mod/settings.php:799
+#: mod/settings.php:808
 msgid ""
 "If you enter your old GNU Social/Statusnet account name here (in the format "
 "user@domain.tld), your contacts will be added automatically. The field will "
 "be emptied when done."
 msgstr ""
 
-#: mod/settings.php:802
+#: mod/settings.php:811
 msgid "Repair OStatus subscriptions"
 msgstr ""
 
-#: mod/settings.php:811 mod/settings.php:812
+#: mod/settings.php:820 mod/settings.php:821
 #, php-format
 msgid "Built-in support for %s connectivity is %s"
 msgstr ""
 
-#: mod/settings.php:811 mod/dfrn_request.php:858
+#: mod/settings.php:820 mod/dfrn_request.php:865
 #: include/contact_selectors.php:80
 msgid "Diaspora"
 msgstr ""
 
-#: mod/settings.php:811 mod/settings.php:812
+#: mod/settings.php:820 mod/settings.php:821
 msgid "enabled"
 msgstr ""
 
-#: mod/settings.php:811 mod/settings.php:812
+#: mod/settings.php:820 mod/settings.php:821
 msgid "disabled"
 msgstr ""
 
-#: mod/settings.php:812
+#: mod/settings.php:821
 msgid "GNU Social (OStatus)"
 msgstr ""
 
-#: mod/settings.php:848
+#: mod/settings.php:857
 msgid "Email access is disabled on this site."
 msgstr ""
 
-#: mod/settings.php:860
+#: mod/settings.php:869
 msgid "Email/Mailbox Setup"
 msgstr ""
 
-#: mod/settings.php:861
+#: mod/settings.php:870
 msgid ""
 "If you wish to communicate with email contacts using this service "
 "(optional), please specify how to connect to your mailbox."
 msgstr ""
 
-#: mod/settings.php:862
+#: mod/settings.php:871
 msgid "Last successful email check:"
 msgstr ""
 
-#: mod/settings.php:864
+#: mod/settings.php:873
 msgid "IMAP server name:"
 msgstr ""
 
-#: mod/settings.php:865
+#: mod/settings.php:874
 msgid "IMAP port:"
 msgstr ""
 
-#: mod/settings.php:866
+#: mod/settings.php:875
 msgid "Security:"
 msgstr ""
 
-#: mod/settings.php:866 mod/settings.php:871
+#: mod/settings.php:875 mod/settings.php:880
 msgid "None"
 msgstr ""
 
-#: mod/settings.php:867
+#: mod/settings.php:876
 msgid "Email login name:"
 msgstr ""
 
-#: mod/settings.php:868
+#: mod/settings.php:877
 msgid "Email password:"
 msgstr ""
 
-#: mod/settings.php:869
+#: mod/settings.php:878
 msgid "Reply-to address:"
 msgstr ""
 
-#: mod/settings.php:870
+#: mod/settings.php:879
 msgid "Send public posts to all email contacts:"
 msgstr ""
 
-#: mod/settings.php:871
+#: mod/settings.php:880
 msgid "Action after import:"
 msgstr ""
 
-#: mod/settings.php:871
+#: mod/settings.php:880
 msgid "Mark as seen"
 msgstr ""
 
-#: mod/settings.php:871
+#: mod/settings.php:880
 msgid "Move to folder"
 msgstr ""
 
-#: mod/settings.php:872
+#: mod/settings.php:881
 msgid "Move to folder:"
 msgstr ""
 
-#: mod/settings.php:958
+#: mod/settings.php:967
 msgid "Display Settings"
 msgstr ""
 
-#: mod/settings.php:964 mod/settings.php:982
+#: mod/settings.php:973 mod/settings.php:991
 msgid "Display Theme:"
 msgstr ""
 
-#: mod/settings.php:965
+#: mod/settings.php:974
 msgid "Mobile Theme:"
 msgstr ""
 
-#: mod/settings.php:966
+#: mod/settings.php:975
 msgid "Update browser every xx seconds"
 msgstr ""
 
-#: mod/settings.php:966
+#: mod/settings.php:975
 msgid "Minimum of 10 seconds. Enter -1 to disable it."
 msgstr ""
 
-#: mod/settings.php:967
+#: mod/settings.php:976
 msgid "Number of items to display per page:"
 msgstr ""
 
-#: mod/settings.php:967 mod/settings.php:968
+#: mod/settings.php:976 mod/settings.php:977
 msgid "Maximum of 100 items"
 msgstr ""
 
-#: mod/settings.php:968
+#: mod/settings.php:977
 msgid "Number of items to display per page when viewed from mobile device:"
 msgstr ""
 
-#: mod/settings.php:969
+#: mod/settings.php:978
 msgid "Don't show emoticons"
 msgstr ""
 
-#: mod/settings.php:970
+#: mod/settings.php:979
 msgid "Calendar"
 msgstr ""
 
-#: mod/settings.php:971
+#: mod/settings.php:980
 msgid "Beginning of week:"
 msgstr ""
 
-#: mod/settings.php:972
+#: mod/settings.php:981
 msgid "Don't show notices"
 msgstr ""
 
-#: mod/settings.php:973
+#: mod/settings.php:982
 msgid "Infinite scroll"
 msgstr ""
 
-#: mod/settings.php:974
+#: mod/settings.php:983
 msgid "Automatic updates only at the top of the network page"
 msgstr ""
 
-#: mod/settings.php:976 view/theme/cleanzero/config.php:82
+#: mod/settings.php:985 view/theme/cleanzero/config.php:82
 #: view/theme/dispy/config.php:72 view/theme/quattro/config.php:66
-#: view/theme/diabook/config.php:150 view/theme/clean/config.php:85
-#: view/theme/vier/config.php:109 view/theme/duepuntozero/config.php:61
+#: view/theme/diabook/config.php:150 view/theme/vier/config.php:109
+#: view/theme/duepuntozero/config.php:61
 msgid "Theme settings"
 msgstr ""
 
-#: mod/settings.php:1053
+#: mod/settings.php:1062
 msgid "User Types"
 msgstr ""
 
-#: mod/settings.php:1054
+#: mod/settings.php:1063
 msgid "Community Types"
 msgstr ""
 
-#: mod/settings.php:1055
+#: mod/settings.php:1064
 msgid "Normal Account Page"
 msgstr ""
 
-#: mod/settings.php:1056
+#: mod/settings.php:1065
 msgid "This account is a normal personal profile"
 msgstr ""
 
-#: mod/settings.php:1059
+#: mod/settings.php:1068
 msgid "Soapbox Page"
 msgstr ""
 
-#: mod/settings.php:1060
+#: mod/settings.php:1069
 msgid "Automatically approve all connection/friend requests as read-only fans"
 msgstr ""
 
-#: mod/settings.php:1063
+#: mod/settings.php:1072
 msgid "Community Forum/Celebrity Account"
 msgstr ""
 
-#: mod/settings.php:1064
+#: mod/settings.php:1073
 msgid "Automatically approve all connection/friend requests as read-write fans"
 msgstr ""
 
-#: mod/settings.php:1067
+#: mod/settings.php:1076
 msgid "Automatic Friend Page"
 msgstr ""
 
-#: mod/settings.php:1068
+#: mod/settings.php:1077
 msgid "Automatically approve all connection/friend requests as friends"
 msgstr ""
 
-#: mod/settings.php:1071
+#: mod/settings.php:1080
 msgid "Private Forum [Experimental]"
 msgstr ""
 
-#: mod/settings.php:1072
+#: mod/settings.php:1081
 msgid "Private forum - approved members only"
 msgstr ""
 
-#: mod/settings.php:1084
+#: mod/settings.php:1093
 msgid "OpenID:"
 msgstr ""
 
-#: mod/settings.php:1084
+#: mod/settings.php:1093
 msgid "(Optional) Allow this OpenID to login to this account."
 msgstr ""
 
-#: mod/settings.php:1094
+#: mod/settings.php:1103
 msgid "Publish your default profile in your local site directory?"
 msgstr ""
 
-#: mod/settings.php:1100
+#: mod/settings.php:1109
 msgid "Publish your default profile in the global social directory?"
 msgstr ""
 
-#: mod/settings.php:1108
+#: mod/settings.php:1117
 msgid "Hide your contact/friend list from viewers of your default profile?"
 msgstr ""
 
-#: mod/settings.php:1112 include/acl_selectors.php:331
+#: mod/settings.php:1121 include/acl_selectors.php:331
 msgid "Hide your profile details from unknown viewers?"
 msgstr ""
 
-#: mod/settings.php:1112
+#: mod/settings.php:1121
 msgid ""
 "If enabled, posting public messages to Diaspora and other networks isn't "
 "possible."
 msgstr ""
 
-#: mod/settings.php:1117
+#: mod/settings.php:1126
 msgid "Allow friends to post to your profile page?"
 msgstr ""
 
-#: mod/settings.php:1123
+#: mod/settings.php:1132
 msgid "Allow friends to tag your posts?"
 msgstr ""
 
-#: mod/settings.php:1129
+#: mod/settings.php:1138
 msgid "Allow us to suggest you as a potential friend to new members?"
 msgstr ""
 
-#: mod/settings.php:1135
+#: mod/settings.php:1144
 msgid "Permit unknown people to send you private mail?"
 msgstr ""
 
-#: mod/settings.php:1143
+#: mod/settings.php:1152
 msgid "Profile is <strong>not published</strong>."
 msgstr ""
 
-#: mod/settings.php:1151
+#: mod/settings.php:1160
 #, php-format
 msgid "Your Identity Address is <strong>'%s'</strong> or '%s'."
 msgstr ""
 
-#: mod/settings.php:1158
+#: mod/settings.php:1167
 msgid "Automatically expire posts after this many days:"
 msgstr ""
 
-#: mod/settings.php:1158
+#: mod/settings.php:1167
 msgid "If empty, posts will not expire. Expired posts will be deleted"
 msgstr ""
 
-#: mod/settings.php:1159
+#: mod/settings.php:1168
 msgid "Advanced expiration settings"
 msgstr ""
 
-#: mod/settings.php:1160
+#: mod/settings.php:1169
 msgid "Advanced Expiration"
 msgstr ""
 
-#: mod/settings.php:1161
+#: mod/settings.php:1170
 msgid "Expire posts:"
 msgstr ""
 
-#: mod/settings.php:1162
+#: mod/settings.php:1171
 msgid "Expire personal notes:"
 msgstr ""
 
-#: mod/settings.php:1163
+#: mod/settings.php:1172
 msgid "Expire starred posts:"
 msgstr ""
 
-#: mod/settings.php:1164
+#: mod/settings.php:1173
 msgid "Expire photos:"
 msgstr ""
 
-#: mod/settings.php:1165
+#: mod/settings.php:1174
 msgid "Only expire posts by others:"
 msgstr ""
 
-#: mod/settings.php:1193
+#: mod/settings.php:1202
 msgid "Account Settings"
 msgstr ""
 
-#: mod/settings.php:1201
+#: mod/settings.php:1210
 msgid "Password Settings"
 msgstr ""
 
-#: mod/settings.php:1202 mod/register.php:274
+#: mod/settings.php:1211 mod/register.php:274
 msgid "New Password:"
 msgstr ""
 
-#: mod/settings.php:1203 mod/register.php:275
+#: mod/settings.php:1212 mod/register.php:275
 msgid "Confirm:"
 msgstr ""
 
-#: mod/settings.php:1203
+#: mod/settings.php:1212
 msgid "Leave password fields blank unless changing"
 msgstr ""
 
-#: mod/settings.php:1204
+#: mod/settings.php:1213
 msgid "Current Password:"
 msgstr ""
 
-#: mod/settings.php:1204 mod/settings.php:1205
+#: mod/settings.php:1213 mod/settings.php:1214
 msgid "Your current password to confirm the changes"
 msgstr ""
 
-#: mod/settings.php:1205
+#: mod/settings.php:1214
 msgid "Password:"
 msgstr ""
 
-#: mod/settings.php:1209
+#: mod/settings.php:1218
 msgid "Basic Settings"
 msgstr ""
 
-#: mod/settings.php:1210 include/identity.php:587
+#: mod/settings.php:1219 include/identity.php:588
 msgid "Full Name:"
 msgstr ""
 
-#: mod/settings.php:1211
+#: mod/settings.php:1220
 msgid "Email Address:"
 msgstr ""
 
-#: mod/settings.php:1212
+#: mod/settings.php:1221
 msgid "Your Timezone:"
 msgstr ""
 
-#: mod/settings.php:1213
+#: mod/settings.php:1222
 msgid "Your Language:"
 msgstr ""
 
-#: mod/settings.php:1213
+#: mod/settings.php:1222
 msgid ""
 "Set the language we use to show you friendica interface and to send you "
 "emails"
 msgstr ""
 
-#: mod/settings.php:1214
+#: mod/settings.php:1223
 msgid "Default Post Location:"
 msgstr ""
 
-#: mod/settings.php:1215
+#: mod/settings.php:1224
 msgid "Use Browser Location:"
 msgstr ""
 
-#: mod/settings.php:1218
+#: mod/settings.php:1227
 msgid "Security and Privacy Settings"
 msgstr ""
 
-#: mod/settings.php:1220
+#: mod/settings.php:1229
 msgid "Maximum Friend Requests/Day:"
 msgstr ""
 
-#: mod/settings.php:1220 mod/settings.php:1250
+#: mod/settings.php:1229 mod/settings.php:1259
 msgid "(to prevent spam abuse)"
 msgstr ""
 
-#: mod/settings.php:1221
+#: mod/settings.php:1230
 msgid "Default Post Permissions"
 msgstr ""
 
-#: mod/settings.php:1222
+#: mod/settings.php:1231
 msgid "(click to open/close)"
 msgstr ""
 
-#: mod/settings.php:1231 mod/photos.php:1199 mod/photos.php:1584
+#: mod/settings.php:1240 mod/photos.php:1199 mod/photos.php:1584
 msgid "Show to Groups"
 msgstr ""
 
-#: mod/settings.php:1232 mod/photos.php:1200 mod/photos.php:1585
+#: mod/settings.php:1241 mod/photos.php:1200 mod/photos.php:1585
 msgid "Show to Contacts"
 msgstr ""
 
-#: mod/settings.php:1233
+#: mod/settings.php:1242
 msgid "Default Private Post"
 msgstr ""
 
-#: mod/settings.php:1234
+#: mod/settings.php:1243
 msgid "Default Public Post"
 msgstr ""
 
-#: mod/settings.php:1238
+#: mod/settings.php:1247
 msgid "Default Permissions for New Posts"
 msgstr ""
 
-#: mod/settings.php:1250
+#: mod/settings.php:1259
 msgid "Maximum private messages per day from unknown people:"
 msgstr ""
 
-#: mod/settings.php:1253
+#: mod/settings.php:1262
 msgid "Notification Settings"
 msgstr ""
 
-#: mod/settings.php:1254
+#: mod/settings.php:1263
 msgid "By default post a status message when:"
 msgstr ""
 
-#: mod/settings.php:1255
+#: mod/settings.php:1264
 msgid "accepting a friend request"
 msgstr ""
 
-#: mod/settings.php:1256
+#: mod/settings.php:1265
 msgid "joining a forum/community"
 msgstr ""
 
-#: mod/settings.php:1257
+#: mod/settings.php:1266
 msgid "making an <em>interesting</em> profile change"
 msgstr ""
 
-#: mod/settings.php:1258
+#: mod/settings.php:1267
 msgid "Send a notification email when:"
 msgstr ""
 
-#: mod/settings.php:1259
+#: mod/settings.php:1268
 msgid "You receive an introduction"
 msgstr ""
 
-#: mod/settings.php:1260
+#: mod/settings.php:1269
 msgid "Your introductions are confirmed"
 msgstr ""
 
-#: mod/settings.php:1261
+#: mod/settings.php:1270
 msgid "Someone writes on your profile wall"
 msgstr ""
 
-#: mod/settings.php:1262
+#: mod/settings.php:1271
 msgid "Someone writes a followup comment"
 msgstr ""
 
-#: mod/settings.php:1263
+#: mod/settings.php:1272
 msgid "You receive a private message"
 msgstr ""
 
-#: mod/settings.php:1264
+#: mod/settings.php:1273
 msgid "You receive a friend suggestion"
 msgstr ""
 
-#: mod/settings.php:1265
+#: mod/settings.php:1274
 msgid "You are tagged in a post"
 msgstr ""
 
-#: mod/settings.php:1266
+#: mod/settings.php:1275
 msgid "You are poked/prodded/etc. in a post"
 msgstr ""
 
-#: mod/settings.php:1268
+#: mod/settings.php:1277
 msgid "Activate desktop notifications"
 msgstr ""
 
-#: mod/settings.php:1268
+#: mod/settings.php:1277
 msgid "Show desktop popup on new notifications"
 msgstr ""
 
-#: mod/settings.php:1270
+#: mod/settings.php:1279
 msgid "Text-only notification emails"
 msgstr ""
 
-#: mod/settings.php:1272
+#: mod/settings.php:1281
 msgid "Send text only notification emails, without the html part"
 msgstr ""
 
-#: mod/settings.php:1274
+#: mod/settings.php:1283
 msgid "Advanced Account/Page Type Settings"
 msgstr ""
 
-#: mod/settings.php:1275
+#: mod/settings.php:1284
 msgid "Change the behaviour of this account for special situations"
 msgstr ""
 
-#: mod/settings.php:1278
+#: mod/settings.php:1287
 msgid "Relocate"
 msgstr ""
 
-#: mod/settings.php:1279
+#: mod/settings.php:1288
 msgid ""
 "If you have moved this profile from another server, and some of your "
 "contacts don't receive your updates, try pushing this button."
 msgstr ""
 
-#: mod/settings.php:1280
+#: mod/settings.php:1289
 msgid "Resend relocate message to contacts"
 msgstr ""
 
-#: mod/dfrn_request.php:95
+#: mod/dfrn_request.php:96
 msgid "This introduction has already been accepted."
 msgstr ""
 
-#: mod/dfrn_request.php:120 mod/dfrn_request.php:519
+#: mod/dfrn_request.php:119 mod/dfrn_request.php:516
 msgid "Profile location is not valid or does not contain profile information."
 msgstr ""
 
-#: mod/dfrn_request.php:125 mod/dfrn_request.php:524
+#: mod/dfrn_request.php:124 mod/dfrn_request.php:521
 msgid "Warning: profile location has no identifiable owner name."
 msgstr ""
 
-#: mod/dfrn_request.php:127 mod/dfrn_request.php:526
+#: mod/dfrn_request.php:126 mod/dfrn_request.php:523
 msgid "Warning: profile location has no profile photo."
 msgstr ""
 
-#: mod/dfrn_request.php:130 mod/dfrn_request.php:529
+#: mod/dfrn_request.php:129 mod/dfrn_request.php:526
 #, php-format
 msgid "%d required parameter was not found at the given location"
 msgid_plural "%d required parameters were not found at the given location"
 msgstr[0] ""
 msgstr[1] ""
 
-#: mod/dfrn_request.php:173
+#: mod/dfrn_request.php:172
 msgid "Introduction complete."
 msgstr ""
 
-#: mod/dfrn_request.php:215
+#: mod/dfrn_request.php:214
 msgid "Unrecoverable protocol error."
 msgstr ""
 
-#: mod/dfrn_request.php:243
+#: mod/dfrn_request.php:242
 msgid "Profile unavailable."
 msgstr ""
 
-#: mod/dfrn_request.php:268
+#: mod/dfrn_request.php:267
 #, php-format
 msgid "%s has received too many connection requests today."
 msgstr ""
 
-#: mod/dfrn_request.php:269
+#: mod/dfrn_request.php:268
 msgid "Spam protection measures have been invoked."
 msgstr ""
 
-#: mod/dfrn_request.php:270
+#: mod/dfrn_request.php:269
 msgid "Friends are advised to please try again in 24 hours."
 msgstr ""
 
-#: mod/dfrn_request.php:332
+#: mod/dfrn_request.php:331
 msgid "Invalid locator"
 msgstr ""
 
-#: mod/dfrn_request.php:341
+#: mod/dfrn_request.php:340
 msgid "Invalid email address."
 msgstr ""
 
-#: mod/dfrn_request.php:368
+#: mod/dfrn_request.php:367
 msgid "This account has not been configured for email. Request failed."
 msgstr ""
 
-#: mod/dfrn_request.php:464
-msgid "Unable to resolve your name at the provided location."
-msgstr ""
-
-#: mod/dfrn_request.php:477
+#: mod/dfrn_request.php:474
 msgid "You have already introduced yourself here."
 msgstr ""
 
-#: mod/dfrn_request.php:481
+#: mod/dfrn_request.php:478
 #, php-format
 msgid "Apparently you are already friends with %s."
 msgstr ""
 
-#: mod/dfrn_request.php:502
+#: mod/dfrn_request.php:499
 msgid "Invalid profile URL."
 msgstr ""
 
-#: mod/dfrn_request.php:508 include/follow.php:72
+#: mod/dfrn_request.php:505 include/follow.php:72
 msgid "Disallowed profile URL."
 msgstr ""
 
-#: mod/dfrn_request.php:599
+#: mod/dfrn_request.php:596
 msgid "Your introduction has been sent."
 msgstr ""
 
-#: mod/dfrn_request.php:652
+#: mod/dfrn_request.php:636
+msgid ""
+"Remote subscription can't be done for your network. Please subscribe "
+"directly on your system."
+msgstr ""
+
+#: mod/dfrn_request.php:659
 msgid "Please login to confirm introduction."
 msgstr ""
 
-#: mod/dfrn_request.php:662
+#: mod/dfrn_request.php:669
 msgid ""
 "Incorrect identity currently logged in. Please login to <strong>this</"
 "strong> profile."
 msgstr ""
 
-#: mod/dfrn_request.php:676 mod/dfrn_request.php:693
+#: mod/dfrn_request.php:683 mod/dfrn_request.php:700
 msgid "Confirm"
 msgstr ""
 
-#: mod/dfrn_request.php:688
+#: mod/dfrn_request.php:695
 msgid "Hide this contact"
 msgstr ""
 
-#: mod/dfrn_request.php:691
+#: mod/dfrn_request.php:698
 #, php-format
 msgid "Welcome home %s."
 msgstr ""
 
-#: mod/dfrn_request.php:692
+#: mod/dfrn_request.php:699
 #, php-format
 msgid "Please confirm your introduction/connection request to %s."
 msgstr ""
 
-#: mod/dfrn_request.php:821
+#: mod/dfrn_request.php:828
 msgid ""
 "Please enter your 'Identity Address' from one of the following supported "
 "communications networks:"
 msgstr ""
 
-#: mod/dfrn_request.php:842
+#: mod/dfrn_request.php:849
 #, php-format
 msgid ""
 "If you are not yet a member of the free social web, <a href=\"%s/siteinfo"
 "\">follow this link to find a public Friendica site and join us today</a>."
 msgstr ""
 
-#: mod/dfrn_request.php:847
+#: mod/dfrn_request.php:854
 msgid "Friend/Connection Request"
 msgstr ""
 
-#: mod/dfrn_request.php:848
+#: mod/dfrn_request.php:855
 msgid ""
 "Examples: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, "
 "testuser@identi.ca"
 msgstr ""
 
-#: mod/dfrn_request.php:856 include/contact_selectors.php:76
+#: mod/dfrn_request.php:863 include/contact_selectors.php:76
 msgid "Friendica"
 msgstr ""
 
-#: mod/dfrn_request.php:857
+#: mod/dfrn_request.php:864
 msgid "StatusNet/Federated Social Web"
 msgstr ""
 
-#: mod/dfrn_request.php:859
+#: mod/dfrn_request.php:866
 #, php-format
 msgid ""
 " - please do not use this form.  Instead, enter %s into your Diaspora search "
@@ -5086,7 +5083,7 @@ msgstr ""
 msgid "Choose a nickname: "
 msgstr ""
 
-#: mod/register.php:280 boot.php:1271 include/nav.php:108
+#: mod/register.php:280 boot.php:1405 include/nav.php:108
 msgid "Register"
 msgstr ""
 
@@ -5128,11 +5125,11 @@ msgstr ""
 msgid "Search results for: %s"
 msgstr ""
 
-#: mod/directory.php:149 include/identity.php:309 include/identity.php:609
+#: mod/directory.php:149 include/identity.php:313 include/identity.php:610
 msgid "Status:"
 msgstr ""
 
-#: mod/directory.php:151 include/identity.php:311 include/identity.php:620
+#: mod/directory.php:151 include/identity.php:315 include/identity.php:621
 msgid "Homepage:"
 msgstr ""
 
@@ -5460,7 +5457,7 @@ msgstr ""
 msgid "Since [date]:"
 msgstr ""
 
-#: mod/profiles.php:724 include/identity.php:618
+#: mod/profiles.php:724 include/identity.php:619
 msgid "Sexual Preference:"
 msgstr ""
 
@@ -5468,11 +5465,11 @@ msgstr ""
 msgid "Homepage URL:"
 msgstr ""
 
-#: mod/profiles.php:726 include/identity.php:622
+#: mod/profiles.php:726 include/identity.php:623
 msgid "Hometown:"
 msgstr ""
 
-#: mod/profiles.php:727 include/identity.php:626
+#: mod/profiles.php:727 include/identity.php:627
 msgid "Political Views:"
 msgstr ""
 
@@ -5488,11 +5485,11 @@ msgstr ""
 msgid "Private Keywords:"
 msgstr ""
 
-#: mod/profiles.php:731 include/identity.php:634
+#: mod/profiles.php:731 include/identity.php:635
 msgid "Likes:"
 msgstr ""
 
-#: mod/profiles.php:732 include/identity.php:636
+#: mod/profiles.php:732 include/identity.php:637
 msgid "Dislikes:"
 msgstr ""
 
@@ -5562,23 +5559,23 @@ msgstr ""
 msgid "Edit/Manage Profiles"
 msgstr ""
 
-#: mod/profiles.php:814 include/identity.php:257 include/identity.php:283
+#: mod/profiles.php:814 include/identity.php:260 include/identity.php:286
 msgid "Change profile photo"
 msgstr ""
 
-#: mod/profiles.php:815 include/identity.php:258
+#: mod/profiles.php:815 include/identity.php:261
 msgid "Create New Profile"
 msgstr ""
 
-#: mod/profiles.php:826 include/identity.php:268
+#: mod/profiles.php:826 include/identity.php:271
 msgid "Profile Image"
 msgstr ""
 
-#: mod/profiles.php:828 include/identity.php:271
+#: mod/profiles.php:828 include/identity.php:274
 msgid "visible to everybody"
 msgstr ""
 
-#: mod/profiles.php:829 include/identity.php:272
+#: mod/profiles.php:829 include/identity.php:275
 msgid "Edit visibility"
 msgstr ""
 
@@ -5590,55 +5587,55 @@ msgstr ""
 msgid "Edit post"
 msgstr ""
 
-#: mod/editpost.php:111 include/conversation.php:1185
+#: mod/editpost.php:111 include/conversation.php:1184
 msgid "upload photo"
 msgstr ""
 
-#: mod/editpost.php:112 include/conversation.php:1186
+#: mod/editpost.php:112 include/conversation.php:1185
 msgid "Attach file"
 msgstr ""
 
-#: mod/editpost.php:113 include/conversation.php:1187
+#: mod/editpost.php:113 include/conversation.php:1186
 msgid "attach file"
 msgstr ""
 
-#: mod/editpost.php:115 include/conversation.php:1189
+#: mod/editpost.php:115 include/conversation.php:1188
 msgid "web link"
 msgstr ""
 
-#: mod/editpost.php:116 include/conversation.php:1190
+#: mod/editpost.php:116 include/conversation.php:1189
 msgid "Insert video link"
 msgstr ""
 
-#: mod/editpost.php:117 include/conversation.php:1191
+#: mod/editpost.php:117 include/conversation.php:1190
 msgid "video link"
 msgstr ""
 
-#: mod/editpost.php:118 include/conversation.php:1192
+#: mod/editpost.php:118 include/conversation.php:1191
 msgid "Insert audio link"
 msgstr ""
 
-#: mod/editpost.php:119 include/conversation.php:1193
+#: mod/editpost.php:119 include/conversation.php:1192
 msgid "audio link"
 msgstr ""
 
-#: mod/editpost.php:120 include/conversation.php:1194
+#: mod/editpost.php:120 include/conversation.php:1193
 msgid "Set your location"
 msgstr ""
 
-#: mod/editpost.php:121 include/conversation.php:1195
+#: mod/editpost.php:121 include/conversation.php:1194
 msgid "set location"
 msgstr ""
 
-#: mod/editpost.php:122 include/conversation.php:1196
+#: mod/editpost.php:122 include/conversation.php:1195
 msgid "Clear browser location"
 msgstr ""
 
-#: mod/editpost.php:123 include/conversation.php:1197
+#: mod/editpost.php:123 include/conversation.php:1196
 msgid "clear location"
 msgstr ""
 
-#: mod/editpost.php:125 include/conversation.php:1203
+#: mod/editpost.php:125 include/conversation.php:1202
 msgid "Permission settings"
 msgstr ""
 
@@ -5646,15 +5643,15 @@ msgstr ""
 msgid "CC: email addresses"
 msgstr ""
 
-#: mod/editpost.php:134 include/conversation.php:1212
+#: mod/editpost.php:134 include/conversation.php:1211
 msgid "Public post"
 msgstr ""
 
-#: mod/editpost.php:137 include/conversation.php:1199
+#: mod/editpost.php:137 include/conversation.php:1198
 msgid "Set title"
 msgstr ""
 
-#: mod/editpost.php:139 include/conversation.php:1201
+#: mod/editpost.php:139 include/conversation.php:1200
 msgid "Categories (comma-separated list)"
 msgstr ""
 
@@ -5662,39 +5659,39 @@ msgstr ""
 msgid "Example: bob@example.com, mary@example.com"
 msgstr ""
 
-#: mod/friendica.php:59
+#: mod/friendica.php:70
 msgid "This is Friendica, version"
 msgstr ""
 
-#: mod/friendica.php:60
+#: mod/friendica.php:71
 msgid "running at web location"
 msgstr ""
 
-#: mod/friendica.php:62
+#: mod/friendica.php:73
 msgid ""
 "Please visit <a href=\"http://friendica.com\">Friendica.com</a> to learn "
 "more about the Friendica project."
 msgstr ""
 
-#: mod/friendica.php:64
+#: mod/friendica.php:75
 msgid "Bug reports and issues: please visit"
 msgstr ""
 
-#: mod/friendica.php:64
+#: mod/friendica.php:75
 msgid "the bugtracker at github"
 msgstr ""
 
-#: mod/friendica.php:65
+#: mod/friendica.php:76
 msgid ""
 "Suggestions, praise, donations, etc. - please email \"Info\" at Friendica - "
 "dot com"
 msgstr ""
 
-#: mod/friendica.php:79
+#: mod/friendica.php:90
 msgid "Installed plugins/addons/apps:"
 msgstr ""
 
-#: mod/friendica.php:92
+#: mod/friendica.php:103
 msgid "No installed plugins/addons/apps"
 msgstr ""
 
@@ -6051,12 +6048,12 @@ msgstr ""
 msgid "Public photo"
 msgstr ""
 
-#: mod/photos.php:1609 include/conversation.php:1183
+#: mod/photos.php:1609 include/conversation.php:1182
 msgid "Share"
 msgstr ""
 
 #: mod/photos.php:1648 include/conversation.php:509
-#: include/conversation.php:1414
+#: include/conversation.php:1413
 msgid "Attending"
 msgid_plural "Attending"
 msgstr[0] ""
@@ -6130,60 +6127,60 @@ msgstr ""
 msgid "Item was not found."
 msgstr ""
 
-#: boot.php:786
+#: boot.php:868
 msgid "Delete this item?"
 msgstr ""
 
-#: boot.php:789
+#: boot.php:871
 msgid "show fewer"
 msgstr ""
 
-#: boot.php:1163
+#: boot.php:1292
 #, php-format
 msgid "Update %s failed. See error logs."
 msgstr ""
 
-#: boot.php:1270
+#: boot.php:1404
 msgid "Create a New Account"
 msgstr ""
 
-#: boot.php:1295 include/nav.php:72
+#: boot.php:1429 include/nav.php:72
 msgid "Logout"
 msgstr ""
 
-#: boot.php:1298
+#: boot.php:1432
 msgid "Nickname or Email address: "
 msgstr ""
 
-#: boot.php:1299
+#: boot.php:1433
 msgid "Password: "
 msgstr ""
 
-#: boot.php:1300
+#: boot.php:1434
 msgid "Remember me"
 msgstr ""
 
-#: boot.php:1303
+#: boot.php:1437
 msgid "Or login using OpenID: "
 msgstr ""
 
-#: boot.php:1309
+#: boot.php:1443
 msgid "Forgot your password?"
 msgstr ""
 
-#: boot.php:1312
+#: boot.php:1446
 msgid "Website Terms of Service"
 msgstr ""
 
-#: boot.php:1313
+#: boot.php:1447
 msgid "terms of service"
 msgstr ""
 
-#: boot.php:1315
+#: boot.php:1449
 msgid "Website Privacy Policy"
 msgstr ""
 
-#: boot.php:1316
+#: boot.php:1450
 msgid "privacy policy"
 msgstr ""
 
@@ -6245,25 +6242,25 @@ msgid ""
 "[pre]%s[/pre]"
 msgstr ""
 
-#: include/dbstructure.php:151
+#: include/dbstructure.php:153
 msgid "Errors encountered creating database tables."
 msgstr ""
 
-#: include/dbstructure.php:209
+#: include/dbstructure.php:230
 msgid "Errors encountered performing database changes."
 msgstr ""
 
-#: include/auth.php:38
+#: include/auth.php:44
 msgid "Logged out."
 msgstr ""
 
-#: include/auth.php:128 include/user.php:75
+#: include/auth.php:134 include/user.php:75
 msgid ""
 "We encountered a problem while logging in with the OpenID you provided. "
 "Please check the correct spelling of the ID."
 msgstr ""
 
-#: include/auth.php:128 include/user.php:75
+#: include/auth.php:134 include/user.php:75
 msgid "The error message was:"
 msgstr ""
 
@@ -6320,7 +6317,7 @@ msgstr ""
 msgid "All Networks"
 msgstr ""
 
-#: include/contact_widgets.php:141 include/features.php:97
+#: include/contact_widgets.php:141 include/features.php:102
 msgid "Saved Folders"
 msgstr ""
 
@@ -6339,194 +6336,194 @@ msgid_plural "%d contacts in common"
 msgstr[0] ""
 msgstr[1] ""
 
-#: include/features.php:58
+#: include/features.php:63
 msgid "General Features"
 msgstr ""
 
-#: include/features.php:60
+#: include/features.php:65
 msgid "Multiple Profiles"
 msgstr ""
 
-#: include/features.php:60
+#: include/features.php:65
 msgid "Ability to create multiple profiles"
 msgstr ""
 
-#: include/features.php:61
+#: include/features.php:66
 msgid "Photo Location"
 msgstr ""
 
-#: include/features.php:61
+#: include/features.php:66
 msgid ""
 "Photo metadata is normally stripped. This extracts the location (if present) "
 "prior to stripping metadata and links it to a map."
 msgstr ""
 
-#: include/features.php:66
+#: include/features.php:71
 msgid "Post Composition Features"
 msgstr ""
 
-#: include/features.php:67
+#: include/features.php:72
 msgid "Richtext Editor"
 msgstr ""
 
-#: include/features.php:67
+#: include/features.php:72
 msgid "Enable richtext editor"
 msgstr ""
 
-#: include/features.php:68
+#: include/features.php:73
 msgid "Post Preview"
 msgstr ""
 
-#: include/features.php:68
+#: include/features.php:73
 msgid "Allow previewing posts and comments before publishing them"
 msgstr ""
 
-#: include/features.php:69
+#: include/features.php:74
 msgid "Auto-mention Forums"
 msgstr ""
 
-#: include/features.php:69
+#: include/features.php:74
 msgid ""
 "Add/remove mention when a fourm page is selected/deselected in ACL window."
 msgstr ""
 
-#: include/features.php:74
+#: include/features.php:79
 msgid "Network Sidebar Widgets"
 msgstr ""
 
-#: include/features.php:75
+#: include/features.php:80
 msgid "Search by Date"
 msgstr ""
 
-#: include/features.php:75
+#: include/features.php:80
 msgid "Ability to select posts by date ranges"
 msgstr ""
 
-#: include/features.php:76 include/features.php:106
+#: include/features.php:81 include/features.php:111
 msgid "List Forums"
 msgstr ""
 
-#: include/features.php:76
+#: include/features.php:81
 msgid "Enable widget to display the forums your are connected with"
 msgstr ""
 
-#: include/features.php:77
+#: include/features.php:82
 msgid "Group Filter"
 msgstr ""
 
-#: include/features.php:77
+#: include/features.php:82
 msgid "Enable widget to display Network posts only from selected group"
 msgstr ""
 
-#: include/features.php:78
+#: include/features.php:83
 msgid "Network Filter"
 msgstr ""
 
-#: include/features.php:78
+#: include/features.php:83
 msgid "Enable widget to display Network posts only from selected network"
 msgstr ""
 
-#: include/features.php:79
+#: include/features.php:84
 msgid "Save search terms for re-use"
 msgstr ""
 
-#: include/features.php:84
+#: include/features.php:89
 msgid "Network Tabs"
 msgstr ""
 
-#: include/features.php:85
+#: include/features.php:90
 msgid "Network Personal Tab"
 msgstr ""
 
-#: include/features.php:85
+#: include/features.php:90
 msgid "Enable tab to display only Network posts that you've interacted on"
 msgstr ""
 
-#: include/features.php:86
+#: include/features.php:91
 msgid "Network New Tab"
 msgstr ""
 
-#: include/features.php:86
+#: include/features.php:91
 msgid "Enable tab to display only new Network posts (from the last 12 hours)"
 msgstr ""
 
-#: include/features.php:87
+#: include/features.php:92
 msgid "Network Shared Links Tab"
 msgstr ""
 
-#: include/features.php:87
+#: include/features.php:92
 msgid "Enable tab to display only Network posts with links in them"
 msgstr ""
 
-#: include/features.php:92
+#: include/features.php:97
 msgid "Post/Comment Tools"
 msgstr ""
 
-#: include/features.php:93
+#: include/features.php:98
 msgid "Multiple Deletion"
 msgstr ""
 
-#: include/features.php:93
+#: include/features.php:98
 msgid "Select and delete multiple posts/comments at once"
 msgstr ""
 
-#: include/features.php:94
+#: include/features.php:99
 msgid "Edit Sent Posts"
 msgstr ""
 
-#: include/features.php:94
+#: include/features.php:99
 msgid "Edit and correct posts and comments after sending"
 msgstr ""
 
-#: include/features.php:95
+#: include/features.php:100
 msgid "Tagging"
 msgstr ""
 
-#: include/features.php:95
+#: include/features.php:100
 msgid "Ability to tag existing posts"
 msgstr ""
 
-#: include/features.php:96
+#: include/features.php:101
 msgid "Post Categories"
 msgstr ""
 
-#: include/features.php:96
+#: include/features.php:101
 msgid "Add categories to your posts"
 msgstr ""
 
-#: include/features.php:97
+#: include/features.php:102
 msgid "Ability to file posts under folders"
 msgstr ""
 
-#: include/features.php:98
+#: include/features.php:103
 msgid "Dislike Posts"
 msgstr ""
 
-#: include/features.php:98
+#: include/features.php:103
 msgid "Ability to dislike posts/comments"
 msgstr ""
 
-#: include/features.php:99
+#: include/features.php:104
 msgid "Star Posts"
 msgstr ""
 
-#: include/features.php:99
+#: include/features.php:104
 msgid "Ability to mark special posts with a star indicator"
 msgstr ""
 
-#: include/features.php:100
+#: include/features.php:105
 msgid "Mute Post Notifications"
 msgstr ""
 
-#: include/features.php:100
+#: include/features.php:105
 msgid "Ability to mute notifications for a thread"
 msgstr ""
 
-#: include/features.php:105
+#: include/features.php:110
 msgid "Advanced Profile Settings"
 msgstr ""
 
-#: include/features.php:106
+#: include/features.php:111
 msgid "Show visitors public community forums at the Advanced Profile Page"
 msgstr ""
 
@@ -6685,134 +6682,134 @@ msgstr ""
 msgid "%1$d %2$s ago"
 msgstr ""
 
-#: include/datetime.php:474 include/items.php:2473
+#: include/datetime.php:474 include/items.php:2500
 #, php-format
 msgid "%s's birthday"
 msgstr ""
 
-#: include/datetime.php:475 include/items.php:2474
+#: include/datetime.php:475 include/items.php:2501
 #, php-format
 msgid "Happy Birthday %s"
 msgstr ""
 
-#: include/identity.php:44
+#: include/identity.php:42
 msgid "Requested account is not available."
 msgstr ""
 
-#: include/identity.php:97 include/identity.php:281 include/identity.php:661
+#: include/identity.php:95 include/identity.php:284 include/identity.php:662
 msgid "Edit profile"
 msgstr ""
 
-#: include/identity.php:241
+#: include/identity.php:244
 msgid "Atom feed"
 msgstr ""
 
-#: include/identity.php:246
+#: include/identity.php:249
 msgid "Message"
 msgstr ""
 
-#: include/identity.php:252 include/nav.php:185
+#: include/identity.php:255 include/nav.php:185
 msgid "Profiles"
 msgstr ""
 
-#: include/identity.php:252
+#: include/identity.php:255
 msgid "Manage/edit profiles"
 msgstr ""
 
-#: include/identity.php:421 include/identity.php:507
+#: include/identity.php:425 include/identity.php:509
 msgid "g A l F d"
 msgstr ""
 
-#: include/identity.php:422 include/identity.php:508
+#: include/identity.php:426 include/identity.php:510
 msgid "F d"
 msgstr ""
 
-#: include/identity.php:467 include/identity.php:554
+#: include/identity.php:471 include/identity.php:556
 msgid "[today]"
 msgstr ""
 
-#: include/identity.php:479
+#: include/identity.php:483
 msgid "Birthday Reminders"
 msgstr ""
 
-#: include/identity.php:480
+#: include/identity.php:484
 msgid "Birthdays this week:"
 msgstr ""
 
-#: include/identity.php:541
+#: include/identity.php:543
 msgid "[No description]"
 msgstr ""
 
-#: include/identity.php:565
+#: include/identity.php:567
 msgid "Event Reminders"
 msgstr ""
 
-#: include/identity.php:566
+#: include/identity.php:568
 msgid "Events this week:"
 msgstr ""
 
-#: include/identity.php:594
+#: include/identity.php:595
 msgid "j F, Y"
 msgstr ""
 
-#: include/identity.php:595
+#: include/identity.php:596
 msgid "j F"
 msgstr ""
 
-#: include/identity.php:602
+#: include/identity.php:603
 msgid "Birthday:"
 msgstr ""
 
-#: include/identity.php:606
+#: include/identity.php:607
 msgid "Age:"
 msgstr ""
 
-#: include/identity.php:615
+#: include/identity.php:616
 #, php-format
 msgid "for %1$d %2$s"
 msgstr ""
 
-#: include/identity.php:628
+#: include/identity.php:629
 msgid "Religion:"
 msgstr ""
 
-#: include/identity.php:632
+#: include/identity.php:633
 msgid "Hobbies/Interests:"
 msgstr ""
 
-#: include/identity.php:639
+#: include/identity.php:640
 msgid "Contact information and Social Networks:"
 msgstr ""
 
-#: include/identity.php:641
+#: include/identity.php:642
 msgid "Musical interests:"
 msgstr ""
 
-#: include/identity.php:643
+#: include/identity.php:644
 msgid "Books, literature:"
 msgstr ""
 
-#: include/identity.php:645
+#: include/identity.php:646
 msgid "Television:"
 msgstr ""
 
-#: include/identity.php:647
+#: include/identity.php:648
 msgid "Film/dance/culture/entertainment:"
 msgstr ""
 
-#: include/identity.php:649
+#: include/identity.php:650
 msgid "Love/Romance:"
 msgstr ""
 
-#: include/identity.php:651
+#: include/identity.php:652
 msgid "Work/employment:"
 msgstr ""
 
-#: include/identity.php:653
+#: include/identity.php:654
 msgid "School/education:"
 msgstr ""
 
-#: include/identity.php:657
+#: include/identity.php:658
 msgid "Forums:"
 msgstr ""
 
@@ -6828,6 +6825,38 @@ msgstr ""
 msgid "Only You Can See This"
 msgstr ""
 
+#: include/like.php:167 include/conversation.php:122
+#: include/conversation.php:258 include/text.php:1998
+#: view/theme/diabook/theme.php:463
+msgid "event"
+msgstr ""
+
+#: include/like.php:184 include/conversation.php:141 include/diaspora.php:2185
+#: view/theme/diabook/theme.php:480
+#, php-format
+msgid "%1$s likes %2$s's %3$s"
+msgstr ""
+
+#: include/like.php:186 include/conversation.php:144
+#, php-format
+msgid "%1$s doesn't like %2$s's %3$s"
+msgstr ""
+
+#: include/like.php:188
+#, php-format
+msgid "%1$s is attending %2$s's %3$s"
+msgstr ""
+
+#: include/like.php:190
+#, php-format
+msgid "%1$s is not attending %2$s's %3$s"
+msgstr ""
+
+#: include/like.php:192
+#, php-format
+msgid "%1$s may attend %2$s's %3$s"
+msgstr ""
+
 #: include/acl_selectors.php:325
 msgid "Post to Email"
 msgstr ""
@@ -6851,6 +6880,10 @@ msgstr ""
 msgid "don't show"
 msgstr ""
 
+#: include/acl_selectors.php:348
+msgid "Close"
+msgstr ""
+
 #: include/message.php:15 include/message.php:173
 msgid "[no subject]"
 msgstr ""
@@ -6859,31 +6892,31 @@ msgstr ""
 msgid "stopped following"
 msgstr ""
 
-#: include/Contact.php:350 include/conversation.php:911
+#: include/Contact.php:337 include/conversation.php:911
 msgid "View Status"
 msgstr ""
 
-#: include/Contact.php:352 include/conversation.php:913
+#: include/Contact.php:339 include/conversation.php:913
 msgid "View Photos"
 msgstr ""
 
-#: include/Contact.php:353 include/conversation.php:914
+#: include/Contact.php:340 include/conversation.php:914
 msgid "Network Posts"
 msgstr ""
 
-#: include/Contact.php:354 include/conversation.php:915
+#: include/Contact.php:341 include/conversation.php:915
 msgid "Edit Contact"
 msgstr ""
 
-#: include/Contact.php:355
+#: include/Contact.php:342
 msgid "Drop Contact"
 msgstr ""
 
-#: include/Contact.php:356 include/conversation.php:916
+#: include/Contact.php:343 include/conversation.php:916
 msgid "Send PM"
 msgstr ""
 
-#: include/Contact.php:357 include/conversation.php:920
+#: include/Contact.php:344 include/conversation.php:920
 msgid "Poke"
 msgstr ""
 
@@ -6946,153 +6979,153 @@ msgstr ""
 msgid "Follow Thread"
 msgstr ""
 
-#: include/conversation.php:1035
+#: include/conversation.php:1034
 #, php-format
 msgid "%s likes this."
 msgstr ""
 
-#: include/conversation.php:1038
+#: include/conversation.php:1037
 #, php-format
 msgid "%s doesn't like this."
 msgstr ""
 
-#: include/conversation.php:1041
+#: include/conversation.php:1040
 #, php-format
 msgid "%s attends."
 msgstr ""
 
-#: include/conversation.php:1044
+#: include/conversation.php:1043
 #, php-format
 msgid "%s doesn't attend."
 msgstr ""
 
-#: include/conversation.php:1047
+#: include/conversation.php:1046
 #, php-format
 msgid "%s attends maybe."
 msgstr ""
 
-#: include/conversation.php:1057
+#: include/conversation.php:1056
 msgid "and"
 msgstr ""
 
-#: include/conversation.php:1063
+#: include/conversation.php:1062
 #, php-format
 msgid ", and %d other people"
 msgstr ""
 
-#: include/conversation.php:1072
+#: include/conversation.php:1071
 #, php-format
 msgid "<span  %1$s>%2$d people</span> like this"
 msgstr ""
 
-#: include/conversation.php:1073
+#: include/conversation.php:1072
 #, php-format
 msgid "%s like this."
 msgstr ""
 
-#: include/conversation.php:1076
+#: include/conversation.php:1075
 #, php-format
 msgid "<span  %1$s>%2$d people</span> don't like this"
 msgstr ""
 
-#: include/conversation.php:1077
+#: include/conversation.php:1076
 #, php-format
 msgid "%s don't like this."
 msgstr ""
 
-#: include/conversation.php:1080
+#: include/conversation.php:1079
 #, php-format
 msgid "<span  %1$s>%2$d people</span> attend"
 msgstr ""
 
-#: include/conversation.php:1081
+#: include/conversation.php:1080
 #, php-format
 msgid "%s attend."
 msgstr ""
 
-#: include/conversation.php:1084
+#: include/conversation.php:1083
 #, php-format
 msgid "<span  %1$s>%2$d people</span> don't attend"
 msgstr ""
 
-#: include/conversation.php:1085
+#: include/conversation.php:1084
 #, php-format
 msgid "%s don't attend."
 msgstr ""
 
-#: include/conversation.php:1088
+#: include/conversation.php:1087
 #, php-format
 msgid "<span  %1$s>%2$d people</span> anttend maybe"
 msgstr ""
 
-#: include/conversation.php:1089
+#: include/conversation.php:1088
 #, php-format
 msgid "%s anttend maybe."
 msgstr ""
 
-#: include/conversation.php:1128 include/conversation.php:1146
+#: include/conversation.php:1127 include/conversation.php:1145
 msgid "Visible to <strong>everybody</strong>"
 msgstr ""
 
-#: include/conversation.php:1130 include/conversation.php:1148
+#: include/conversation.php:1129 include/conversation.php:1147
 msgid "Please enter a video link/URL:"
 msgstr ""
 
-#: include/conversation.php:1131 include/conversation.php:1149
+#: include/conversation.php:1130 include/conversation.php:1148
 msgid "Please enter an audio link/URL:"
 msgstr ""
 
-#: include/conversation.php:1132 include/conversation.php:1150
+#: include/conversation.php:1131 include/conversation.php:1149
 msgid "Tag term:"
 msgstr ""
 
-#: include/conversation.php:1134 include/conversation.php:1152
+#: include/conversation.php:1133 include/conversation.php:1151
 msgid "Where are you right now?"
 msgstr ""
 
-#: include/conversation.php:1135
+#: include/conversation.php:1134
 msgid "Delete item(s)?"
 msgstr ""
 
-#: include/conversation.php:1204
+#: include/conversation.php:1203
 msgid "permissions"
 msgstr ""
 
-#: include/conversation.php:1227
+#: include/conversation.php:1226
 msgid "Post to Groups"
 msgstr ""
 
-#: include/conversation.php:1228
+#: include/conversation.php:1227
 msgid "Post to Contacts"
 msgstr ""
 
-#: include/conversation.php:1229
+#: include/conversation.php:1228
 msgid "Private post"
 msgstr ""
 
-#: include/conversation.php:1386
+#: include/conversation.php:1385
 msgid "View all"
 msgstr ""
 
-#: include/conversation.php:1408
+#: include/conversation.php:1407
 msgid "Like"
 msgid_plural "Likes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: include/conversation.php:1411
+#: include/conversation.php:1410
 msgid "Dislike"
 msgid_plural "Dislikes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: include/conversation.php:1417
+#: include/conversation.php:1416
 msgid "Not Attending"
 msgid_plural "Not Attending"
 msgstr[0] ""
 msgstr[1] ""
 
-#: include/conversation.php:1420 include/profile_selectors.php:6
+#: include/conversation.php:1419 include/profile_selectors.php:6
 msgid "Undecided"
 msgid_plural "Undecided"
 msgstr[0] ""
@@ -7294,66 +7327,58 @@ msgstr ""
 msgid "surprised"
 msgstr ""
 
-#: include/text.php:1497
+#: include/text.php:1504
 msgid "bytes"
 msgstr ""
 
-#: include/text.php:1529 include/text.php:1541
+#: include/text.php:1536 include/text.php:1548
 msgid "Click to open/close"
 msgstr ""
 
-#: include/text.php:1715
+#: include/text.php:1722
 msgid "View on separate page"
 msgstr ""
 
-#: include/text.php:1716
+#: include/text.php:1723
 msgid "view on separate page"
 msgstr ""
 
-#: include/text.php:1995
+#: include/text.php:2002
 msgid "activity"
 msgstr ""
 
-#: include/text.php:1998
+#: include/text.php:2005
 msgid "post"
 msgstr ""
 
-#: include/text.php:2166
+#: include/text.php:2173
 msgid "Item filed"
 msgstr ""
 
-#: include/bbcode.php:483 include/bbcode.php:1143 include/bbcode.php:1144
+#: include/bbcode.php:482 include/bbcode.php:1157 include/bbcode.php:1158
 msgid "Image/photo"
 msgstr ""
 
-#: include/bbcode.php:581
+#: include/bbcode.php:595
 #, php-format
 msgid "<a href=\"%1$s\" target=\"_blank\">%2$s</a> %3$s"
 msgstr ""
 
-#: include/bbcode.php:615
+#: include/bbcode.php:629
 #, php-format
 msgid ""
 "<span><a href=\"%s\" target=\"_blank\">%s</a> wrote the following <a href="
 "\"%s\" target=\"_blank\">post</a>"
 msgstr ""
 
-#: include/bbcode.php:1103 include/bbcode.php:1123
+#: include/bbcode.php:1117 include/bbcode.php:1137
 msgid "$1 wrote:"
 msgstr ""
 
-#: include/bbcode.php:1152 include/bbcode.php:1153
+#: include/bbcode.php:1166 include/bbcode.php:1167
 msgid "Encrypted content"
 msgstr ""
 
-#: include/notifier.php:843 include/delivery.php:459
-msgid "(no subject)"
-msgstr ""
-
-#: include/notifier.php:853 include/delivery.php:470 include/enotify.php:37
-msgid "noreply"
-msgstr ""
-
 #: include/dba_pdo.php:72 include/dba.php:55
 #, php-format
 msgid "Cannot locate DNS info for database server '%s'"
@@ -7399,6 +7424,10 @@ msgstr ""
 msgid "RSS/Atom"
 msgstr ""
 
+#: include/contact_selectors.php:81
+msgid "Facebook"
+msgstr ""
+
 #: include/contact_selectors.php:82
 msgid "Zot!"
 msgstr ""
@@ -7443,7 +7472,7 @@ msgstr ""
 msgid "Redmatrix"
 msgstr ""
 
-#: include/Scrape.php:610
+#: include/Scrape.php:624
 msgid " on Last.fm"
 msgstr ""
 
@@ -7619,46 +7648,21 @@ msgstr ""
 msgid "Site map"
 msgstr ""
 
-#: include/api.php:345 include/api.php:356 include/api.php:465
-#: include/api.php:1184 include/api.php:1186
-msgid "User not found."
-msgstr ""
-
-#: include/api.php:832
+#: include/api.php:878
 #, php-format
 msgid "Daily posting limit of %d posts reached. The post was rejected."
 msgstr ""
 
-#: include/api.php:851
+#: include/api.php:897
 #, php-format
 msgid "Weekly posting limit of %d posts reached. The post was rejected."
 msgstr ""
 
-#: include/api.php:870
+#: include/api.php:916
 #, php-format
 msgid "Monthly posting limit of %d posts reached. The post was rejected."
 msgstr ""
 
-#: include/api.php:1393
-msgid "There is no status with this id."
-msgstr ""
-
-#: include/api.php:1467
-msgid "There is no conversation with this id."
-msgstr ""
-
-#: include/api.php:1746
-msgid "Invalid item."
-msgstr ""
-
-#: include/api.php:1756
-msgid "Invalid action. "
-msgstr ""
-
-#: include/api.php:1764
-msgid "DB error"
-msgstr ""
-
 #: include/user.php:48
 msgid "An invitation is required."
 msgstr ""
@@ -7721,8 +7725,7 @@ msgstr ""
 msgid "An error occurred during registration. Please try again."
 msgstr ""
 
-#: include/user.php:256 view/theme/clean/config.php:56
-#: view/theme/duepuntozero/config.php:44
+#: include/user.php:256 view/theme/duepuntozero/config.php:44
 msgid "default"
 msgstr ""
 
@@ -7782,15 +7785,23 @@ msgstr ""
 msgid "Sharing notification from Diaspora network"
 msgstr ""
 
-#: include/diaspora.php:2607
+#: include/diaspora.php:2625
 msgid "Attachments:"
 msgstr ""
 
-#: include/items.php:4900
+#: include/delivery.php:533
+msgid "(no subject)"
+msgstr ""
+
+#: include/delivery.php:544 include/enotify.php:37
+msgid "noreply"
+msgstr ""
+
+#: include/items.php:4926
 msgid "Do you really want to delete this item?"
 msgstr ""
 
-#: include/items.php:5175
+#: include/items.php:5201
 msgid "Archives"
 msgstr ""
 
@@ -8306,11 +8317,11 @@ msgstr ""
 msgid "Please visit %s to approve or reject the request."
 msgstr ""
 
-#: include/oembed.php:220
+#: include/oembed.php:226
 msgid "Embedded content"
 msgstr ""
 
-#: include/oembed.php:229
+#: include/oembed.php:235
 msgid "Embedding disabled"
 msgstr ""
 
@@ -8368,7 +8379,6 @@ msgid "Set theme width"
 msgstr ""
 
 #: view/theme/cleanzero/config.php:86 view/theme/quattro/config.php:68
-#: view/theme/clean/config.php:88
 msgid "Color scheme"
 msgstr ""
 
@@ -8490,56 +8500,6 @@ msgstr ""
 msgid "Show/hide boxes at right-hand column:"
 msgstr ""
 
-#: view/theme/clean/config.php:57
-msgid "Midnight"
-msgstr ""
-
-#: view/theme/clean/config.php:58
-msgid "Zenburn"
-msgstr ""
-
-#: view/theme/clean/config.php:59
-msgid "Bootstrap"
-msgstr ""
-
-#: view/theme/clean/config.php:60
-msgid "Shades of Pink"
-msgstr ""
-
-#: view/theme/clean/config.php:61
-msgid "Lime and Orange"
-msgstr ""
-
-#: view/theme/clean/config.php:62
-msgid "GeoCities Retro"
-msgstr ""
-
-#: view/theme/clean/config.php:86
-msgid "Background Image"
-msgstr ""
-
-#: view/theme/clean/config.php:86
-msgid ""
-"The URL to a picture (e.g. from your photo album) that should be used as "
-"background image."
-msgstr ""
-
-#: view/theme/clean/config.php:87
-msgid "Background Color"
-msgstr ""
-
-#: view/theme/clean/config.php:87
-msgid "HEX value for the background color. Don't include the #"
-msgstr ""
-
-#: view/theme/clean/config.php:89
-msgid "font size"
-msgstr ""
-
-#: view/theme/clean/config.php:89
-msgid "base font size for your interface"
-msgstr ""
-
 #: view/theme/vier/config.php:64
 msgid "Comma separated list of helper forums"
 msgstr ""
index d671c21a9098350145c1d99c25aa64f0a709f79f..9840156ca41c7c4d106260f689a397b635cad330 100644 (file)
@@ -24,7 +24,7 @@
 # Sennewood <sebastian@sebsen.net>, 2013
 # Sennewood <sebastian@sebsen.net>, 2012-2013
 # silke m <silke@silkemeyer.net>, 2015
-# bavatar <tobias.diekershoff@gmx.net>, 2013-2015
+# bavatar <tobias.diekershoff@gmx.net>, 2013-2016
 # bavatar <tobias.diekershoff@gmx.net>, 2011-2013
 # zottel <transifex@zottel.net>, 2011-2012
 # tschlotfeldt <ts+transifex@ml.tschlotfeldt.de>, 2011
@@ -32,8 +32,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: friendica\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-12-27 08:40+0100\n"
-"PO-Revision-Date: 2015-12-31 06:29+0000\n"
+"POT-Creation-Date: 2016-01-24 06:49+0100\n"
+"PO-Revision-Date: 2016-01-24 13:15+0000\n"
 "Last-Translator: bavatar <tobias.diekershoff@gmx.net>\n"
 "Language-Team: German (http://www.transifex.com/Friendica/friendica/language/de/)\n"
 "MIME-Version: 1.0\n"
@@ -42,26 +42,26 @@ msgstr ""
 "Language: de\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#: mod/contacts.php:50 include/identity.php:389
+#: mod/contacts.php:50 include/identity.php:395
 msgid "Network:"
 msgstr "Netzwerk"
 
-#: mod/contacts.php:51 mod/contacts.php:986 mod/videos.php:37
-#: mod/viewcontacts.php:105 mod/dirfind.php:208 mod/network.php:596
+#: mod/contacts.php:51 mod/contacts.php:961 mod/videos.php:37
+#: mod/viewcontacts.php:105 mod/dirfind.php:214 mod/network.php:598
 #: mod/allfriends.php:77 mod/match.php:82 mod/directory.php:172
 #: mod/common.php:123 mod/suggest.php:95 mod/photos.php:41
-#: include/identity.php:295
+#: include/identity.php:298
 msgid "Forum"
 msgstr "Forum"
 
 #: mod/contacts.php:128
 #, php-format
 msgid "%d contact edited."
-msgid_plural "%d contacts edited"
+msgid_plural "%d contacts edited."
 msgstr[0] "%d Kontakt bearbeitet."
-msgstr[1] "%d Kontakte bearbeitet"
+msgstr[1] "%d Kontakte bearbeitet."
 
-#: mod/contacts.php:159 mod/contacts.php:382
+#: mod/contacts.php:159 mod/contacts.php:383
 msgid "Could not access contact record."
 msgstr "Konnte nicht auf die Kontaktdaten zugreifen."
 
@@ -73,15 +73,15 @@ msgstr "Konnte das ausgewählte Profil nicht finden."
 msgid "Contact updated."
 msgstr "Kontakt aktualisiert."
 
-#: mod/contacts.php:208 mod/dfrn_request.php:578
+#: mod/contacts.php:208 mod/dfrn_request.php:575
 msgid "Failed to update contact record."
 msgstr "Aktualisierung der Kontaktdaten fehlgeschlagen."
 
-#: mod/contacts.php:364 mod/manage.php:96 mod/display.php:493
+#: mod/contacts.php:365 mod/manage.php:96 mod/display.php:509
 #: mod/profile_photo.php:19 mod/profile_photo.php:175
 #: mod/profile_photo.php:186 mod/profile_photo.php:199
-#: mod/ostatus_subscribe.php:9 mod/follow.php:10 mod/follow.php:72
-#: mod/follow.php:137 mod/item.php:169 mod/item.php:181 mod/group.php:19
+#: mod/ostatus_subscribe.php:9 mod/follow.php:11 mod/follow.php:73
+#: mod/follow.php:155 mod/item.php:180 mod/item.php:192 mod/group.php:19
 #: mod/dfrn_confirm.php:55 mod/fsuggest.php:78 mod/wall_upload.php:77
 #: mod/wall_upload.php:80 mod/viewcontacts.php:40 mod/notifications.php:69
 #: mod/message.php:45 mod/message.php:181 mod/crepair.php:117
@@ -89,129 +89,129 @@ msgstr "Aktualisierung der Kontaktdaten fehlgeschlagen."
 #: mod/allfriends.php:12 mod/events.php:165 mod/wallmessage.php:9
 #: mod/wallmessage.php:33 mod/wallmessage.php:79 mod/wallmessage.php:103
 #: mod/wall_attach.php:67 mod/wall_attach.php:70 mod/settings.php:20
-#: mod/settings.php:116 mod/settings.php:637 mod/register.php:42
+#: mod/settings.php:126 mod/settings.php:646 mod/register.php:42
 #: mod/delegate.php:12 mod/common.php:18 mod/mood.php:114 mod/suggest.php:58
 #: mod/profiles.php:165 mod/profiles.php:615 mod/editpost.php:10
 #: mod/api.php:26 mod/api.php:31 mod/notes.php:22 mod/poke.php:149
 #: mod/repair_ostatus.php:9 mod/invite.php:15 mod/invite.php:101
 #: mod/photos.php:171 mod/photos.php:1105 mod/regmod.php:110
-#: mod/uimport.php:23 mod/attach.php:33 include/items.php:5070 index.php:383
+#: mod/uimport.php:23 mod/attach.php:33 include/items.php:5096 index.php:383
 msgid "Permission denied."
 msgstr "Zugriff verweigert."
 
-#: mod/contacts.php:403
+#: mod/contacts.php:404
 msgid "Contact has been blocked"
 msgstr "Kontakt wurde blockiert"
 
-#: mod/contacts.php:403
+#: mod/contacts.php:404
 msgid "Contact has been unblocked"
 msgstr "Kontakt wurde wieder freigegeben"
 
-#: mod/contacts.php:414
+#: mod/contacts.php:415
 msgid "Contact has been ignored"
 msgstr "Kontakt wurde ignoriert"
 
-#: mod/contacts.php:414
+#: mod/contacts.php:415
 msgid "Contact has been unignored"
 msgstr "Kontakt wird nicht mehr ignoriert"
 
-#: mod/contacts.php:426
+#: mod/contacts.php:427
 msgid "Contact has been archived"
 msgstr "Kontakt wurde archiviert"
 
-#: mod/contacts.php:426
+#: mod/contacts.php:427
 msgid "Contact has been unarchived"
 msgstr "Kontakt wurde aus dem Archiv geholt"
 
-#: mod/contacts.php:453 mod/contacts.php:801
+#: mod/contacts.php:454 mod/contacts.php:802
 msgid "Do you really want to delete this contact?"
 msgstr "Möchtest Du wirklich diesen Kontakt löschen?"
 
-#: mod/contacts.php:455 mod/follow.php:105 mod/message.php:216
-#: mod/settings.php:1094 mod/settings.php:1100 mod/settings.php:1108
-#: mod/settings.php:1112 mod/settings.php:1117 mod/settings.php:1123
-#: mod/settings.php:1129 mod/settings.php:1135 mod/settings.php:1161
-#: mod/settings.php:1162 mod/settings.php:1163 mod/settings.php:1164
-#: mod/settings.php:1165 mod/dfrn_request.php:850 mod/register.php:238
+#: mod/contacts.php:456 mod/follow.php:110 mod/message.php:216
+#: mod/settings.php:1103 mod/settings.php:1109 mod/settings.php:1117
+#: mod/settings.php:1121 mod/settings.php:1126 mod/settings.php:1132
+#: mod/settings.php:1138 mod/settings.php:1144 mod/settings.php:1170
+#: mod/settings.php:1171 mod/settings.php:1172 mod/settings.php:1173
+#: mod/settings.php:1174 mod/dfrn_request.php:857 mod/register.php:238
 #: mod/suggest.php:29 mod/profiles.php:658 mod/profiles.php:661
-#: mod/profiles.php:687 mod/api.php:105 include/items.php:4902
+#: mod/profiles.php:687 mod/api.php:105 include/items.php:4928
 msgid "Yes"
 msgstr "Ja"
 
-#: mod/contacts.php:458 mod/tagrm.php:11 mod/tagrm.php:94 mod/follow.php:116
+#: mod/contacts.php:459 mod/tagrm.php:11 mod/tagrm.php:94 mod/follow.php:121
 #: mod/videos.php:131 mod/message.php:219 mod/fbrowser.php:93
-#: mod/fbrowser.php:128 mod/settings.php:651 mod/settings.php:677
-#: mod/dfrn_request.php:864 mod/suggest.php:32 mod/editpost.php:148
-#: mod/photos.php:247 mod/photos.php:336 include/conversation.php:1221
-#: include/items.php:4905
+#: mod/fbrowser.php:128 mod/settings.php:660 mod/settings.php:686
+#: mod/dfrn_request.php:871 mod/suggest.php:32 mod/editpost.php:148
+#: mod/photos.php:247 mod/photos.php:336 include/conversation.php:1220
+#: include/items.php:4931
 msgid "Cancel"
 msgstr "Abbrechen"
 
-#: mod/contacts.php:470
+#: mod/contacts.php:471
 msgid "Contact has been removed."
 msgstr "Kontakt wurde entfernt."
 
-#: mod/contacts.php:511
+#: mod/contacts.php:512
 #, php-format
 msgid "You are mutual friends with %s"
 msgstr "Du hast mit %s eine beidseitige Freundschaft"
 
-#: mod/contacts.php:515
+#: mod/contacts.php:516
 #, php-format
 msgid "You are sharing with %s"
 msgstr "Du teilst mit %s"
 
-#: mod/contacts.php:520
+#: mod/contacts.php:521
 #, php-format
 msgid "%s is sharing with you"
 msgstr "%s teilt mit Dir"
 
-#: mod/contacts.php:540
+#: mod/contacts.php:541
 msgid "Private communications are not available for this contact."
 msgstr "Private Kommunikation ist für diesen Kontakt nicht verfügbar."
 
-#: mod/contacts.php:543 mod/admin.php:647
+#: mod/contacts.php:544 mod/admin.php:822
 msgid "Never"
 msgstr "Niemals"
 
-#: mod/contacts.php:547
+#: mod/contacts.php:548
 msgid "(Update was successful)"
 msgstr "(Aktualisierung war erfolgreich)"
 
-#: mod/contacts.php:547
+#: mod/contacts.php:548
 msgid "(Update was not successful)"
 msgstr "(Aktualisierung war nicht erfolgreich)"
 
-#: mod/contacts.php:549
+#: mod/contacts.php:550
 msgid "Suggest friends"
 msgstr "Kontakte vorschlagen"
 
-#: mod/contacts.php:553
+#: mod/contacts.php:554
 #, php-format
 msgid "Network type: %s"
 msgstr "Netzwerktyp: %s"
 
-#: mod/contacts.php:566
+#: mod/contacts.php:567
 msgid "Communications lost with this contact!"
 msgstr "Verbindungen mit diesem Kontakt verloren!"
 
-#: mod/contacts.php:569
+#: mod/contacts.php:570
 msgid "Fetch further information for feeds"
 msgstr "Weitere Informationen zu Feeds holen"
 
-#: mod/contacts.php:570 mod/admin.php:656
+#: mod/contacts.php:571 mod/admin.php:831
 msgid "Disabled"
 msgstr "Deaktiviert"
 
-#: mod/contacts.php:570
+#: mod/contacts.php:571
 msgid "Fetch information"
 msgstr "Beziehe Information"
 
-#: mod/contacts.php:570
+#: mod/contacts.php:571
 msgid "Fetch information and keywords"
 msgstr "Beziehe Information und Schlüsselworte"
 
-#: mod/contacts.php:586 mod/manage.php:143 mod/fsuggest.php:107
+#: mod/contacts.php:587 mod/manage.php:143 mod/fsuggest.php:107
 #: mod/message.php:342 mod/message.php:525 mod/crepair.php:196
 #: mod/events.php:574 mod/content.php:712 mod/install.php:261
 #: mod/install.php:299 mod/mood.php:137 mod/profiles.php:696
@@ -221,308 +221,308 @@ msgstr "Beziehe Information und Schlüsselworte"
 #: object/Item.php:710 view/theme/cleanzero/config.php:80
 #: view/theme/dispy/config.php:70 view/theme/quattro/config.php:64
 #: view/theme/diabook/config.php:148 view/theme/diabook/theme.php:633
-#: view/theme/clean/config.php:83 view/theme/vier/config.php:107
-#: view/theme/duepuntozero/config.php:59
+#: view/theme/vier/config.php:107 view/theme/duepuntozero/config.php:59
 msgid "Submit"
 msgstr "Senden"
 
-#: mod/contacts.php:587
+#: mod/contacts.php:588
 msgid "Profile Visibility"
 msgstr "Profil-Sichtbarkeit"
 
-#: mod/contacts.php:588
+#: mod/contacts.php:589
 #, php-format
 msgid ""
 "Please choose the profile you would like to display to %s when viewing your "
 "profile securely."
 msgstr "Bitte wähle eines Deiner Profile das angezeigt werden soll, wenn %s Dein Profil aufruft."
 
-#: mod/contacts.php:589
+#: mod/contacts.php:590
 msgid "Contact Information / Notes"
 msgstr "Kontakt Informationen / Notizen"
 
-#: mod/contacts.php:590
+#: mod/contacts.php:591
 msgid "Edit contact notes"
 msgstr "Notizen zum Kontakt bearbeiten"
 
-#: mod/contacts.php:595 mod/contacts.php:977 mod/viewcontacts.php:97
+#: mod/contacts.php:596 mod/contacts.php:952 mod/viewcontacts.php:97
 #: mod/nogroup.php:41
 #, php-format
 msgid "Visit %s's profile [%s]"
 msgstr "Besuche %ss Profil [%s]"
 
-#: mod/contacts.php:596
+#: mod/contacts.php:597
 msgid "Block/Unblock contact"
 msgstr "Kontakt blockieren/freischalten"
 
-#: mod/contacts.php:597
+#: mod/contacts.php:598
 msgid "Ignore contact"
 msgstr "Ignoriere den Kontakt"
 
-#: mod/contacts.php:598
+#: mod/contacts.php:599
 msgid "Repair URL settings"
 msgstr "URL Einstellungen reparieren"
 
-#: mod/contacts.php:599
+#: mod/contacts.php:600
 msgid "View conversations"
 msgstr "Unterhaltungen anzeigen"
 
-#: mod/contacts.php:601
+#: mod/contacts.php:602
 msgid "Delete contact"
 msgstr "Lösche den Kontakt"
 
-#: mod/contacts.php:605
+#: mod/contacts.php:606
 msgid "Last update:"
 msgstr "Letzte Aktualisierung: "
 
-#: mod/contacts.php:607
+#: mod/contacts.php:608
 msgid "Update public posts"
 msgstr "Öffentliche Beiträge aktualisieren"
 
-#: mod/contacts.php:609 mod/admin.php:1656
+#: mod/contacts.php:610
 msgid "Update now"
 msgstr "Jetzt aktualisieren"
 
-#: mod/contacts.php:611 mod/dirfind.php:190 mod/allfriends.php:65
-#: mod/match.php:71 mod/suggest.php:82 include/contact_widgets.php:32
-#: include/Contact.php:310 include/conversation.php:924
+#: mod/contacts.php:612 mod/follow.php:103 mod/dirfind.php:196
+#: mod/allfriends.php:65 mod/match.php:71 mod/suggest.php:82
+#: include/contact_widgets.php:32 include/Contact.php:297
+#: include/conversation.php:924
 msgid "Connect/Follow"
 msgstr "Verbinden/Folgen"
 
-#: mod/contacts.php:614 mod/contacts.php:805 mod/contacts.php:864
-#: mod/admin.php:1120
+#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:865
+#: mod/admin.php:1312
 msgid "Unblock"
 msgstr "Entsperren"
 
-#: mod/contacts.php:614 mod/contacts.php:805 mod/contacts.php:864
-#: mod/admin.php:1119
+#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:865
+#: mod/admin.php:1311
 msgid "Block"
 msgstr "Sperren"
 
-#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:871
+#: mod/contacts.php:616 mod/contacts.php:807 mod/contacts.php:872
 msgid "Unignore"
 msgstr "Ignorieren aufheben"
 
-#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:871
+#: mod/contacts.php:616 mod/contacts.php:807 mod/contacts.php:872
 #: mod/notifications.php:54 mod/notifications.php:179
 #: mod/notifications.php:259
 msgid "Ignore"
 msgstr "Ignorieren"
 
-#: mod/contacts.php:618
+#: mod/contacts.php:619
 msgid "Currently blocked"
 msgstr "Derzeit geblockt"
 
-#: mod/contacts.php:619
+#: mod/contacts.php:620
 msgid "Currently ignored"
 msgstr "Derzeit ignoriert"
 
-#: mod/contacts.php:620
+#: mod/contacts.php:621
 msgid "Currently archived"
 msgstr "Momentan archiviert"
 
-#: mod/contacts.php:621 mod/notifications.php:172 mod/notifications.php:251
+#: mod/contacts.php:622 mod/notifications.php:172 mod/notifications.php:251
 msgid "Hide this contact from others"
 msgstr "Verbirg diesen Kontakt vor andere"
 
-#: mod/contacts.php:621
+#: mod/contacts.php:622
 msgid ""
 "Replies/likes to your public posts <strong>may</strong> still be visible"
 msgstr "Antworten/Likes auf deine öffentlichen Beiträge <strong>könnten</strong> weiterhin sichtbar sein"
 
-#: mod/contacts.php:622
+#: mod/contacts.php:623
 msgid "Notification for new posts"
 msgstr "Benachrichtigung bei neuen Beiträgen"
 
-#: mod/contacts.php:622
+#: mod/contacts.php:623
 msgid "Send a notification of every new post of this contact"
 msgstr "Sende eine Benachrichtigung, wann immer dieser Kontakt einen neuen Beitrag schreibt."
 
-#: mod/contacts.php:625
+#: mod/contacts.php:626
 msgid "Blacklisted keywords"
 msgstr "Blacklistete Schlüsselworte "
 
-#: mod/contacts.php:625
+#: mod/contacts.php:626
 msgid ""
 "Comma separated list of keywords that should not be converted to hashtags, "
 "when \"Fetch information and keywords\" is selected"
 msgstr "Komma-Separierte Liste mit Schlüsselworten, die nicht in Hashtags konvertiert werden, wenn \"Beziehe Information und Schlüsselworte\" aktiviert wurde"
 
-#: mod/contacts.php:632 mod/follow.php:121 mod/notifications.php:255
+#: mod/contacts.php:633 mod/follow.php:126 mod/notifications.php:255
 msgid "Profile URL"
 msgstr "Profil URL"
 
-#: mod/contacts.php:635 mod/follow.php:125 mod/notifications.php:244
-#: mod/events.php:566 mod/directory.php:145 include/identity.php:304
-#: include/bb2diaspora.php:170 include/event.php:36 include/event.php:60
+#: mod/contacts.php:636 mod/notifications.php:244 mod/events.php:566
+#: mod/directory.php:145 include/identity.php:308 include/bb2diaspora.php:170
+#: include/event.php:36 include/event.php:60
 msgid "Location:"
 msgstr "Ort:"
 
-#: mod/contacts.php:637 mod/follow.php:127 mod/notifications.php:246
-#: mod/directory.php:153 include/identity.php:313 include/identity.php:630
+#: mod/contacts.php:638 mod/notifications.php:246 mod/directory.php:153
+#: include/identity.php:317 include/identity.php:631
 msgid "About:"
 msgstr "Über:"
 
-#: mod/contacts.php:639 mod/follow.php:129 mod/notifications.php:248
-#: include/identity.php:624
+#: mod/contacts.php:640 mod/follow.php:134 mod/notifications.php:248
+#: include/identity.php:625
 msgid "Tags:"
 msgstr "Tags"
 
-#: mod/contacts.php:684
+#: mod/contacts.php:685
 msgid "Suggestions"
 msgstr "Kontaktvorschläge"
 
-#: mod/contacts.php:687
+#: mod/contacts.php:688
 msgid "Suggest potential friends"
 msgstr "Freunde vorschlagen"
 
-#: mod/contacts.php:692 mod/group.php:192
+#: mod/contacts.php:693 mod/group.php:192
 msgid "All Contacts"
 msgstr "Alle Kontakte"
 
-#: mod/contacts.php:695
+#: mod/contacts.php:696
 msgid "Show all contacts"
 msgstr "Alle Kontakte anzeigen"
 
-#: mod/contacts.php:700
+#: mod/contacts.php:701
 msgid "Unblocked"
 msgstr "Ungeblockt"
 
-#: mod/contacts.php:703
+#: mod/contacts.php:704
 msgid "Only show unblocked contacts"
 msgstr "Nur nicht-blockierte Kontakte anzeigen"
 
-#: mod/contacts.php:709
+#: mod/contacts.php:710
 msgid "Blocked"
 msgstr "Geblockt"
 
-#: mod/contacts.php:712
+#: mod/contacts.php:713
 msgid "Only show blocked contacts"
 msgstr "Nur blockierte Kontakte anzeigen"
 
-#: mod/contacts.php:718
+#: mod/contacts.php:719
 msgid "Ignored"
 msgstr "Ignoriert"
 
-#: mod/contacts.php:721
+#: mod/contacts.php:722
 msgid "Only show ignored contacts"
 msgstr "Nur ignorierte Kontakte anzeigen"
 
-#: mod/contacts.php:727
+#: mod/contacts.php:728
 msgid "Archived"
 msgstr "Archiviert"
 
-#: mod/contacts.php:730
+#: mod/contacts.php:731
 msgid "Only show archived contacts"
 msgstr "Nur archivierte Kontakte anzeigen"
 
-#: mod/contacts.php:736
+#: mod/contacts.php:737
 msgid "Hidden"
 msgstr "Verborgen"
 
-#: mod/contacts.php:739
+#: mod/contacts.php:740
 msgid "Only show hidden contacts"
 msgstr "Nur verborgene Kontakte anzeigen"
 
-#: mod/contacts.php:792 mod/contacts.php:840 mod/viewcontacts.php:116
+#: mod/contacts.php:793 mod/contacts.php:841 mod/viewcontacts.php:116
 #: include/identity.php:741 include/identity.php:744 include/text.php:1012
 #: include/nav.php:123 include/nav.php:187 view/theme/diabook/theme.php:125
 msgid "Contacts"
 msgstr "Kontakte"
 
-#: mod/contacts.php:796
+#: mod/contacts.php:797
 msgid "Search your contacts"
 msgstr "Suche in deinen Kontakten"
 
-#: mod/contacts.php:797
+#: mod/contacts.php:798
 msgid "Finding: "
 msgstr "Funde: "
 
-#: mod/contacts.php:798 mod/directory.php:210 include/contact_widgets.php:34
+#: mod/contacts.php:799 mod/directory.php:210 include/contact_widgets.php:34
 msgid "Find"
 msgstr "Finde"
 
-#: mod/contacts.php:804 mod/settings.php:146 mod/settings.php:676
+#: mod/contacts.php:805 mod/settings.php:156 mod/settings.php:685
 msgid "Update"
 msgstr "Aktualisierungen"
 
-#: mod/contacts.php:807 mod/contacts.php:878
+#: mod/contacts.php:808 mod/contacts.php:879
 msgid "Archive"
 msgstr "Archivieren"
 
-#: mod/contacts.php:807 mod/contacts.php:878
+#: mod/contacts.php:808 mod/contacts.php:879
 msgid "Unarchive"
 msgstr "Aus Archiv zurückholen"
 
-#: mod/contacts.php:808 mod/group.php:171 mod/admin.php:1118
-#: mod/content.php:440 mod/content.php:743 mod/settings.php:713
+#: mod/contacts.php:809 mod/group.php:171 mod/admin.php:1310
+#: mod/content.php:440 mod/content.php:743 mod/settings.php:722
 #: mod/photos.php:1723 object/Item.php:134 include/conversation.php:635
 msgid "Delete"
 msgstr "Löschen"
 
-#: mod/contacts.php:821 include/identity.php:686 include/nav.php:75
+#: mod/contacts.php:822 include/identity.php:686 include/nav.php:75
 msgid "Status"
 msgstr "Status"
 
-#: mod/contacts.php:824 include/identity.php:689
+#: mod/contacts.php:825 mod/follow.php:143 include/identity.php:689
 msgid "Status Messages and Posts"
 msgstr "Statusnachrichten und Beiträge"
 
-#: mod/contacts.php:829 mod/profperm.php:104 mod/newmember.php:32
-#: include/identity.php:578 include/identity.php:664 include/identity.php:694
+#: mod/contacts.php:830 mod/profperm.php:104 mod/newmember.php:32
+#: include/identity.php:579 include/identity.php:665 include/identity.php:694
 #: include/nav.php:76 view/theme/diabook/theme.php:124
 msgid "Profile"
 msgstr "Profil"
 
-#: mod/contacts.php:832 include/identity.php:697
+#: mod/contacts.php:833 include/identity.php:697
 msgid "Profile Details"
 msgstr "Profildetails"
 
-#: mod/contacts.php:843
+#: mod/contacts.php:844
 msgid "View all contacts"
 msgstr "Alle Kontakte anzeigen"
 
-#: mod/contacts.php:849 mod/common.php:134
+#: mod/contacts.php:850 mod/common.php:134
 msgid "Common Friends"
 msgstr "Gemeinsame Freunde"
 
-#: mod/contacts.php:852
+#: mod/contacts.php:853
 msgid "View all common friends"
 msgstr "Alle Kontakte anzeigen"
 
-#: mod/contacts.php:856
+#: mod/contacts.php:857
 msgid "Repair"
 msgstr "Reparieren"
 
-#: mod/contacts.php:859
+#: mod/contacts.php:860
 msgid "Advanced Contact Settings"
 msgstr "Fortgeschrittene Kontakteinstellungen"
 
-#: mod/contacts.php:867
+#: mod/contacts.php:868
 msgid "Toggle Blocked status"
 msgstr "Geblockt-Status ein-/ausschalten"
 
-#: mod/contacts.php:874
+#: mod/contacts.php:875
 msgid "Toggle Ignored status"
 msgstr "Ignoriert-Status ein-/ausschalten"
 
-#: mod/contacts.php:881
+#: mod/contacts.php:882
 msgid "Toggle Archive status"
 msgstr "Archiviert-Status ein-/ausschalten"
 
-#: mod/contacts.php:949
+#: mod/contacts.php:924
 msgid "Mutual Friendship"
 msgstr "Beidseitige Freundschaft"
 
-#: mod/contacts.php:953
+#: mod/contacts.php:928
 msgid "is a fan of yours"
 msgstr "ist ein Fan von dir"
 
-#: mod/contacts.php:957
+#: mod/contacts.php:932
 msgid "you are a fan of"
 msgstr "Du bist Fan von"
 
-#: mod/contacts.php:978 mod/nogroup.php:42
+#: mod/contacts.php:953 mod/nogroup.php:42
 msgid "Edit contact"
 msgstr "Kontakt bearbeiten"
 
@@ -572,23 +572,23 @@ msgstr "Sichtbar für"
 msgid "All Contacts (with secure profile access)"
 msgstr "Alle Kontakte (mit gesichertem Profilzugriff)"
 
-#: mod/display.php:82 mod/display.php:280 mod/display.php:497
-#: mod/viewsrc.php:15 mod/admin.php:196 mod/admin.php:1163 mod/admin.php:1384
-#: mod/notice.php:15 include/items.php:4861
+#: mod/display.php:82 mod/display.php:291 mod/display.php:513
+#: mod/viewsrc.php:15 mod/admin.php:234 mod/admin.php:1365 mod/admin.php:1599
+#: mod/notice.php:15 include/items.php:4887
 msgid "Item not found."
 msgstr "Beitrag nicht gefunden."
 
-#: mod/display.php:209 mod/videos.php:197 mod/viewcontacts.php:35
-#: mod/community.php:18 mod/dfrn_request.php:779 mod/search.php:93
+#: mod/display.php:220 mod/videos.php:197 mod/viewcontacts.php:35
+#: mod/community.php:22 mod/dfrn_request.php:786 mod/search.php:93
 #: mod/search.php:99 mod/directory.php:37 mod/photos.php:976
 msgid "Public access denied."
 msgstr "Öffentlicher Zugriff verweigert."
 
-#: mod/display.php:328 mod/profile.php:155
+#: mod/display.php:339 mod/profile.php:155
 msgid "Access to this profile has been restricted."
 msgstr "Der Zugriff zu diesem Profil wurde eingeschränkt."
 
-#: mod/display.php:490
+#: mod/display.php:506
 msgid "Item has been removed."
 msgstr "Eintrag wurde entfernt."
 
@@ -623,8 +623,8 @@ msgid ""
 " join."
 msgstr "Auf der <em>Quick Start</em> Seite findest Du eine kurze Einleitung in die einzelnen Funktionen Deines Profils und die Netzwerk-Reiter, wo Du interessante Foren findest und neue Kontakte knüpfst."
 
-#: mod/newmember.php:22 mod/admin.php:1215 mod/admin.php:1460
-#: mod/settings.php:99 include/nav.php:182 view/theme/diabook/theme.php:544
+#: mod/newmember.php:22 mod/admin.php:1418 mod/admin.php:1676
+#: mod/settings.php:109 include/nav.php:182 view/theme/diabook/theme.php:544
 #: view/theme/diabook/theme.php:648
 msgid "Settings"
 msgstr "Einstellungen"
@@ -685,60 +685,44 @@ msgstr "Trage ein paar öffentliche Stichwörter in Dein Standardprofil ein, die
 msgid "Connecting"
 msgstr "Verbindungen knüpfen"
 
-#: mod/newmember.php:49 mod/newmember.php:51 include/contact_selectors.php:81
-msgid "Facebook"
-msgstr "Facebook"
-
-#: mod/newmember.php:49
-msgid ""
-"Authorise the Facebook Connector if you currently have a Facebook account "
-"and we will (optionally) import all your Facebook friends and conversations."
-msgstr "Richte die Verbindung zu Facebook ein, wenn Du im Augenblick ein Facebook-Konto hast und (optional) Deine Facebook-Freunde und -Unterhaltungen importieren willst."
-
 #: mod/newmember.php:51
-msgid ""
-"<em>If</em> this is your own personal server, installing the Facebook addon "
-"may ease your transition to the free social web."
-msgstr "<em>Wenn</em> dies Dein privater Server ist, könnte die Installation des Facebook Connectors Deinen Umzug ins freie soziale Netz angenehmer gestalten."
-
-#: mod/newmember.php:56
 msgid "Importing Emails"
 msgstr "Emails Importieren"
 
-#: mod/newmember.php:56
+#: mod/newmember.php:51
 msgid ""
 "Enter your email access information on your Connector Settings page if you "
 "wish to import and interact with friends or mailing lists from your email "
 "INBOX"
 msgstr "Gib Deine E-Mail-Zugangsinformationen auf der Connector-Einstellungsseite ein, falls Du E-Mails aus Deinem Posteingang importieren und mit Freunden und Mailinglisten interagieren willst."
 
-#: mod/newmember.php:58
+#: mod/newmember.php:53
 msgid "Go to Your Contacts Page"
 msgstr "Gehe zu deiner Kontakt-Seite"
 
-#: mod/newmember.php:58
+#: mod/newmember.php:53
 msgid ""
 "Your Contacts page is your gateway to managing friendships and connecting "
 "with friends on other networks. Typically you enter their address or site "
 "URL in the <em>Add New Contact</em> dialog."
 msgstr "Die Kontakte-Seite ist die Einstiegsseite, von der aus Du Kontakte verwalten und Dich mit Freunden in anderen Netzwerken verbinden kannst. Normalerweise gibst Du dazu einfach ihre Adresse oder die URL der Seite im Kasten <em>Neuen Kontakt hinzufügen</em> ein."
 
-#: mod/newmember.php:60
+#: mod/newmember.php:55
 msgid "Go to Your Site's Directory"
 msgstr "Gehe zum Verzeichnis Deiner Friendica Instanz"
 
-#: mod/newmember.php:60
+#: mod/newmember.php:55
 msgid ""
 "The Directory page lets you find other people in this network or other "
 "federated sites. Look for a <em>Connect</em> or <em>Follow</em> link on "
 "their profile page. Provide your own Identity Address if requested."
 msgstr "Über die Verzeichnisseite kannst Du andere Personen auf diesem Server oder anderen verknüpften Seiten finden. Halte nach einem <em>Verbinden</em> oder <em>Folgen</em> Link auf deren Profilseiten Ausschau und gib Deine eigene Profiladresse an, falls Du danach gefragt wirst."
 
-#: mod/newmember.php:62
+#: mod/newmember.php:57
 msgid "Finding New People"
 msgstr "Neue Leute kennenlernen"
 
-#: mod/newmember.php:62
+#: mod/newmember.php:57
 msgid ""
 "On the side panel of the Contacts page are several tools to find new "
 "friends. We can match people by interest, look up people by name or "
@@ -747,41 +731,41 @@ msgid ""
 "hours."
 msgstr "Im seitlichen Bedienfeld der Kontakteseite gibt es diverse Werkzeuge, um neue Freunde zu finden. Wir können Menschen mit den gleichen Interessen finden, anhand von Namen oder Interessen suchen oder aber aufgrund vorhandener Kontakte neue Freunde vorschlagen.\nAuf einer brandneuen - soeben erstellten - Seite starten die Kontaktvorschläge innerhalb von 24 Stunden."
 
-#: mod/newmember.php:66 include/group.php:283
+#: mod/newmember.php:61 include/group.php:283
 msgid "Groups"
 msgstr "Gruppen"
 
-#: mod/newmember.php:70
+#: mod/newmember.php:65
 msgid "Group Your Contacts"
 msgstr "Gruppiere deine Kontakte"
 
-#: mod/newmember.php:70
+#: mod/newmember.php:65
 msgid ""
 "Once you have made some friends, organize them into private conversation "
 "groups from the sidebar of your Contacts page and then you can interact with"
 " each group privately on your Network page."
 msgstr "Sobald Du einige Freunde gefunden hast, organisiere sie in Gruppen zur privaten Kommunikation im Seitenmenü der Kontakte-Seite. Du kannst dann mit jeder dieser Gruppen von der Netzwerkseite aus privat interagieren."
 
-#: mod/newmember.php:73
+#: mod/newmember.php:68
 msgid "Why Aren't My Posts Public?"
 msgstr "Warum sind meine Beiträge nicht öffentlich?"
 
-#: mod/newmember.php:73
+#: mod/newmember.php:68
 msgid ""
 "Friendica respects your privacy. By default, your posts will only show up to"
 " people you've added as friends. For more information, see the help section "
 "from the link above."
 msgstr "Friendica respektiert Deine Privatsphäre. Mit der Grundeinstellung werden Deine Beiträge ausschließlich Deinen Kontakten angezeigt. Für weitere Informationen diesbezüglich lies Dir bitte den entsprechenden Abschnitt in der Hilfe unter dem obigen Link durch."
 
-#: mod/newmember.php:78
+#: mod/newmember.php:73
 msgid "Getting Help"
 msgstr "Hilfe bekommen"
 
-#: mod/newmember.php:82
+#: mod/newmember.php:77
 msgid "Go to the Help Section"
 msgstr "Zum Hilfe Abschnitt gehen"
 
-#: mod/newmember.php:82
+#: mod/newmember.php:77
 msgid ""
 "Our <strong>help</strong> pages may be consulted for detail on other program"
 " features and resources."
@@ -796,7 +780,7 @@ msgid ""
 "Account not found and OpenID registration is not permitted on this site."
 msgstr "Nutzerkonto wurde nicht gefunden und OpenID-Registrierung ist auf diesem Server nicht gestattet."
 
-#: mod/openid.php:93 include/auth.php:112 include/auth.php:175
+#: mod/openid.php:93 include/auth.php:118 include/auth.php:181
 msgid "Login failed."
 msgstr "Anmeldung fehlgeschlagen."
 
@@ -882,18 +866,18 @@ msgstr "Bild erfolgreich hochgeladen."
 msgid "Image upload failed."
 msgstr "Hochladen des Bildes gescheitert."
 
-#: mod/subthread.php:87 mod/tagger.php:62 mod/like.php:168
+#: mod/subthread.php:87 mod/tagger.php:62 include/like.php:165
 #: include/conversation.php:130 include/conversation.php:266
-#: include/text.php:1993 include/diaspora.php:2147
+#: include/text.php:2000 include/diaspora.php:2169
 #: view/theme/diabook/theme.php:471
 msgid "photo"
 msgstr "Foto"
 
-#: mod/subthread.php:87 mod/tagger.php:62 mod/like.php:168 mod/like.php:346
-#: include/conversation.php:125 include/conversation.php:134
-#: include/conversation.php:261 include/conversation.php:270
-#: include/diaspora.php:2147 view/theme/diabook/theme.php:466
-#: view/theme/diabook/theme.php:475
+#: mod/subthread.php:87 mod/tagger.php:62 include/like.php:165
+#: include/like.php:334 include/conversation.php:125
+#: include/conversation.php:134 include/conversation.php:261
+#: include/conversation.php:270 include/diaspora.php:2169
+#: view/theme/diabook/theme.php:466 view/theme/diabook/theme.php:475
 msgid "status"
 msgstr "Status"
 
@@ -954,8 +938,8 @@ msgstr "Ignoriert"
 msgid "Keep this window open until done."
 msgstr "Lasse dieses Fenster offen, bis der Vorgang abgeschlossen ist."
 
-#: mod/filer.php:30 include/conversation.php:1133
-#: include/conversation.php:1151
+#: mod/filer.php:30 include/conversation.php:1132
+#: include/conversation.php:1150
 msgid "Save to Folder:"
 msgstr "In diesem Ordner speichern:"
 
@@ -968,54 +952,54 @@ msgstr "- auswählen -"
 msgid "Save"
 msgstr "Speichern"
 
-#: mod/follow.php:18 mod/dfrn_request.php:863
+#: mod/follow.php:19 mod/dfrn_request.php:870
 msgid "Submit Request"
 msgstr "Anfrage abschicken"
 
-#: mod/follow.php:29
+#: mod/follow.php:30
 msgid "You already added this contact."
 msgstr "Du hast den Kontakt bereits hinzugefügt."
 
-#: mod/follow.php:38
+#: mod/follow.php:39
 msgid "Diaspora support isn't enabled. Contact can't be added."
 msgstr "Diaspora Unterstützung ist nicht aktiviert. Der Kontakt kann nicht zugefügt werden."
 
-#: mod/follow.php:45
+#: mod/follow.php:46
 msgid "OStatus support is disabled. Contact can't be added."
 msgstr "OStatus Unterstützung ist nicht aktiviert. Der Kontakt kann nicht zugefügt werden."
 
-#: mod/follow.php:52
+#: mod/follow.php:53
 msgid "The network type couldn't be detected. Contact can't be added."
 msgstr "Der Netzwerktype wurde nicht erkannt. Der Kontakt kann nicht hinzugefügt werden."
 
-#: mod/follow.php:104 mod/dfrn_request.php:849
+#: mod/follow.php:109 mod/dfrn_request.php:856
 msgid "Please answer the following:"
 msgstr "Bitte beantworte folgendes:"
 
-#: mod/follow.php:105 mod/dfrn_request.php:850
+#: mod/follow.php:110 mod/dfrn_request.php:857
 #, php-format
 msgid "Does %s know you?"
 msgstr "Kennt %s Dich?"
 
-#: mod/follow.php:105 mod/settings.php:1094 mod/settings.php:1100
-#: mod/settings.php:1108 mod/settings.php:1112 mod/settings.php:1117
-#: mod/settings.php:1123 mod/settings.php:1129 mod/settings.php:1135
-#: mod/settings.php:1161 mod/settings.php:1162 mod/settings.php:1163
-#: mod/settings.php:1164 mod/settings.php:1165 mod/dfrn_request.php:850
+#: mod/follow.php:110 mod/settings.php:1103 mod/settings.php:1109
+#: mod/settings.php:1117 mod/settings.php:1121 mod/settings.php:1126
+#: mod/settings.php:1132 mod/settings.php:1138 mod/settings.php:1144
+#: mod/settings.php:1170 mod/settings.php:1171 mod/settings.php:1172
+#: mod/settings.php:1173 mod/settings.php:1174 mod/dfrn_request.php:857
 #: mod/register.php:239 mod/profiles.php:658 mod/profiles.php:662
 #: mod/profiles.php:687 mod/api.php:106
 msgid "No"
 msgstr "Nein"
 
-#: mod/follow.php:106 mod/dfrn_request.php:854
+#: mod/follow.php:111 mod/dfrn_request.php:861
 msgid "Add a personal note:"
 msgstr "Eine persönliche Notiz beifügen:"
 
-#: mod/follow.php:112 mod/dfrn_request.php:860
+#: mod/follow.php:117 mod/dfrn_request.php:867
 msgid "Your Identity Address:"
 msgstr "Adresse Deines Profils:"
 
-#: mod/follow.php:162
+#: mod/follow.php:180
 msgid "Contact added"
 msgstr "Kontakt hinzugefügt"
 
@@ -1023,39 +1007,39 @@ msgstr "Kontakt hinzugefügt"
 msgid "Unable to locate original post."
 msgstr "Konnte den Originalbeitrag nicht finden."
 
-#: mod/item.php:318
+#: mod/item.php:329
 msgid "Empty post discarded."
 msgstr "Leerer Beitrag wurde verworfen."
 
-#: mod/item.php:456 mod/wall_upload.php:213 mod/wall_upload.php:227
-#: mod/wall_upload.php:234 include/Photo.php:954 include/Photo.php:969
-#: include/Photo.php:976 include/Photo.php:998 include/message.php:145
+#: mod/item.php:467 mod/wall_upload.php:213 mod/wall_upload.php:227
+#: mod/wall_upload.php:234 include/Photo.php:958 include/Photo.php:973
+#: include/Photo.php:980 include/Photo.php:1002 include/message.php:145
 msgid "Wall Photos"
 msgstr "Pinnwand-Bilder"
 
-#: mod/item.php:830
+#: mod/item.php:842
 msgid "System error. Post not saved."
 msgstr "Systemfehler. Beitrag konnte nicht gespeichert werden."
 
-#: mod/item.php:959
+#: mod/item.php:971
 #, php-format
 msgid ""
 "This message was sent to you by %s, a member of the Friendica social "
 "network."
 msgstr "Diese Nachricht wurde dir von %s geschickt, einem Mitglied des Sozialen Netzwerks Friendica."
 
-#: mod/item.php:961
+#: mod/item.php:973
 #, php-format
 msgid "You may visit them online at %s"
 msgstr "Du kannst sie online unter %s besuchen"
 
-#: mod/item.php:962
+#: mod/item.php:974
 msgid ""
 "Please contact the sender by replying to this post if you do not wish to "
 "receive these messages."
 msgstr "Falls Du diese Beiträge nicht erhalten möchtest, kontaktiere bitte den Autor, indem Du auf diese Nachricht antwortest."
 
-#: mod/item.php:966
+#: mod/item.php:978
 #, php-format
 msgid "%s posted an update."
 msgstr "%s hat ein Update veröffentlicht."
@@ -1104,7 +1088,7 @@ msgstr "Gruppeneditor"
 msgid "Members"
 msgstr "Mitglieder"
 
-#: mod/group.php:193 mod/network.php:563 mod/content.php:130
+#: mod/group.php:193 mod/network.php:576 mod/content.php:130
 msgid "Group is empty"
 msgstr "Gruppe ist leer"
 
@@ -1206,7 +1190,7 @@ msgstr "Deine Kontaktreferenzen konnten nicht in unserem System gespeichert werd
 msgid "Unable to update your contact profile details on our system"
 msgstr "Die Updates für Dein Profil konnten nicht gespeichert werden"
 
-#: mod/dfrn_confirm.php:753 mod/dfrn_request.php:734 include/items.php:4273
+#: mod/dfrn_confirm.php:753 mod/dfrn_request.php:741 include/items.php:4299
 msgid "[Name Withheld]"
 msgstr "[Name unterdrückt]"
 
@@ -1215,7 +1199,7 @@ msgstr "[Name unterdrückt]"
 msgid "%1$s has joined %2$s"
 msgstr "%1$s ist %2$s beigetreten"
 
-#: mod/profile.php:21 include/identity.php:53
+#: mod/profile.php:21 include/identity.php:51
 msgid "Requested profile is not available."
 msgstr "Das angefragte Profil ist nicht vorhanden."
 
@@ -1239,7 +1223,7 @@ msgstr "Keine Videos  ausgewählt"
 msgid "Access to this item is restricted."
 msgstr "Zugriff zu diesem Eintrag wurde eingeschränkt."
 
-#: mod/videos.php:383 include/text.php:1465
+#: mod/videos.php:383 include/text.php:1472
 msgid "View Video"
 msgstr "Video ansehen"
 
@@ -1275,7 +1259,7 @@ msgstr "Schlage %s einen Kontakt vor"
 
 #: mod/wall_upload.php:20 mod/wall_upload.php:33 mod/wall_upload.php:86
 #: mod/wall_upload.php:122 mod/wall_upload.php:125 mod/wall_attach.php:17
-#: mod/wall_attach.php:25 mod/wall_attach.php:76 include/api.php:1735
+#: mod/wall_attach.php:25 mod/wall_attach.php:76 include/api.php:1781
 msgid "Invalid request."
 msgstr "Ungültige Anfrage"
 
@@ -1331,7 +1315,7 @@ msgid ""
 "Password reset failed."
 msgstr "Anfrage konnte nicht verifiziert werden. (Eventuell hast Du bereits eine ähnliche Anfrage gestellt.) Zurücksetzen des Passworts gescheitert."
 
-#: mod/lostpass.php:109 boot.php:1310
+#: mod/lostpass.php:109 boot.php:1444
 msgid "Password Reset"
 msgstr "Passwort zurücksetzen"
 
@@ -1405,37 +1389,6 @@ msgstr "Spitzname oder E-Mail:"
 msgid "Reset"
 msgstr "Zurücksetzen"
 
-#: mod/like.php:170 include/conversation.php:122 include/conversation.php:258
-#: include/text.php:1991 view/theme/diabook/theme.php:463
-msgid "event"
-msgstr "Event"
-
-#: mod/like.php:187 include/conversation.php:141 include/diaspora.php:2163
-#: view/theme/diabook/theme.php:480
-#, php-format
-msgid "%1$s likes %2$s's %3$s"
-msgstr "%1$s mag %2$ss %3$s"
-
-#: mod/like.php:189 include/conversation.php:144
-#, php-format
-msgid "%1$s doesn't like %2$s's %3$s"
-msgstr "%1$s mag %2$ss %3$s nicht"
-
-#: mod/like.php:191
-#, php-format
-msgid "%1$s is attending %2$s's %3$s"
-msgstr "%1$s nimmt an %2$ss %3$s teil."
-
-#: mod/like.php:193
-#, php-format
-msgid "%1$s is not attending %2$s's %3$s"
-msgstr "%1$s nimmt nicht an %2$ss %3$s teil."
-
-#: mod/like.php:195
-#, php-format
-msgid "%1$s may attend %2$s's %3$s"
-msgstr "%1$s nimmt eventuell an %2$ss %3$s teil."
-
 #: mod/ping.php:265
 msgid "{0} wants to be your friend"
 msgstr "{0} möchte mit Dir in Kontakt treten"
@@ -1465,11 +1418,11 @@ msgstr "Verwerfen"
 msgid "System"
 msgstr "System"
 
-#: mod/notifications.php:87 mod/admin.php:228 include/nav.php:154
+#: mod/notifications.php:87 mod/admin.php:390 include/nav.php:154
 msgid "Network"
 msgstr "Netzwerk"
 
-#: mod/notifications.php:93 mod/network.php:381
+#: mod/notifications.php:93 mod/network.php:384
 msgid "Personal"
 msgstr "Persönlich"
 
@@ -1511,7 +1464,7 @@ msgstr "Neue-Kontakt Nachricht senden"
 msgid "if applicable"
 msgstr "falls anwendbar"
 
-#: mod/notifications.php:176 mod/notifications.php:257 mod/admin.php:1116
+#: mod/notifications.php:176 mod/notifications.php:257 mod/admin.php:1308
 msgid "Approve"
 msgstr "Genehmigen"
 
@@ -1561,8 +1514,8 @@ msgstr "Kontakt-/Freundschaftsanfrage"
 msgid "New Follower"
 msgstr "Neuer Bewunderer"
 
-#: mod/notifications.php:250 mod/directory.php:147 include/identity.php:306
-#: include/identity.php:589
+#: mod/notifications.php:250 mod/directory.php:147 include/identity.php:310
+#: include/identity.php:590
 msgid "Gender:"
 msgstr "Geschlecht:"
 
@@ -1733,7 +1686,7 @@ msgstr "Unterhaltung gelöscht."
 
 #: mod/message.php:290 mod/message.php:298 mod/message.php:427
 #: mod/message.php:435 mod/wallmessage.php:127 mod/wallmessage.php:135
-#: include/conversation.php:1129 include/conversation.php:1147
+#: include/conversation.php:1128 include/conversation.php:1146
 msgid "Please enter a link URL:"
 msgstr "Bitte gib die URL des Links ein:"
 
@@ -1755,19 +1708,19 @@ msgid "Your message:"
 msgstr "Deine Nachricht:"
 
 #: mod/message.php:339 mod/message.php:523 mod/wallmessage.php:154
-#: mod/editpost.php:110 include/conversation.php:1184
+#: mod/editpost.php:110 include/conversation.php:1183
 msgid "Upload photo"
 msgstr "Foto hochladen"
 
 #: mod/message.php:340 mod/message.php:524 mod/wallmessage.php:155
-#: mod/editpost.php:114 include/conversation.php:1188
+#: mod/editpost.php:114 include/conversation.php:1187
 msgid "Insert web link"
 msgstr "Einen Link einfügen"
 
 #: mod/message.php:341 mod/message.php:526 mod/content.php:501
 #: mod/content.php:885 mod/wallmessage.php:156 mod/editpost.php:124
 #: mod/photos.php:1610 object/Item.php:396 include/conversation.php:713
-#: include/conversation.php:1202
+#: include/conversation.php:1201
 msgid "Please wait"
 msgstr "Bitte warten"
 
@@ -1783,7 +1736,7 @@ msgstr "Nachricht nicht verfügbar."
 msgid "Delete message"
 msgstr "Nachricht löschen"
 
-#: mod/message.php:507 mod/message.php:582
+#: mod/message.php:507 mod/message.php:584
 msgid "Delete conversation"
 msgstr "Unterhaltung löschen"
 
@@ -1797,26 +1750,26 @@ msgstr "Sichere Kommunikation ist nicht verfügbar. <strong>Eventuell</strong> k
 msgid "Send Reply"
 msgstr "Antwort senden"
 
-#: mod/message.php:555
+#: mod/message.php:557
 #, php-format
 msgid "Unknown sender - %s"
 msgstr "'Unbekannter Absender - %s"
 
-#: mod/message.php:558
+#: mod/message.php:560
 #, php-format
 msgid "You and %s"
 msgstr "Du und %s"
 
-#: mod/message.php:561
+#: mod/message.php:563
 #, php-format
 msgid "%s and You"
 msgstr "%s und Du"
 
-#: mod/message.php:585
+#: mod/message.php:587
 msgid "D, d M Y - g:i A"
 msgstr "D, d. M Y - g:i A"
 
-#: mod/message.php:588
+#: mod/message.php:590
 #, php-format
 msgid "%d message"
 msgid_plural "%d messages"
@@ -1868,9 +1821,9 @@ msgstr "Zurück zum Kontakteditor"
 msgid "Refetch contact data"
 msgstr "Kontaktdaten neu laden"
 
-#: mod/crepair.php:170 mod/admin.php:1114 mod/admin.php:1126
-#: mod/admin.php:1127 mod/admin.php:1140 mod/settings.php:652
-#: mod/settings.php:678
+#: mod/crepair.php:170 mod/admin.php:1306 mod/admin.php:1318
+#: mod/admin.php:1319 mod/admin.php:1332 mod/settings.php:661
+#: mod/settings.php:687
 msgid "Name"
 msgstr "Name"
 
@@ -1920,7 +1873,7 @@ msgid ""
 "entries from this contact."
 msgstr "Markiere diesen Kontakt als remote_self (entferntes Konto), dies veranlasst Friendica alle Top-Level Beiträge dieses Kontakts an all Deine Kontakte zu senden."
 
-#: mod/bookmarklet.php:12 boot.php:1296 include/nav.php:91
+#: mod/bookmarklet.php:12 boot.php:1430 include/nav.php:91
 msgid "Login"
 msgstr "Anmeldung"
 
@@ -1932,24 +1885,24 @@ msgstr "Der Beitrag wurde angelegt"
 msgid "Access denied."
 msgstr "Zugriff verweigert."
 
-#: mod/dirfind.php:188 mod/allfriends.php:80 mod/match.php:85
-#: mod/suggest.php:98 include/contact_widgets.php:10 include/identity.php:209
+#: mod/dirfind.php:194 mod/allfriends.php:80 mod/match.php:85
+#: mod/suggest.php:98 include/contact_widgets.php:10 include/identity.php:212
 msgid "Connect"
 msgstr "Verbinden"
 
-#: mod/dirfind.php:189 mod/allfriends.php:64 mod/match.php:70
-#: mod/directory.php:162 mod/suggest.php:81 include/Contact.php:296
-#: include/Contact.php:309 include/Contact.php:351
+#: mod/dirfind.php:195 mod/allfriends.php:64 mod/match.php:70
+#: mod/directory.php:162 mod/suggest.php:81 include/Contact.php:283
+#: include/Contact.php:296 include/Contact.php:338
 #: include/conversation.php:912 include/conversation.php:926
 msgid "View Profile"
 msgstr "Profil anschauen"
 
-#: mod/dirfind.php:218
+#: mod/dirfind.php:224
 #, php-format
 msgid "People Search - %s"
 msgstr "Personensuche - %s"
 
-#: mod/dirfind.php:225 mod/match.php:105
+#: mod/dirfind.php:231 mod/match.php:105
 msgid "No matches"
 msgstr "Keine Übereinstimmungen"
 
@@ -1973,548 +1926,579 @@ msgstr "Dateien"
 msgid "Contacts who are not members of a group"
 msgstr "Kontakte, die keiner Gruppe zugewiesen sind"
 
-#: mod/admin.php:80
+#: mod/admin.php:92
 msgid "Theme settings updated."
 msgstr "Themeneinstellungen aktualisiert."
 
-#: mod/admin.php:127 mod/admin.php:713
+#: mod/admin.php:156 mod/admin.php:888
 msgid "Site"
 msgstr "Seite"
 
-#: mod/admin.php:128 mod/admin.php:657 mod/admin.php:1109 mod/admin.php:1124
+#: mod/admin.php:157 mod/admin.php:832 mod/admin.php:1301 mod/admin.php:1316
 msgid "Users"
 msgstr "Nutzer"
 
-#: mod/admin.php:129 mod/admin.php:1213 mod/admin.php:1273 mod/settings.php:66
+#: mod/admin.php:158 mod/admin.php:1416 mod/admin.php:1476 mod/settings.php:72
 msgid "Plugins"
 msgstr "Plugins"
 
-#: mod/admin.php:130 mod/admin.php:1458 mod/admin.php:1509
+#: mod/admin.php:159 mod/admin.php:1674 mod/admin.php:1724
 msgid "Themes"
 msgstr "Themen"
 
-#: mod/admin.php:131
+#: mod/admin.php:160 mod/settings.php:50
+msgid "Additional features"
+msgstr "Zusätzliche Features"
+
+#: mod/admin.php:161
 msgid "DB updates"
 msgstr "DB Updates"
 
-#: mod/admin.php:132 mod/admin.php:223
+#: mod/admin.php:162 mod/admin.php:385
 msgid "Inspect Queue"
 msgstr "Warteschlange Inspizieren"
 
-#: mod/admin.php:147 mod/admin.php:156 mod/admin.php:1597
+#: mod/admin.php:163 mod/admin.php:354
+msgid "Federation Statistics"
+msgstr "Federation Statistik"
+
+#: mod/admin.php:177 mod/admin.php:188 mod/admin.php:1792
 msgid "Logs"
 msgstr "Protokolle"
 
-#: mod/admin.php:148
+#: mod/admin.php:178 mod/admin.php:1859
+msgid "View Logs"
+msgstr "Protokolle anzeigen"
+
+#: mod/admin.php:179
 msgid "probe address"
 msgstr "Adresse untersuchen"
 
-#: mod/admin.php:149
+#: mod/admin.php:180
 msgid "check webfinger"
 msgstr "Webfinger überprüfen"
 
-#: mod/admin.php:154 include/nav.php:194
+#: mod/admin.php:186 include/nav.php:194
 msgid "Admin"
 msgstr "Administration"
 
-#: mod/admin.php:155
+#: mod/admin.php:187
 msgid "Plugin Features"
 msgstr "Plugin Features"
 
-#: mod/admin.php:157
+#: mod/admin.php:189
 msgid "diagnostics"
 msgstr "Diagnose"
 
-#: mod/admin.php:158
+#: mod/admin.php:190
 msgid "User registrations waiting for confirmation"
 msgstr "Nutzeranmeldungen die auf Bestätigung warten"
 
-#: mod/admin.php:222 mod/admin.php:272 mod/admin.php:712 mod/admin.php:1108
-#: mod/admin.php:1212 mod/admin.php:1272 mod/admin.php:1457 mod/admin.php:1508
-#: mod/admin.php:1596
+#: mod/admin.php:347
+msgid ""
+"This page offers you some numbers to the known part of the federated social "
+"network your Friendica node is part of. These numbers are not complete but "
+"only reflect the part of the network your node is aware of."
+msgstr "Diese Seite präsentiert einige Zahlen zu dem bekannten Teil des föderalen sozialen Netzwerks, von dem deine Friendica Installation ein Teil ist. Diese Zahlen sind nicht absolut und reflektieren nur den Teil des Netzwerks, den dein Knoten kennt."
+
+#: mod/admin.php:348
+msgid ""
+"The <em>Auto Discovered Contact Directory</em> feature is not enabled, it "
+"will improve the data displayed here."
+msgstr "Die Funktion um <em>Automatisch ein Kontaktverzeichnis erstellen</em> ist nicht aktiv. Es wird die hier angezeigten Daten verbessern."
+
+#: mod/admin.php:353 mod/admin.php:384 mod/admin.php:441 mod/admin.php:887
+#: mod/admin.php:1300 mod/admin.php:1415 mod/admin.php:1475 mod/admin.php:1673
+#: mod/admin.php:1723 mod/admin.php:1791 mod/admin.php:1858
 msgid "Administration"
 msgstr "Administration"
 
-#: mod/admin.php:225
+#: mod/admin.php:360
+#, php-format
+msgid "Currently this node is aware of %d nodes from the following platforms:"
+msgstr "Momentan kennt dieser Knoten %d andere Knoten der folgenden Plattformen:"
+
+#: mod/admin.php:387
 msgid "ID"
 msgstr "ID"
 
-#: mod/admin.php:226
+#: mod/admin.php:388
 msgid "Recipient Name"
 msgstr "Empfänger Name"
 
-#: mod/admin.php:227
+#: mod/admin.php:389
 msgid "Recipient Profile"
 msgstr "Empfänger Profil"
 
-#: mod/admin.php:229
+#: mod/admin.php:391
 msgid "Created"
 msgstr "Erstellt"
 
-#: mod/admin.php:230
+#: mod/admin.php:392
 msgid "Last Tried"
 msgstr "Zuletzt versucht"
 
-#: mod/admin.php:231
+#: mod/admin.php:393
 msgid ""
 "This page lists the content of the queue for outgoing postings. These are "
 "postings the initial delivery failed for. They will be resend later and "
 "eventually deleted if the delivery fails permanently."
 msgstr "Auf dieser Seite werden die in der Warteschlange eingereihten Beiträge aufgelistet. Bei diesen Beiträgen schlug die erste Zustellung fehl. Es wird später wiederholt versucht die Beiträge zuzustellen, bis sie schließlich gelöscht werden."
 
-#: mod/admin.php:243 mod/admin.php:1062
+#: mod/admin.php:412 mod/admin.php:1254
 msgid "Normal Account"
 msgstr "Normales Konto"
 
-#: mod/admin.php:244 mod/admin.php:1063
+#: mod/admin.php:413 mod/admin.php:1255
 msgid "Soapbox Account"
 msgstr "Marktschreier-Konto"
 
-#: mod/admin.php:245 mod/admin.php:1064
+#: mod/admin.php:414 mod/admin.php:1256
 msgid "Community/Celebrity Account"
 msgstr "Forum/Promi-Konto"
 
-#: mod/admin.php:246 mod/admin.php:1065
+#: mod/admin.php:415 mod/admin.php:1257
 msgid "Automatic Friend Account"
 msgstr "Automatisches Freundekonto"
 
-#: mod/admin.php:247
+#: mod/admin.php:416
 msgid "Blog Account"
 msgstr "Blog-Konto"
 
-#: mod/admin.php:248
+#: mod/admin.php:417
 msgid "Private Forum"
 msgstr "Privates Forum"
 
-#: mod/admin.php:267
+#: mod/admin.php:436
 msgid "Message queues"
 msgstr "Nachrichten-Warteschlangen"
 
-#: mod/admin.php:273
+#: mod/admin.php:442
 msgid "Summary"
 msgstr "Zusammenfassung"
 
-#: mod/admin.php:275
+#: mod/admin.php:444
 msgid "Registered users"
 msgstr "Registrierte Nutzer"
 
-#: mod/admin.php:277
+#: mod/admin.php:446
 msgid "Pending registrations"
 msgstr "Anstehende Anmeldungen"
 
-#: mod/admin.php:278
+#: mod/admin.php:447
 msgid "Version"
 msgstr "Version"
 
-#: mod/admin.php:283
+#: mod/admin.php:452
 msgid "Active plugins"
 msgstr "Aktive Plugins"
 
-#: mod/admin.php:306
+#: mod/admin.php:475
 msgid "Can not parse base url. Must have at least <scheme>://<domain>"
 msgstr "Die Basis-URL konnte nicht analysiert werden. Sie muss mindestens aus <protokoll>://<domain> bestehen"
 
-#: mod/admin.php:589
+#: mod/admin.php:760
 msgid "RINO2 needs mcrypt php extension to work."
 msgstr "RINO2 benötigt die PHP Extension mcrypt."
 
-#: mod/admin.php:597
+#: mod/admin.php:768
 msgid "Site settings updated."
 msgstr "Seiteneinstellungen aktualisiert."
 
-#: mod/admin.php:621 mod/settings.php:903
+#: mod/admin.php:796 mod/settings.php:912
 msgid "No special theme for mobile devices"
 msgstr "Kein spezielles Theme für mobile Geräte verwenden."
 
-#: mod/admin.php:640
+#: mod/admin.php:815
 msgid "No community page"
 msgstr "Keine Gemeinschaftsseite"
 
-#: mod/admin.php:641
+#: mod/admin.php:816
 msgid "Public postings from users of this site"
 msgstr "Öffentliche Beiträge von Nutzer_innen dieser Seite"
 
-#: mod/admin.php:642
+#: mod/admin.php:817
 msgid "Global community page"
 msgstr "Globale Gemeinschaftsseite"
 
-#: mod/admin.php:648
+#: mod/admin.php:823
 msgid "At post arrival"
 msgstr "Beim Empfang von Nachrichten"
 
-#: mod/admin.php:649 include/contact_selectors.php:56
+#: mod/admin.php:824 include/contact_selectors.php:56
 msgid "Frequently"
 msgstr "immer wieder"
 
-#: mod/admin.php:650 include/contact_selectors.php:57
+#: mod/admin.php:825 include/contact_selectors.php:57
 msgid "Hourly"
 msgstr "Stündlich"
 
-#: mod/admin.php:651 include/contact_selectors.php:58
+#: mod/admin.php:826 include/contact_selectors.php:58
 msgid "Twice daily"
 msgstr "Zweimal täglich"
 
-#: mod/admin.php:652 include/contact_selectors.php:59
+#: mod/admin.php:827 include/contact_selectors.php:59
 msgid "Daily"
 msgstr "Täglich"
 
-#: mod/admin.php:658
+#: mod/admin.php:833
 msgid "Users, Global Contacts"
 msgstr "Nutzer, globale Kontakte"
 
-#: mod/admin.php:659
+#: mod/admin.php:834
 msgid "Users, Global Contacts/fallback"
 msgstr "Nutzer, globale Kontakte / Fallback"
 
-#: mod/admin.php:663
+#: mod/admin.php:838
 msgid "One month"
 msgstr "ein Monat"
 
-#: mod/admin.php:664
+#: mod/admin.php:839
 msgid "Three months"
 msgstr "drei Monate"
 
-#: mod/admin.php:665
+#: mod/admin.php:840
 msgid "Half a year"
 msgstr "ein halbes Jahr"
 
-#: mod/admin.php:666
+#: mod/admin.php:841
 msgid "One year"
 msgstr "ein Jahr"
 
-#: mod/admin.php:671
+#: mod/admin.php:846
 msgid "Multi user instance"
 msgstr "Mehrbenutzer Instanz"
 
-#: mod/admin.php:694
+#: mod/admin.php:869
 msgid "Closed"
 msgstr "Geschlossen"
 
-#: mod/admin.php:695
+#: mod/admin.php:870
 msgid "Requires approval"
 msgstr "Bedarf der Zustimmung"
 
-#: mod/admin.php:696
+#: mod/admin.php:871
 msgid "Open"
 msgstr "Offen"
 
-#: mod/admin.php:700
+#: mod/admin.php:875
 msgid "No SSL policy, links will track page SSL state"
 msgstr "Keine SSL Richtlinie, Links werden das verwendete Protokoll beibehalten"
 
-#: mod/admin.php:701
+#: mod/admin.php:876
 msgid "Force all links to use SSL"
 msgstr "SSL für alle Links erzwingen"
 
-#: mod/admin.php:702
+#: mod/admin.php:877
 msgid "Self-signed certificate, use SSL for local links only (discouraged)"
 msgstr "Selbst-unterzeichnetes Zertifikat, SSL nur für lokale Links verwenden (nicht empfohlen)"
 
-#: mod/admin.php:714 mod/admin.php:1274 mod/admin.php:1510 mod/admin.php:1598
-#: mod/settings.php:650 mod/settings.php:760 mod/settings.php:804
-#: mod/settings.php:873 mod/settings.php:960 mod/settings.php:1195
+#: mod/admin.php:889 mod/admin.php:1477 mod/admin.php:1725 mod/admin.php:1793
+#: mod/admin.php:1942 mod/settings.php:659 mod/settings.php:769
+#: mod/settings.php:813 mod/settings.php:882 mod/settings.php:969
+#: mod/settings.php:1204
 msgid "Save Settings"
 msgstr "Einstellungen speichern"
 
-#: mod/admin.php:715 mod/register.php:263
+#: mod/admin.php:890 mod/register.php:263
 msgid "Registration"
 msgstr "Registrierung"
 
-#: mod/admin.php:716
+#: mod/admin.php:891
 msgid "File upload"
 msgstr "Datei hochladen"
 
-#: mod/admin.php:717
+#: mod/admin.php:892
 msgid "Policies"
 msgstr "Regeln"
 
-#: mod/admin.php:718
+#: mod/admin.php:893
 msgid "Advanced"
 msgstr "Erweitert"
 
-#: mod/admin.php:719
+#: mod/admin.php:894
 msgid "Auto Discovered Contact Directory"
 msgstr "Automatisch ein Kontaktverzeichnis erstellen"
 
-#: mod/admin.php:720
+#: mod/admin.php:895
 msgid "Performance"
 msgstr "Performance"
 
-#: mod/admin.php:721
+#: mod/admin.php:896
 msgid ""
 "Relocate - WARNING: advanced function. Could make this server unreachable."
 msgstr "Umsiedeln - WARNUNG: Könnte diesen Server unerreichbar machen."
 
-#: mod/admin.php:724
+#: mod/admin.php:899
 msgid "Site name"
 msgstr "Seitenname"
 
-#: mod/admin.php:725
+#: mod/admin.php:900
 msgid "Host name"
 msgstr "Host Name"
 
-#: mod/admin.php:726
+#: mod/admin.php:901
 msgid "Sender Email"
 msgstr "Absender für Emails"
 
-#: mod/admin.php:726
+#: mod/admin.php:901
 msgid ""
 "The email address your server shall use to send notification emails from."
 msgstr "Die E-Mail Adresse die dein Server zum Versenden von Benachrichtigungen verwenden soll."
 
-#: mod/admin.php:727
+#: mod/admin.php:902
 msgid "Banner/Logo"
 msgstr "Banner/Logo"
 
-#: mod/admin.php:728
+#: mod/admin.php:903
 msgid "Shortcut icon"
 msgstr "Shortcut Icon"
 
-#: mod/admin.php:728
+#: mod/admin.php:903
 msgid "Link to an icon that will be used for browsers."
 msgstr "Link zu einem Icon, das Browser verwenden werden."
 
-#: mod/admin.php:729
+#: mod/admin.php:904
 msgid "Touch icon"
 msgstr "Touch Icon"
 
-#: mod/admin.php:729
+#: mod/admin.php:904
 msgid "Link to an icon that will be used for tablets and mobiles."
 msgstr "Link zu einem Icon das Tablets und Handies verwenden sollen."
 
-#: mod/admin.php:730
+#: mod/admin.php:905
 msgid "Additional Info"
 msgstr "Zusätzliche Informationen"
 
-#: mod/admin.php:730
+#: mod/admin.php:905
 #, php-format
 msgid ""
 "For public servers: you can add additional information here that will be "
 "listed at %s/siteinfo."
 msgstr "Für öffentliche Server kannst Du hier zusätzliche Informationen angeben, die dann auf %s/siteinfo angezeigt werden."
 
-#: mod/admin.php:731
+#: mod/admin.php:906
 msgid "System language"
 msgstr "Systemsprache"
 
-#: mod/admin.php:732
+#: mod/admin.php:907
 msgid "System theme"
 msgstr "Systemweites Theme"
 
-#: mod/admin.php:732
+#: mod/admin.php:907
 msgid ""
 "Default system theme - may be over-ridden by user profiles - <a href='#' "
 "id='cnftheme'>change theme settings</a>"
 msgstr "Vorgabe für das System-Theme - kann von Benutzerprofilen überschrieben werden - <a href='#' id='cnftheme'>Theme-Einstellungen ändern</a>"
 
-#: mod/admin.php:733
+#: mod/admin.php:908
 msgid "Mobile system theme"
 msgstr "Systemweites mobiles Theme"
 
-#: mod/admin.php:733
+#: mod/admin.php:908
 msgid "Theme for mobile devices"
 msgstr "Thema für mobile Geräte"
 
-#: mod/admin.php:734
+#: mod/admin.php:909
 msgid "SSL link policy"
 msgstr "Regeln für SSL Links"
 
-#: mod/admin.php:734
+#: mod/admin.php:909
 msgid "Determines whether generated links should be forced to use SSL"
 msgstr "Bestimmt, ob generierte Links SSL verwenden müssen"
 
-#: mod/admin.php:735
+#: mod/admin.php:910
 msgid "Force SSL"
 msgstr "Erzwinge SSL"
 
-#: mod/admin.php:735
+#: mod/admin.php:910
 msgid ""
 "Force all Non-SSL requests to SSL - Attention: on some systems it could lead"
 " to endless loops."
 msgstr "Erzinge alle Nicht-SSL Anfragen auf SSL - Achtung: auf manchen Systemen verursacht dies eine Endlosschleife."
 
-#: mod/admin.php:736
+#: mod/admin.php:911
 msgid "Old style 'Share'"
 msgstr "Altes \"Teilen\" Element"
 
-#: mod/admin.php:736
+#: mod/admin.php:911
 msgid "Deactivates the bbcode element 'share' for repeating items."
 msgstr "Deaktiviert das BBCode Element \"share\" beim Wiederholen von Beiträgen."
 
-#: mod/admin.php:737
+#: mod/admin.php:912
 msgid "Hide help entry from navigation menu"
 msgstr "Verberge den Menüeintrag für die Hilfe im Navigationsmenü"
 
-#: mod/admin.php:737
+#: mod/admin.php:912
 msgid ""
 "Hides the menu entry for the Help pages from the navigation menu. You can "
 "still access it calling /help directly."
 msgstr "Verbirgt den Menüeintrag für die Hilfe-Seiten im Navigationsmenü. Die Seiten können weiterhin über /help aufgerufen werden."
 
-#: mod/admin.php:738
+#: mod/admin.php:913
 msgid "Single user instance"
 msgstr "Ein-Nutzer Instanz"
 
-#: mod/admin.php:738
+#: mod/admin.php:913
 msgid "Make this instance multi-user or single-user for the named user"
 msgstr "Regelt ob es sich bei dieser Instanz um eine ein Personen Installation oder eine Installation mit mehr als einem Nutzer handelt."
 
-#: mod/admin.php:739
+#: mod/admin.php:914
 msgid "Maximum image size"
 msgstr "Maximale Bildgröße"
 
-#: mod/admin.php:739
+#: mod/admin.php:914
 msgid ""
 "Maximum size in bytes of uploaded images. Default is 0, which means no "
 "limits."
 msgstr "Maximale Uploadgröße von Bildern in Bytes. Standard ist 0, d.h. ohne Limit."
 
-#: mod/admin.php:740
+#: mod/admin.php:915
 msgid "Maximum image length"
 msgstr "Maximale Bildlänge"
 
-#: mod/admin.php:740
+#: mod/admin.php:915
 msgid ""
 "Maximum length in pixels of the longest side of uploaded images. Default is "
 "-1, which means no limits."
 msgstr "Maximale Länge in Pixeln der längsten Seite eines hoch geladenen Bildes. Grundeinstellung ist -1 was keine Einschränkung bedeutet."
 
-#: mod/admin.php:741
+#: mod/admin.php:916
 msgid "JPEG image quality"
 msgstr "Qualität des JPEG Bildes"
 
-#: mod/admin.php:741
+#: mod/admin.php:916
 msgid ""
 "Uploaded JPEGS will be saved at this quality setting [0-100]. Default is "
 "100, which is full quality."
 msgstr "Hoch geladene JPEG Bilder werden mit dieser Qualität [0-100] gespeichert. Grundeinstellung ist 100, kein Qualitätsverlust."
 
-#: mod/admin.php:743
+#: mod/admin.php:918
 msgid "Register policy"
 msgstr "Registrierungsmethode"
 
-#: mod/admin.php:744
+#: mod/admin.php:919
 msgid "Maximum Daily Registrations"
 msgstr "Maximum täglicher Registrierungen"
 
-#: mod/admin.php:744
+#: mod/admin.php:919
 msgid ""
 "If registration is permitted above, this sets the maximum number of new user"
 " registrations to accept per day.  If register is set to closed, this "
 "setting has no effect."
 msgstr "Wenn die Registrierung weiter oben erlaubt ist, regelt dies die maximale Anzahl von Neuanmeldungen pro Tag. Wenn die Registrierung geschlossen ist, hat diese Einstellung keinen Effekt."
 
-#: mod/admin.php:745
+#: mod/admin.php:920
 msgid "Register text"
 msgstr "Registrierungstext"
 
-#: mod/admin.php:745
+#: mod/admin.php:920
 msgid "Will be displayed prominently on the registration page."
 msgstr "Wird gut sichtbar auf der Registrierungsseite angezeigt."
 
-#: mod/admin.php:746
+#: mod/admin.php:921
 msgid "Accounts abandoned after x days"
 msgstr "Nutzerkonten gelten nach x Tagen als unbenutzt"
 
-#: mod/admin.php:746
+#: mod/admin.php:921
 msgid ""
 "Will not waste system resources polling external sites for abandonded "
 "accounts. Enter 0 for no time limit."
 msgstr "Verschwende keine System-Ressourcen auf das Pollen externer Seiten, wenn Konten nicht mehr benutzt werden. 0 eingeben für kein Limit."
 
-#: mod/admin.php:747
+#: mod/admin.php:922
 msgid "Allowed friend domains"
 msgstr "Erlaubte Domains für Kontakte"
 
-#: mod/admin.php:747
+#: mod/admin.php:922
 msgid ""
 "Comma separated list of domains which are allowed to establish friendships "
 "with this site. Wildcards are accepted. Empty to allow any domains"
 msgstr "Liste der Domains, die für Freundschaften erlaubt sind, durch Kommas getrennt. Platzhalter werden akzeptiert. Leer lassen, um alle Domains zu erlauben."
 
-#: mod/admin.php:748
+#: mod/admin.php:923
 msgid "Allowed email domains"
 msgstr "Erlaubte Domains für E-Mails"
 
-#: mod/admin.php:748
+#: mod/admin.php:923
 msgid ""
 "Comma separated list of domains which are allowed in email addresses for "
 "registrations to this site. Wildcards are accepted. Empty to allow any "
 "domains"
 msgstr "Liste der Domains, die für E-Mail-Adressen bei der Registrierung erlaubt sind, durch Kommas getrennt. Platzhalter werden akzeptiert. Leer lassen, um alle Domains zu erlauben."
 
-#: mod/admin.php:749
+#: mod/admin.php:924
 msgid "Block public"
 msgstr "Öffentlichen Zugriff blockieren"
 
-#: mod/admin.php:749
+#: mod/admin.php:924
 msgid ""
 "Check to block public access to all otherwise public personal pages on this "
 "site unless you are currently logged in."
 msgstr "Klicken, um öffentlichen Zugriff auf sonst öffentliche Profile zu blockieren, wenn man nicht eingeloggt ist."
 
-#: mod/admin.php:750
+#: mod/admin.php:925
 msgid "Force publish"
 msgstr "Erzwinge Veröffentlichung"
 
-#: mod/admin.php:750
+#: mod/admin.php:925
 msgid ""
 "Check to force all profiles on this site to be listed in the site directory."
 msgstr "Klicken, um Anzeige aller Profile dieses Servers im Verzeichnis zu erzwingen."
 
-#: mod/admin.php:751
+#: mod/admin.php:926
 msgid "Global directory URL"
 msgstr "URL des weltweiten Verzeichnisses"
 
-#: mod/admin.php:751
+#: mod/admin.php:926
 msgid ""
 "URL to the global directory. If this is not set, the global directory is "
 "completely unavailable to the application."
 msgstr "URL des weltweiten Verzeichnisses. Wenn diese nicht gesetzt ist, ist das Verzeichnis für die Applikation nicht erreichbar."
 
-#: mod/admin.php:752
+#: mod/admin.php:927
 msgid "Allow threaded items"
 msgstr "Erlaube Threads in Diskussionen"
 
-#: mod/admin.php:752
+#: mod/admin.php:927
 msgid "Allow infinite level threading for items on this site."
 msgstr "Erlaube ein unendliches Level für Threads auf dieser Seite."
 
-#: mod/admin.php:753
+#: mod/admin.php:928
 msgid "Private posts by default for new users"
 msgstr "Private Beiträge als Standard für neue Nutzer"
 
-#: mod/admin.php:753
+#: mod/admin.php:928
 msgid ""
 "Set default post permissions for all new members to the default privacy "
 "group rather than public."
 msgstr "Die Standard-Zugriffsrechte für neue Nutzer werden so gesetzt, dass als Voreinstellung in die private Gruppe gepostet wird anstelle von öffentlichen Beiträgen."
 
-#: mod/admin.php:754
+#: mod/admin.php:929
 msgid "Don't include post content in email notifications"
 msgstr "Inhalte von Beiträgen nicht in E-Mail-Benachrichtigungen versenden"
 
-#: mod/admin.php:754
+#: mod/admin.php:929
 msgid ""
 "Don't include the content of a post/comment/private message/etc. in the "
 "email notifications that are sent out from this site, as a privacy measure."
 msgstr "Inhalte von Beiträgen/Kommentaren/privaten Nachrichten/usw., zum Datenschutz nicht in E-Mail-Benachrichtigungen einbinden."
 
-#: mod/admin.php:755
+#: mod/admin.php:930
 msgid "Disallow public access to addons listed in the apps menu."
 msgstr "Öffentlichen Zugriff auf Addons im Apps Menü verbieten."
 
-#: mod/admin.php:755
+#: mod/admin.php:930
 msgid ""
 "Checking this box will restrict addons listed in the apps menu to members "
 "only."
 msgstr "Wenn ausgewählt werden die im Apps Menü aufgeführten Addons nur angemeldeten Nutzern der Seite zur Verfügung gestellt."
 
-#: mod/admin.php:756
+#: mod/admin.php:931
 msgid "Don't embed private images in posts"
 msgstr "Private Bilder nicht in Beiträgen einbetten."
 
-#: mod/admin.php:756
+#: mod/admin.php:931
 msgid ""
 "Don't replace locally-hosted private photos in posts with an embedded copy "
 "of the image. This means that contacts who receive posts containing private "
@@ -2522,228 +2506,228 @@ msgid ""
 "while."
 msgstr "Ersetze lokal gehostete private Fotos in Beiträgen nicht mit einer eingebetteten Kopie des Bildes. Dies bedeutet, dass Kontakte, die Beiträge mit privaten Fotos erhalten sich zunächst auf den jeweiligen Servern authentifizieren müssen bevor die Bilder geladen und angezeigt werden, was eine gewisse Zeit dauert."
 
-#: mod/admin.php:757
+#: mod/admin.php:932
 msgid "Allow Users to set remote_self"
 msgstr "Nutzern erlauben das remote_self Flag zu setzen"
 
-#: mod/admin.php:757
+#: mod/admin.php:932
 msgid ""
 "With checking this, every user is allowed to mark every contact as a "
 "remote_self in the repair contact dialog. Setting this flag on a contact "
 "causes mirroring every posting of that contact in the users stream."
 msgstr "Ist dies ausgewählt kann jeder Nutzer jeden seiner Kontakte als remote_self (entferntes Konto) im Kontakt reparieren Dialog markieren. Nach dem setzten dieses Flags werden alle Top-Level Beiträge dieser Kontakte automatisch in den Stream dieses Nutzers gepostet."
 
-#: mod/admin.php:758
+#: mod/admin.php:933
 msgid "Block multiple registrations"
 msgstr "Unterbinde Mehrfachregistrierung"
 
-#: mod/admin.php:758
+#: mod/admin.php:933
 msgid "Disallow users to register additional accounts for use as pages."
 msgstr "Benutzern nicht erlauben, weitere Konten als zusätzliche Profile anzulegen."
 
-#: mod/admin.php:759
+#: mod/admin.php:934
 msgid "OpenID support"
 msgstr "OpenID Unterstützung"
 
-#: mod/admin.php:759
+#: mod/admin.php:934
 msgid "OpenID support for registration and logins."
 msgstr "OpenID-Unterstützung für Registrierung und Login."
 
-#: mod/admin.php:760
+#: mod/admin.php:935
 msgid "Fullname check"
 msgstr "Namen auf Vollständigkeit überprüfen"
 
-#: mod/admin.php:760
+#: mod/admin.php:935
 msgid ""
 "Force users to register with a space between firstname and lastname in Full "
 "name, as an antispam measure"
 msgstr "Leerzeichen zwischen Vor- und Nachname im vollständigen Namen erzwingen, um SPAM zu vermeiden."
 
-#: mod/admin.php:761
+#: mod/admin.php:936
 msgid "UTF-8 Regular expressions"
 msgstr "UTF-8 Reguläre Ausdrücke"
 
-#: mod/admin.php:761
+#: mod/admin.php:936
 msgid "Use PHP UTF8 regular expressions"
 msgstr "PHP UTF8 Ausdrücke verwenden"
 
-#: mod/admin.php:762
+#: mod/admin.php:937
 msgid "Community Page Style"
 msgstr "Art der Gemeinschaftsseite"
 
-#: mod/admin.php:762
+#: mod/admin.php:937
 msgid ""
 "Type of community page to show. 'Global community' shows every public "
 "posting from an open distributed network that arrived on this server."
 msgstr "Welche Art der Gemeinschaftsseite soll verwendet werden? Globale Gemeinschaftsseite zeigt alle öffentlichen Beiträge eines offenen dezentralen Netzwerks an die auf diesem Server eintreffen."
 
-#: mod/admin.php:763
+#: mod/admin.php:938
 msgid "Posts per user on community page"
 msgstr "Anzahl der Beiträge pro Benutzer auf der Gemeinschaftsseite"
 
-#: mod/admin.php:763
+#: mod/admin.php:938
 msgid ""
 "The maximum number of posts per user on the community page. (Not valid for "
 "'Global Community')"
 msgstr "Die Anzahl der Beiträge die von jedem Nutzer maximal auf der Gemeinschaftsseite angezeigt werden sollen. Dieser Parameter wird nicht für die Globale Gemeinschaftsseite genutzt."
 
-#: mod/admin.php:764
+#: mod/admin.php:939
 msgid "Enable OStatus support"
 msgstr "OStatus Unterstützung aktivieren"
 
-#: mod/admin.php:764
+#: mod/admin.php:939
 msgid ""
 "Provide built-in OStatus (StatusNet, GNU Social etc.) compatibility. All "
 "communications in OStatus are public, so privacy warnings will be "
 "occasionally displayed."
 msgstr "Biete die eingebaute OStatus (iStatusNet, GNU Social, etc.) Unterstützung an. Jede Kommunikation in OStatus ist öffentlich, Privatsphäre Warnungen werden nur bei Bedarf angezeigt."
 
-#: mod/admin.php:765
+#: mod/admin.php:940
 msgid "OStatus conversation completion interval"
 msgstr "Intervall zum Vervollständigen von OStatus Unterhaltungen"
 
-#: mod/admin.php:765
+#: mod/admin.php:940
 msgid ""
 "How often shall the poller check for new entries in OStatus conversations? "
 "This can be a very ressource task."
 msgstr "Wie oft soll der Poller checken ob es neue Nachrichten in OStatus Unterhaltungen gibt die geladen werden müssen. Je nach Anzahl der OStatus Kontakte könnte dies ein sehr Ressourcen lastiger Job sein."
 
-#: mod/admin.php:766
+#: mod/admin.php:941
 msgid "OStatus support can only be enabled if threading is enabled."
 msgstr "OStatus Unterstützung kann nur aktiviert werden wenn \"Threading\" aktiviert ist. "
 
-#: mod/admin.php:768
+#: mod/admin.php:943
 msgid ""
 "Diaspora support can't be enabled because Friendica was installed into a sub"
 " directory."
 msgstr "Diaspora Unterstützung kann nicht aktiviert werden da Friendica in ein Unterverzeichnis installiert ist."
 
-#: mod/admin.php:769
+#: mod/admin.php:944
 msgid "Enable Diaspora support"
 msgstr "Diaspora Unterstützung aktivieren"
 
-#: mod/admin.php:769
+#: mod/admin.php:944
 msgid "Provide built-in Diaspora network compatibility."
 msgstr "Verwende die eingebaute Diaspora-Verknüpfung."
 
-#: mod/admin.php:770
+#: mod/admin.php:945
 msgid "Only allow Friendica contacts"
 msgstr "Nur Friendica-Kontakte erlauben"
 
-#: mod/admin.php:770
+#: mod/admin.php:945
 msgid ""
 "All contacts must use Friendica protocols. All other built-in communication "
 "protocols disabled."
 msgstr "Alle Kontakte müssen das Friendica Protokoll nutzen. Alle anderen Kommunikationsprotokolle werden deaktiviert."
 
-#: mod/admin.php:771
+#: mod/admin.php:946
 msgid "Verify SSL"
 msgstr "SSL Überprüfen"
 
-#: mod/admin.php:771
+#: mod/admin.php:946
 msgid ""
 "If you wish, you can turn on strict certificate checking. This will mean you"
 " cannot connect (at all) to self-signed SSL sites."
 msgstr "Wenn gewollt, kann man hier eine strenge Zertifikatkontrolle einstellen. Das bedeutet, dass man zu keinen Seiten mit selbst unterzeichnetem SSL eine Verbindung herstellen kann."
 
-#: mod/admin.php:772
+#: mod/admin.php:947
 msgid "Proxy user"
 msgstr "Proxy Nutzer"
 
-#: mod/admin.php:773
+#: mod/admin.php:948
 msgid "Proxy URL"
 msgstr "Proxy URL"
 
-#: mod/admin.php:774
+#: mod/admin.php:949
 msgid "Network timeout"
 msgstr "Netzwerk Wartezeit"
 
-#: mod/admin.php:774
+#: mod/admin.php:949
 msgid "Value is in seconds. Set to 0 for unlimited (not recommended)."
 msgstr "Der Wert ist in Sekunden. Setze 0 für unbegrenzt (nicht empfohlen)."
 
-#: mod/admin.php:775
+#: mod/admin.php:950
 msgid "Delivery interval"
 msgstr "Zustellungsintervall"
 
-#: mod/admin.php:775
+#: mod/admin.php:950
 msgid ""
 "Delay background delivery processes by this many seconds to reduce system "
 "load. Recommend: 4-5 for shared hosts, 2-3 for virtual private servers. 0-1 "
 "for large dedicated servers."
 msgstr "Verzögere im Hintergrund laufende Auslieferungsprozesse um die angegebene Anzahl an Sekunden, um die Systemlast zu verringern. Empfehlungen: 4-5 für Shared-Hosts, 2-3 für VPS, 0-1 für große dedizierte Server."
 
-#: mod/admin.php:776
+#: mod/admin.php:951
 msgid "Poll interval"
 msgstr "Abfrageintervall"
 
-#: mod/admin.php:776
+#: mod/admin.php:951
 msgid ""
 "Delay background polling processes by this many seconds to reduce system "
 "load. If 0, use delivery interval."
 msgstr "Verzögere Hintergrundprozesse um diese Anzahl an Sekunden, um die Systemlast zu reduzieren. Bei 0 Sekunden wird das Auslieferungsintervall verwendet."
 
-#: mod/admin.php:777
+#: mod/admin.php:952
 msgid "Maximum Load Average"
 msgstr "Maximum Load Average"
 
-#: mod/admin.php:777
+#: mod/admin.php:952
 msgid ""
 "Maximum system load before delivery and poll processes are deferred - "
 "default 50."
 msgstr "Maximale Systemlast bevor Verteil- und Empfangsprozesse verschoben werden - Standard 50"
 
-#: mod/admin.php:778
+#: mod/admin.php:953
 msgid "Maximum Load Average (Frontend)"
 msgstr "Maximum Load Average (Frontend)"
 
-#: mod/admin.php:778
+#: mod/admin.php:953
 msgid "Maximum system load before the frontend quits service - default 50."
 msgstr "Maximale Systemlast bevor Vordergrundprozesse pausiert werden - Standard 50."
 
-#: mod/admin.php:779
+#: mod/admin.php:954
 msgid "Maximum table size for optimization"
 msgstr "Maximale Tabellengröße zur Optimierung"
 
-#: mod/admin.php:779
+#: mod/admin.php:954
 msgid ""
 "Maximum table size (in MB) for the automatic optimization - default 100 MB. "
 "Enter -1 to disable it."
 msgstr "Maximale Tabellengröße (in MB) für die automatische Optimierung - Standard 100 MB. Gib -1 für Deaktivierung ein."
 
-#: mod/admin.php:780
+#: mod/admin.php:955
 msgid "Minimum level of fragmentation"
-msgstr "Minimales Fragmentationsgrad"
+msgstr "Minimaler Fragmentationsgrad"
 
-#: mod/admin.php:780
+#: mod/admin.php:955
 msgid ""
 "Minimum fragmenation level to start the automatic optimization - default "
 "value is 30%."
 msgstr "Minimales Fragmentationsgrad von Datenbanktabellen um die automatische Optimierung einzuleiten - Standardwert ist 30%"
 
-#: mod/admin.php:782
+#: mod/admin.php:957
 msgid "Periodical check of global contacts"
 msgstr "Regelmäßig globale Kontakte überprüfen"
 
-#: mod/admin.php:782
+#: mod/admin.php:957
 msgid ""
 "If enabled, the global contacts are checked periodically for missing or "
 "outdated data and the vitality of the contacts and servers."
 msgstr "Wenn diese Option aktiviert ist, werden die globalen Kontakte regelmäßig auf fehlende oder veraltete Daten sowie auf Erreichbarkeit des Kontakts und des Servers überprüft."
 
-#: mod/admin.php:783
+#: mod/admin.php:958
 msgid "Days between requery"
 msgstr "Tage zwischen erneuten Abfragen"
 
-#: mod/admin.php:783
+#: mod/admin.php:958
 msgid "Number of days after which a server is requeried for his contacts."
 msgstr "Legt das Abfrageintervall fest, nachdem ein Server erneut nach Kontakten abgefragt werden soll."
 
-#: mod/admin.php:784
+#: mod/admin.php:959
 msgid "Discover contacts from other servers"
 msgstr "Neue Kontakte auf anderen Servern entdecken"
 
-#: mod/admin.php:784
+#: mod/admin.php:959
 msgid ""
 "Periodically query other servers for contacts. You can choose between "
 "'users': the users on the remote system, 'Global Contacts': active contacts "
@@ -2753,32 +2737,32 @@ msgid ""
 "Global Contacts'."
 msgstr "Regelmäßig andere Server nach potentiellen Kontakten absuchen. Du kannst zwischen 'Nutzern', den tatsächlichen Nutzern des anderen Systems und 'globalen Kontakten', aktiven Kontakten die auf dem System bekannt sind, wählen. Der Fallback-Mechanismus ist für ältere Friendica und Redmatrix Server gedacht, bei denen globale Kontakte noch nicht verfügbar sind. Durch den Fallbackmodus entsteht auf deinem Server eine wesentlich höhere Last, empfohlen wird der Modus 'Nutzer, globale Kontakte'."
 
-#: mod/admin.php:785
+#: mod/admin.php:960
 msgid "Timeframe for fetching global contacts"
 msgstr "Zeitfenster für globale Kontakte"
 
-#: mod/admin.php:785
+#: mod/admin.php:960
 msgid ""
 "When the discovery is activated, this value defines the timeframe for the "
 "activity of the global contacts that are fetched from other servers."
 msgstr "Wenn die Entdeckung neuer Kontakte aktiv ist, definiert dieses Zeitfenster den Zeitraum in dem globale Kontakte als aktiv gelten und von anderen Servern importiert werden."
 
-#: mod/admin.php:786
+#: mod/admin.php:961
 msgid "Search the local directory"
 msgstr "Lokales Verzeichnis durchsuchen"
 
-#: mod/admin.php:786
+#: mod/admin.php:961
 msgid ""
 "Search the local directory instead of the global directory. When searching "
 "locally, every search will be executed on the global directory in the "
 "background. This improves the search results when the search is repeated."
 msgstr "Suche im lokalen Verzeichnis anstelle des globalen Verzeichnisses durchführen. Jede Suche wird im Hintergrund auch im globalen Verzeichnis durchgeführt umd die Suchresultate zu verbessern, wenn diese Suche wiederholt wird."
 
-#: mod/admin.php:788
+#: mod/admin.php:963
 msgid "Publish server information"
 msgstr "Server Informationen veröffentlichen"
 
-#: mod/admin.php:788
+#: mod/admin.php:963
 msgid ""
 "If enabled, general server and usage data will be published. The data "
 "contains the name and version of the server, number of users with public "
@@ -2786,205 +2770,205 @@ msgid ""
 " href='http://the-federation.info/'>the-federation.info</a> for details."
 msgstr "Wenn aktiviert, werden allgemeine Informationen über den Server und Nutzungsdaten veröffentlicht. Die Daten beinhalten den Namen sowie die Version des Servers, die Anzahl der Nutzer_innen mit öffentlichen Profilen, die Anzahl der Beiträge sowie aktivierte Protokolle und Connectoren. Für Details bitte <a href='http://the-federation.info/'>the-federation.info</a> aufrufen."
 
-#: mod/admin.php:790
+#: mod/admin.php:965
 msgid "Use MySQL full text engine"
 msgstr "Nutze MySQL full text engine"
 
-#: mod/admin.php:790
+#: mod/admin.php:965
 msgid ""
 "Activates the full text engine. Speeds up search - but can only search for "
 "four and more characters."
 msgstr "Aktiviert die 'full text engine'. Beschleunigt die Suche - aber es kann nur nach vier oder mehr Zeichen gesucht werden."
 
-#: mod/admin.php:791
+#: mod/admin.php:966
 msgid "Suppress Language"
 msgstr "Sprachinformation unterdrücken"
 
-#: mod/admin.php:791
+#: mod/admin.php:966
 msgid "Suppress language information in meta information about a posting."
 msgstr "Verhindert das Erzeugen der Meta-Information zur Spracherkennung eines Beitrags."
 
-#: mod/admin.php:792
+#: mod/admin.php:967
 msgid "Suppress Tags"
 msgstr "Tags Unterdrücken"
 
-#: mod/admin.php:792
+#: mod/admin.php:967
 msgid "Suppress showing a list of hashtags at the end of the posting."
 msgstr "Unterdrückt die Anzeige von Tags am Ende eines Beitrags."
 
-#: mod/admin.php:793
+#: mod/admin.php:968
 msgid "Path to item cache"
 msgstr "Pfad zum Eintrag Cache"
 
-#: mod/admin.php:793
+#: mod/admin.php:968
 msgid "The item caches buffers generated bbcode and external images."
 msgstr "Im Item-Cache werden externe Bilder und geparster BBCode zwischen gespeichert."
 
-#: mod/admin.php:794
+#: mod/admin.php:969
 msgid "Cache duration in seconds"
 msgstr "Cache-Dauer in Sekunden"
 
-#: mod/admin.php:794
+#: mod/admin.php:969
 msgid ""
 "How long should the cache files be hold? Default value is 86400 seconds (One"
 " day). To disable the item cache, set the value to -1."
 msgstr "Wie lange sollen die gecachedten Dateien vorgehalten werden? Grundeinstellung sind 86400 Sekunden (ein Tag). Um den Item Cache zu deaktivieren, setze diesen Wert auf -1."
 
-#: mod/admin.php:795
+#: mod/admin.php:970
 msgid "Maximum numbers of comments per post"
 msgstr "Maximale Anzahl von Kommentaren pro Beitrag"
 
-#: mod/admin.php:795
+#: mod/admin.php:970
 msgid "How much comments should be shown for each post? Default value is 100."
 msgstr "Wie viele Kommentare sollen pro Beitrag angezeigt werden? Standardwert sind 100."
 
-#: mod/admin.php:796
+#: mod/admin.php:971
 msgid "Path for lock file"
 msgstr "Pfad für die Sperrdatei"
 
-#: mod/admin.php:796
+#: mod/admin.php:971
 msgid ""
 "The lock file is used to avoid multiple pollers at one time. Only define a "
 "folder here."
 msgstr "Die lock-Datei wird benutzt, damit nicht mehrere poller auf einmal laufen. Definiere hier einen Dateiverzeichnis."
 
-#: mod/admin.php:797
+#: mod/admin.php:972
 msgid "Temp path"
 msgstr "Temp Pfad"
 
-#: mod/admin.php:797
+#: mod/admin.php:972
 msgid ""
 "If you have a restricted system where the webserver can't access the system "
 "temp path, enter another path here."
 msgstr "Solltest du ein eingeschränktes System haben, auf dem der Webserver nicht auf das temp Verzeichnis des Systems zugreifen kann, setze hier einen anderen Pfad."
 
-#: mod/admin.php:798
+#: mod/admin.php:973
 msgid "Base path to installation"
 msgstr "Basis-Pfad zur Installation"
 
-#: mod/admin.php:798
+#: mod/admin.php:973
 msgid ""
 "If the system cannot detect the correct path to your installation, enter the"
 " correct path here. This setting should only be set if you are using a "
 "restricted system and symbolic links to your webroot."
 msgstr "Falls das System nicht den korrekten Pfad zu deiner Installation gefunden hat, gib den richtigen Pfad bitte hier ein. Du solltest hier den Pfad nur auf einem eingeschränkten System angeben müssen, bei dem du mit symbolischen Links auf dein Webverzeichnis verweist."
 
-#: mod/admin.php:799
+#: mod/admin.php:974
 msgid "Disable picture proxy"
 msgstr "Bilder Proxy deaktivieren"
 
-#: mod/admin.php:799
+#: mod/admin.php:974
 msgid ""
 "The picture proxy increases performance and privacy. It shouldn't be used on"
 " systems with very low bandwith."
 msgstr "Der Proxy für Bilder verbessert die Leistung und Privatsphäre der Nutzer. Er sollte nicht auf Systemen verwendet werden, die nur über begrenzte Bandbreite verfügen."
 
-#: mod/admin.php:800
+#: mod/admin.php:975
 msgid "Enable old style pager"
 msgstr "Den Old-Style Pager aktiviren"
 
-#: mod/admin.php:800
+#: mod/admin.php:975
 msgid ""
 "The old style pager has page numbers but slows down massively the page "
 "speed."
 msgstr "Der Old-Style Pager zeigt Seitennummern an, verlangsamt aber auch drastisch das Laden einer Seite."
 
-#: mod/admin.php:801
+#: mod/admin.php:976
 msgid "Only search in tags"
 msgstr "Nur in Tags suchen"
 
-#: mod/admin.php:801
+#: mod/admin.php:976
 msgid "On large systems the text search can slow down the system extremely."
 msgstr "Auf großen Knoten kann die Volltext-Suche das System ausbremsen."
 
-#: mod/admin.php:803
+#: mod/admin.php:978
 msgid "New base url"
 msgstr "Neue Basis-URL"
 
-#: mod/admin.php:803
+#: mod/admin.php:978
 msgid ""
 "Change base url for this server. Sends relocate message to all DFRN contacts"
 " of all users."
 msgstr "Ändert die Basis-URL dieses Servers und sendet eine Umzugsmitteilung an alle DFRN Kontakte deiner Nutzer_innen."
 
-#: mod/admin.php:805
+#: mod/admin.php:980
 msgid "RINO Encryption"
 msgstr "RINO Verschlüsselung"
 
-#: mod/admin.php:805
+#: mod/admin.php:980
 msgid "Encryption layer between nodes."
 msgstr "Verschlüsselung zwischen Friendica Instanzen"
 
-#: mod/admin.php:806
+#: mod/admin.php:981
 msgid "Embedly API key"
 msgstr "Embedly  API Schlüssel"
 
-#: mod/admin.php:806
+#: mod/admin.php:981
 msgid ""
 "<a href='http://embed.ly'>Embedly</a> is used to fetch additional data for "
 "web pages. This is an optional parameter."
 msgstr "<a href='http://embed.ly'>Embedly</a> wird verwendet um zusätzliche Informationen von Webseiten zu laden. Dies ist ein optionaler Parameter."
 
-#: mod/admin.php:824
+#: mod/admin.php:1010
 msgid "Update has been marked successful"
 msgstr "Update wurde als erfolgreich markiert"
 
-#: mod/admin.php:832
+#: mod/admin.php:1018
 #, php-format
 msgid "Database structure update %s was successfully applied."
 msgstr "Das Update %s der Struktur der Datenbank wurde erfolgreich angewandt."
 
-#: mod/admin.php:835
+#: mod/admin.php:1021
 #, php-format
 msgid "Executing of database structure update %s failed with error: %s"
 msgstr "Das Update %s der Struktur der Datenbank schlug mit folgender Fehlermeldung fehl: %s"
 
-#: mod/admin.php:847
+#: mod/admin.php:1033
 #, php-format
 msgid "Executing %s failed with error: %s"
 msgstr "Die Ausführung von %s schlug fehl. Fehlermeldung: %s"
 
-#: mod/admin.php:850
+#: mod/admin.php:1036
 #, php-format
 msgid "Update %s was successfully applied."
 msgstr "Update %s war erfolgreich."
 
-#: mod/admin.php:854
+#: mod/admin.php:1040
 #, php-format
 msgid "Update %s did not return a status. Unknown if it succeeded."
 msgstr "Update %s hat keinen Status zurückgegeben. Unbekannter Status."
 
-#: mod/admin.php:856
+#: mod/admin.php:1042
 #, php-format
 msgid "There was no additional update function %s that needed to be called."
 msgstr "Es gab keine weitere Update-Funktion, die von %s ausgeführt werden musste."
 
-#: mod/admin.php:875
+#: mod/admin.php:1061
 msgid "No failed updates."
 msgstr "Keine fehlgeschlagenen Updates."
 
-#: mod/admin.php:876
+#: mod/admin.php:1062
 msgid "Check database structure"
 msgstr "Datenbank Struktur überprüfen"
 
-#: mod/admin.php:881
+#: mod/admin.php:1067
 msgid "Failed Updates"
 msgstr "Fehlgeschlagene Updates"
 
-#: mod/admin.php:882
+#: mod/admin.php:1068
 msgid ""
 "This does not include updates prior to 1139, which did not return a status."
 msgstr "Ohne Updates vor 1139, da diese keinen Status zurückgegeben haben."
 
-#: mod/admin.php:883
+#: mod/admin.php:1069
 msgid "Mark success (if update was manually applied)"
 msgstr "Als erfolgreich markieren (falls das Update manuell installiert wurde)"
 
-#: mod/admin.php:884
+#: mod/admin.php:1070
 msgid "Attempt to execute this update step automatically"
 msgstr "Versuchen, diesen Schritt automatisch auszuführen"
 
-#: mod/admin.php:916
+#: mod/admin.php:1102
 #, php-format
 msgid ""
 "\n"
@@ -2992,7 +2976,7 @@ msgid ""
 "\t\t\t\tthe administrator of %2$s has set up an account for you."
 msgstr "\nHallo %1$s,\n\nauf %2$s wurde ein Account für Dich angelegt."
 
-#: mod/admin.php:919
+#: mod/admin.php:1105
 #, php-format
 msgid ""
 "\n"
@@ -3022,233 +3006,255 @@ msgid ""
 "\t\t\tThank you and welcome to %4$s."
 msgstr "\nNachfolgend die Anmelde-Details:\n\tAdresse der Seite:\t%1$s\n\tBenutzername:\t%2$s\n\tPasswort:\t%3$s\n\nDu kannst Dein Passwort unter \"Einstellungen\" ändern, sobald Du Dich\nangemeldet hast.\n\nBitte nimm Dir ein paar Minuten um die anderen Einstellungen auf dieser\nSeite zu kontrollieren.\n\nEventuell magst Du ja auch einige Informationen über Dich in Deinem\nProfil veröffentlichen, damit andere Leute Dich einfacher finden können.\nBearbeite hierfür einfach Dein Standard-Profil (über die Profil-Seite).\n\nWir empfehlen Dir, Deinen kompletten Namen anzugeben und ein zu Dir\npassendes Profilbild zu wählen, damit Dich alte Bekannte wieder finden.\nAußerdem ist es nützlich, wenn Du auf Deinem Profil Schlüsselwörter\nangibst. Das erleichtert es, Leute zu finden, die Deine Interessen teilen.\n\nWir respektieren Deine Privatsphäre - keine dieser Angaben ist nötig.\nWenn Du neu im Netzwerk bist und noch niemanden kennst, dann können sie\nallerdings dabei helfen, neue und interessante Kontakte zu knüpfen.\n\nNun viel Spaß, gute Begegnungen und willkommen auf %4$s."
 
-#: mod/admin.php:951 include/user.php:423
+#: mod/admin.php:1137 include/user.php:423
 #, php-format
 msgid "Registration details for %s"
 msgstr "Details der Registration von %s"
 
-#: mod/admin.php:963
+#: mod/admin.php:1149
 #, php-format
 msgid "%s user blocked/unblocked"
 msgid_plural "%s users blocked/unblocked"
 msgstr[0] "%s Benutzer geblockt/freigegeben"
 msgstr[1] "%s Benutzer geblockt/freigegeben"
 
-#: mod/admin.php:970
+#: mod/admin.php:1156
 #, php-format
 msgid "%s user deleted"
 msgid_plural "%s users deleted"
 msgstr[0] "%s Nutzer gelöscht"
 msgstr[1] "%s Nutzer gelöscht"
 
-#: mod/admin.php:1009
+#: mod/admin.php:1203
 #, php-format
 msgid "User '%s' deleted"
 msgstr "Nutzer '%s' gelöscht"
 
-#: mod/admin.php:1017
+#: mod/admin.php:1211
 #, php-format
 msgid "User '%s' unblocked"
 msgstr "Nutzer '%s' entsperrt"
 
-#: mod/admin.php:1017
+#: mod/admin.php:1211
 #, php-format
 msgid "User '%s' blocked"
 msgstr "Nutzer '%s' gesperrt"
 
-#: mod/admin.php:1110
+#: mod/admin.php:1302
 msgid "Add User"
 msgstr "Nutzer hinzufügen"
 
-#: mod/admin.php:1111
+#: mod/admin.php:1303
 msgid "select all"
 msgstr "Alle auswählen"
 
-#: mod/admin.php:1112
+#: mod/admin.php:1304
 msgid "User registrations waiting for confirm"
 msgstr "Neuanmeldungen, die auf Deine Bestätigung warten"
 
-#: mod/admin.php:1113
+#: mod/admin.php:1305
 msgid "User waiting for permanent deletion"
 msgstr "Nutzer wartet auf permanente Löschung"
 
-#: mod/admin.php:1114
+#: mod/admin.php:1306
 msgid "Request date"
 msgstr "Anfragedatum"
 
-#: mod/admin.php:1114 mod/admin.php:1126 mod/admin.php:1127 mod/admin.php:1142
+#: mod/admin.php:1306 mod/admin.php:1318 mod/admin.php:1319 mod/admin.php:1334
 #: include/contact_selectors.php:79 include/contact_selectors.php:86
 msgid "Email"
 msgstr "E-Mail"
 
-#: mod/admin.php:1115
+#: mod/admin.php:1307
 msgid "No registrations."
 msgstr "Keine Neuanmeldungen."
 
-#: mod/admin.php:1117
+#: mod/admin.php:1309
 msgid "Deny"
 msgstr "Verwehren"
 
-#: mod/admin.php:1121
+#: mod/admin.php:1313
 msgid "Site admin"
 msgstr "Seitenadministrator"
 
-#: mod/admin.php:1122
+#: mod/admin.php:1314
 msgid "Account expired"
 msgstr "Account ist abgelaufen"
 
-#: mod/admin.php:1125
+#: mod/admin.php:1317
 msgid "New User"
 msgstr "Neuer Nutzer"
 
-#: mod/admin.php:1126 mod/admin.php:1127
+#: mod/admin.php:1318 mod/admin.php:1319
 msgid "Register date"
 msgstr "Anmeldedatum"
 
-#: mod/admin.php:1126 mod/admin.php:1127
+#: mod/admin.php:1318 mod/admin.php:1319
 msgid "Last login"
 msgstr "Letzte Anmeldung"
 
-#: mod/admin.php:1126 mod/admin.php:1127
+#: mod/admin.php:1318 mod/admin.php:1319
 msgid "Last item"
 msgstr "Letzter Beitrag"
 
-#: mod/admin.php:1126
+#: mod/admin.php:1318
 msgid "Deleted since"
 msgstr "Gelöscht seit"
 
-#: mod/admin.php:1127 mod/settings.php:41
+#: mod/admin.php:1319 mod/settings.php:41
 msgid "Account"
 msgstr "Nutzerkonto"
 
-#: mod/admin.php:1129
+#: mod/admin.php:1321
 msgid ""
 "Selected users will be deleted!\\n\\nEverything these users had posted on "
 "this site will be permanently deleted!\\n\\nAre you sure?"
 msgstr "Die markierten Nutzer werden gelöscht!\\n\\nAlle Beiträge, die diese Nutzer auf dieser Seite veröffentlicht haben, werden permanent gelöscht!\\n\\nBist Du sicher?"
 
-#: mod/admin.php:1130
+#: mod/admin.php:1322
 msgid ""
 "The user {0} will be deleted!\\n\\nEverything this user has posted on this "
 "site will be permanently deleted!\\n\\nAre you sure?"
 msgstr "Der Nutzer {0} wird gelöscht!\\n\\nAlles was dieser Nutzer auf dieser Seite veröffentlicht hat, wird permanent gelöscht!\\n\\nBist Du sicher?"
 
-#: mod/admin.php:1140
+#: mod/admin.php:1332
 msgid "Name of the new user."
 msgstr "Name des neuen Nutzers"
 
-#: mod/admin.php:1141
+#: mod/admin.php:1333
 msgid "Nickname"
 msgstr "Spitzname"
 
-#: mod/admin.php:1141
+#: mod/admin.php:1333
 msgid "Nickname of the new user."
 msgstr "Spitznamen für den neuen Nutzer"
 
-#: mod/admin.php:1142
+#: mod/admin.php:1334
 msgid "Email address of the new user."
 msgstr "Email Adresse des neuen Nutzers"
 
-#: mod/admin.php:1175
+#: mod/admin.php:1377
 #, php-format
 msgid "Plugin %s disabled."
 msgstr "Plugin %s deaktiviert."
 
-#: mod/admin.php:1179
+#: mod/admin.php:1381
 #, php-format
 msgid "Plugin %s enabled."
 msgstr "Plugin %s aktiviert."
 
-#: mod/admin.php:1189 mod/admin.php:1413
+#: mod/admin.php:1392 mod/admin.php:1628
 msgid "Disable"
 msgstr "Ausschalten"
 
-#: mod/admin.php:1191 mod/admin.php:1415
+#: mod/admin.php:1394 mod/admin.php:1630
 msgid "Enable"
 msgstr "Einschalten"
 
-#: mod/admin.php:1214 mod/admin.php:1459
+#: mod/admin.php:1417 mod/admin.php:1675
 msgid "Toggle"
 msgstr "Umschalten"
 
-#: mod/admin.php:1222 mod/admin.php:1469
+#: mod/admin.php:1425 mod/admin.php:1684
 msgid "Author: "
 msgstr "Autor:"
 
-#: mod/admin.php:1223 mod/admin.php:1470
+#: mod/admin.php:1426 mod/admin.php:1685
 msgid "Maintainer: "
 msgstr "Betreuer:"
 
-#: mod/admin.php:1275
-#: view/smarty3/compiled/f835364006028b1061f37be121c9bd9db5fa50a9.file.admin_plugins.tpl.php:42
+#: mod/admin.php:1478
 msgid "Reload active plugins"
 msgstr "Aktive Plugins neu laden"
 
-#: mod/admin.php:1373
+#: mod/admin.php:1483
+#, php-format
+msgid ""
+"There are currently no plugins available on your node. You can find the "
+"official plugin repository at %1$s and might find other interesting plugins "
+"in the open plugin registry at %2$s"
+msgstr "Es sind derzeit keine Plugins auf diesem Knoten verfügbar. Du findest das offizielle Plugin-Repository unter %1$s und weitere eventuell interessante Plugins im offenen Plugins-Verzeichnis auf %2$s."
+
+#: mod/admin.php:1588
 msgid "No themes found."
 msgstr "Keine Themen gefunden."
 
-#: mod/admin.php:1451
+#: mod/admin.php:1666
 msgid "Screenshot"
 msgstr "Bildschirmfoto"
 
-#: mod/admin.php:1511
+#: mod/admin.php:1726
 msgid "Reload active themes"
 msgstr "Aktives Theme neu laden"
 
-#: mod/admin.php:1515
+#: mod/admin.php:1731
+#, php-format
+msgid "No themes found on the system. They should be paced in %1$s"
+msgstr "Es wurden keine Themes auf dem System gefunden. Diese sollten in %1$s patziert werden."
+
+#: mod/admin.php:1732
 msgid "[Experimental]"
 msgstr "[Experimentell]"
 
-#: mod/admin.php:1516
+#: mod/admin.php:1733
 msgid "[Unsupported]"
 msgstr "[Nicht unterstützt]"
 
-#: mod/admin.php:1543
+#: mod/admin.php:1757
 msgid "Log settings updated."
 msgstr "Protokolleinstellungen aktualisiert."
 
-#: mod/admin.php:1599
+#: mod/admin.php:1794
 msgid "Clear"
 msgstr "löschen"
 
-#: mod/admin.php:1605
+#: mod/admin.php:1799
 msgid "Enable Debugging"
 msgstr "Protokoll führen"
 
-#: mod/admin.php:1606
+#: mod/admin.php:1800
 msgid "Log file"
 msgstr "Protokolldatei"
 
-#: mod/admin.php:1606
+#: mod/admin.php:1800
 msgid ""
 "Must be writable by web server. Relative to your Friendica top-level "
 "directory."
 msgstr "Webserver muss Schreibrechte besitzen. Abhängig vom Friendica-Installationsverzeichnis."
 
-#: mod/admin.php:1607
+#: mod/admin.php:1801
 msgid "Log level"
 msgstr "Protokoll-Level"
 
-#: mod/admin.php:1657 include/acl_selectors.php:348
-msgid "Close"
-msgstr "Schließen"
+#: mod/admin.php:1804
+msgid "PHP logging"
+msgstr "PHP Protokollieren"
 
-#: mod/admin.php:1663
-msgid "FTP Host"
-msgstr "FTP Host"
+#: mod/admin.php:1805
+msgid ""
+"To enable logging of PHP errors and warnings you can add the following to "
+"the .htconfig.php file of your installation. The filename set in the "
+"'error_log' line is relative to the friendica top-level directory and must "
+"be writeable by the web server. The option '1' for 'log_errors' and "
+"'display_errors' is to enable these options, set to '0' to disable them."
+msgstr "Um PHP Warnungen und Fehler zu protokollieren, kannst du die folgenden Zeilen zur .htconfig.php Datei deiner Installation hinzufügen. Den Dateinamen der Log-Datei legst du in der Zeile mit dem 'error_log' fest,  Er ist relativ zum Friendica-Stammverzeichnis und muss schreibbar durch den Webserver sein. Eine \"1\" als Option für die Punkte 'log_errors' und 'display_errors' aktiviert die Funktionen zum Protokollieren bzw. Anzeigen der Fehler, eine \"0\" deaktiviert sie."
 
-#: mod/admin.php:1664
-msgid "FTP Path"
-msgstr "FTP Pfad"
+#: mod/admin.php:1931 mod/admin.php:1932 mod/settings.php:759
+msgid "Off"
+msgstr "Aus"
 
-#: mod/admin.php:1665
-msgid "FTP User"
-msgstr "FTP Nutzername"
+#: mod/admin.php:1931 mod/admin.php:1932 mod/settings.php:759
+msgid "On"
+msgstr "An"
 
-#: mod/admin.php:1666
-msgid "FTP Password"
-msgstr "FTP Passwort"
+#: mod/admin.php:1932
+#, php-format
+msgid "Lock feature %s"
+msgstr "Feature festlegen: %s"
+
+#: mod/admin.php:1940
+msgid "Manage Additional Features"
+msgstr "Zusätzliche Features Verwalten"
 
 #: mod/network.php:146
 #, php-format
@@ -3259,7 +3265,7 @@ msgstr "Suchergebnisse für: %s"
 msgid "Remove term"
 msgstr "Begriff entfernen"
 
-#: mod/network.php:200 mod/search.php:34 include/features.php:79
+#: mod/network.php:200 mod/search.php:34 include/features.php:84
 msgid "Saved Searches"
 msgstr "Gespeicherte Suchen"
 
@@ -3267,51 +3273,51 @@ msgstr "Gespeicherte Suchen"
 msgid "add"
 msgstr "hinzufügen"
 
-#: mod/network.php:362
+#: mod/network.php:365
 msgid "Commented Order"
 msgstr "Neueste Kommentare"
 
-#: mod/network.php:365
+#: mod/network.php:368
 msgid "Sort by Comment Date"
 msgstr "Nach Kommentardatum sortieren"
 
-#: mod/network.php:370
+#: mod/network.php:373
 msgid "Posted Order"
 msgstr "Neueste Beiträge"
 
-#: mod/network.php:373
+#: mod/network.php:376
 msgid "Sort by Post Date"
 msgstr "Nach Beitragsdatum sortieren"
 
-#: mod/network.php:384
+#: mod/network.php:387
 msgid "Posts that mention or involve you"
 msgstr "Beiträge, in denen es um Dich geht"
 
-#: mod/network.php:392
+#: mod/network.php:395
 msgid "New"
 msgstr "Neue"
 
-#: mod/network.php:395
+#: mod/network.php:398
 msgid "Activity Stream - by date"
 msgstr "Aktivitäten-Stream - nach Datum"
 
-#: mod/network.php:403
+#: mod/network.php:406
 msgid "Shared Links"
 msgstr "Geteilte Links"
 
-#: mod/network.php:406
+#: mod/network.php:409
 msgid "Interesting Links"
 msgstr "Interessante Links"
 
-#: mod/network.php:414
+#: mod/network.php:417
 msgid "Starred"
 msgstr "Markierte"
 
-#: mod/network.php:417
+#: mod/network.php:420
 msgid "Favourite Posts"
 msgstr "Favorisierte Beiträge"
 
-#: mod/network.php:476
+#: mod/network.php:479
 #, php-format
 msgid "Warning: This group contains %s member from an insecure network."
 msgid_plural ""
@@ -3319,24 +3325,24 @@ msgid_plural ""
 msgstr[0] "Warnung: Diese Gruppe beinhaltet %s Person aus einem unsicheren Netzwerk."
 msgstr[1] "Warnung: Diese Gruppe beinhaltet %s Personen aus unsicheren Netzwerken."
 
-#: mod/network.php:479
+#: mod/network.php:482
 msgid "Private messages to this group are at risk of public disclosure."
 msgstr "Private Nachrichten an diese Gruppe könnten an die Öffentlichkeit geraten."
 
-#: mod/network.php:546 mod/content.php:119
+#: mod/network.php:549 mod/content.php:119
 msgid "No such group"
 msgstr "Es gibt keine solche Gruppe"
 
-#: mod/network.php:574 mod/content.php:135
+#: mod/network.php:580 mod/content.php:135
 #, php-format
 msgid "Group: %s"
 msgstr "Gruppe: %s"
 
-#: mod/network.php:606
+#: mod/network.php:608
 msgid "Private messages to this person are at risk of public disclosure."
 msgstr "Private Nachrichten an diese Person könnten an die Öffentlichkeit gelangen."
 
-#: mod/network.php:611
+#: mod/network.php:613
 msgid "Invalid contact."
 msgstr "Ungültiger Kontakt."
 
@@ -3380,11 +3386,11 @@ msgstr "Fr"
 msgid "Sat"
 msgstr "Sa"
 
-#: mod/events.php:208 mod/settings.php:939 include/text.php:1274
+#: mod/events.php:208 mod/settings.php:948 include/text.php:1274
 msgid "Sunday"
 msgstr "Sonntag"
 
-#: mod/events.php:209 mod/settings.php:939 include/text.php:1274
+#: mod/events.php:209 mod/settings.php:948 include/text.php:1274
 msgid "Monday"
 msgstr "Montag"
 
@@ -3524,7 +3530,7 @@ msgstr "l, F j"
 msgid "Edit event"
 msgstr "Veranstaltung bearbeiten"
 
-#: mod/events.php:421 include/text.php:1721 include/text.php:1728
+#: mod/events.php:421 include/text.php:1728 include/text.php:1735
 msgid "link to source"
 msgstr "Link zum Originalbeitrag"
 
@@ -3587,7 +3593,7 @@ msgstr "Veranstaltung teilen"
 
 #: mod/events.php:572 mod/content.php:721 mod/editpost.php:145
 #: mod/photos.php:1631 mod/photos.php:1679 mod/photos.php:1767
-#: object/Item.php:719 include/conversation.php:1217
+#: object/Item.php:719 include/conversation.php:1216
 msgid "Preview"
 msgstr "Vorschau"
 
@@ -3631,15 +3637,15 @@ msgstr[0] "%d Kommentar"
 msgstr[1] "%d Kommentare"
 
 #: mod/content.php:607 object/Item.php:421 object/Item.php:434
-#: include/text.php:1997
+#: include/text.php:2004
 msgid "comment"
 msgid_plural "comments"
 msgstr[0] "Kommentar"
 msgstr[1] "Kommentare"
 
-#: mod/content.php:608 boot.php:788 object/Item.php:422
+#: mod/content.php:608 boot.php:870 object/Item.php:422
 #: include/contact_widgets.php:242 include/forums.php:110
-#: include/items.php:5181 view/theme/vier/theme.php:264
+#: include/items.php:5207 view/theme/vier/theme.php:264
 msgid "show more"
 msgstr "mehr anzeigen"
 
@@ -3677,7 +3683,7 @@ msgid "This is you"
 msgstr "Das bist Du"
 
 #: mod/content.php:711 mod/photos.php:1629 mod/photos.php:1677
-#: mod/photos.php:1765 boot.php:787 object/Item.php:393 object/Item.php:709
+#: mod/photos.php:1765 boot.php:869 object/Item.php:393 object/Item.php:709
 msgid "Comment"
 msgstr "Kommentar"
 
@@ -3713,7 +3719,7 @@ msgstr "Link"
 msgid "Video"
 msgstr "Video"
 
-#: mod/content.php:730 mod/settings.php:712 object/Item.php:122
+#: mod/content.php:730 mod/settings.php:721 object/Item.php:122
 #: object/Item.php:124
 msgid "Edit"
 msgstr "Bearbeiten"
@@ -4109,19 +4115,19 @@ msgid ""
 "your site allow private mail from unknown senders."
 msgstr "Wenn Du möchtest, dass %s Dir antworten kann, überprüfe Deine Privatsphären-Einstellungen und erlaube private Nachrichten von unbekannten Absendern."
 
-#: mod/help.php:31
+#: mod/help.php:41
 msgid "Help:"
 msgstr "Hilfe:"
 
-#: mod/help.php:36 include/nav.php:113 view/theme/vier/theme.php:302
+#: mod/help.php:47 include/nav.php:113 view/theme/vier/theme.php:302
 msgid "Help"
 msgstr "Hilfe"
 
-#: mod/help.php:42 mod/p.php:16 mod/p.php:25 index.php:270
+#: mod/help.php:53 mod/p.php:16 mod/p.php:25 index.php:270
 msgid "Not Found"
 msgstr "Nicht gefunden"
 
-#: mod/help.php:45 index.php:273
+#: mod/help.php:56 index.php:273
 msgid "Page not found."
 msgstr "Seite nicht gefunden."
 
@@ -4168,16 +4174,16 @@ msgstr "Profilübereinstimmungen"
 msgid "link"
 msgstr "Link"
 
-#: mod/community.php:23
+#: mod/community.php:27
 msgid "Not available."
 msgstr "Nicht verfügbar."
 
-#: mod/community.php:32 include/nav.php:136 include/nav.php:138
+#: mod/community.php:36 include/nav.php:136 include/nav.php:138
 #: view/theme/diabook/theme.php:129
 msgid "Community"
 msgstr "Gemeinschaft"
 
-#: mod/community.php:62 mod/community.php:71 mod/search.php:228
+#: mod/community.php:66 mod/community.php:75 mod/search.php:228
 msgid "No results."
 msgstr "Keine Ergebnisse."
 
@@ -4185,822 +4191,812 @@ msgstr "Keine Ergebnisse."
 msgid "everybody"
 msgstr "jeder"
 
-#: mod/settings.php:47
-msgid "Additional features"
-msgstr "Zusätzliche Features"
-
-#: mod/settings.php:53
+#: mod/settings.php:58
 msgid "Display"
 msgstr "Anzeige"
 
-#: mod/settings.php:60 mod/settings.php:855
+#: mod/settings.php:65 mod/settings.php:864
 msgid "Social Networks"
 msgstr "Soziale Netzwerke"
 
-#: mod/settings.php:72 include/nav.php:180
+#: mod/settings.php:79 include/nav.php:180
 msgid "Delegations"
 msgstr "Delegationen"
 
-#: mod/settings.php:78
+#: mod/settings.php:86
 msgid "Connected apps"
 msgstr "Verbundene Programme"
 
-#: mod/settings.php:84 mod/uexport.php:85
+#: mod/settings.php:93 mod/uexport.php:85
 msgid "Export personal data"
 msgstr "Persönliche Daten exportieren"
 
-#: mod/settings.php:90
+#: mod/settings.php:100
 msgid "Remove account"
 msgstr "Konto löschen"
 
-#: mod/settings.php:143
+#: mod/settings.php:153
 msgid "Missing some important data!"
 msgstr "Wichtige Daten fehlen!"
 
-#: mod/settings.php:256
+#: mod/settings.php:266
 msgid "Failed to connect with email account using the settings provided."
 msgstr "Verbindung zum E-Mail-Konto mit den angegebenen Einstellungen nicht möglich."
 
-#: mod/settings.php:261
+#: mod/settings.php:271
 msgid "Email settings updated."
 msgstr "E-Mail Einstellungen bearbeitet."
 
-#: mod/settings.php:276
+#: mod/settings.php:286
 msgid "Features updated"
 msgstr "Features aktualisiert"
 
-#: mod/settings.php:343
+#: mod/settings.php:353
 msgid "Relocate message has been send to your contacts"
 msgstr "Die Umzugsbenachrichtigung wurde an Deine Kontakte versendet."
 
-#: mod/settings.php:357 include/user.php:39
+#: mod/settings.php:367 include/user.php:39
 msgid "Passwords do not match. Password unchanged."
 msgstr "Die Passwörter stimmen nicht überein. Das Passwort bleibt unverändert."
 
-#: mod/settings.php:362
+#: mod/settings.php:372
 msgid "Empty passwords are not allowed. Password unchanged."
 msgstr "Leere Passwörter sind nicht erlaubt. Passwort bleibt unverändert."
 
-#: mod/settings.php:370
+#: mod/settings.php:380
 msgid "Wrong password."
 msgstr "Falsches Passwort."
 
-#: mod/settings.php:381
+#: mod/settings.php:391
 msgid "Password changed."
 msgstr "Passwort geändert."
 
-#: mod/settings.php:383
+#: mod/settings.php:393
 msgid "Password update failed. Please try again."
 msgstr "Aktualisierung des Passworts gescheitert, bitte versuche es noch einmal."
 
-#: mod/settings.php:452
+#: mod/settings.php:462
 msgid " Please use a shorter name."
 msgstr " Bitte verwende einen kürzeren Namen."
 
-#: mod/settings.php:454
+#: mod/settings.php:464
 msgid " Name too short."
 msgstr " Name ist zu kurz."
 
-#: mod/settings.php:463
+#: mod/settings.php:473
 msgid "Wrong Password"
 msgstr "Falsches Passwort"
 
-#: mod/settings.php:468
+#: mod/settings.php:478
 msgid " Not valid email."
 msgstr " Keine gültige E-Mail."
 
-#: mod/settings.php:474
+#: mod/settings.php:484
 msgid " Cannot change to that email."
 msgstr "Ändern der E-Mail nicht möglich. "
 
-#: mod/settings.php:530
+#: mod/settings.php:540
 msgid "Private forum has no privacy permissions. Using default privacy group."
 msgstr "Für das private Forum sind keine Zugriffsrechte eingestellt. Die voreingestellte Gruppe für neue Kontakte wird benutzt."
 
-#: mod/settings.php:534
+#: mod/settings.php:544
 msgid "Private forum has no privacy permissions and no default privacy group."
 msgstr "Für das private Forum sind keine Zugriffsrechte eingestellt, und es gibt keine voreingestellte Gruppe für neue Kontakte."
 
-#: mod/settings.php:573
+#: mod/settings.php:583
 msgid "Settings updated."
 msgstr "Einstellungen aktualisiert."
 
-#: mod/settings.php:649 mod/settings.php:675 mod/settings.php:711
+#: mod/settings.php:658 mod/settings.php:684 mod/settings.php:720
 msgid "Add application"
 msgstr "Programm hinzufügen"
 
-#: mod/settings.php:653 mod/settings.php:679
+#: mod/settings.php:662 mod/settings.php:688
 msgid "Consumer Key"
 msgstr "Consumer Key"
 
-#: mod/settings.php:654 mod/settings.php:680
+#: mod/settings.php:663 mod/settings.php:689
 msgid "Consumer Secret"
 msgstr "Consumer Secret"
 
-#: mod/settings.php:655 mod/settings.php:681
+#: mod/settings.php:664 mod/settings.php:690
 msgid "Redirect"
 msgstr "Umleiten"
 
-#: mod/settings.php:656 mod/settings.php:682
+#: mod/settings.php:665 mod/settings.php:691
 msgid "Icon url"
 msgstr "Icon URL"
 
-#: mod/settings.php:667
+#: mod/settings.php:676
 msgid "You can't edit this application."
 msgstr "Du kannst dieses Programm nicht bearbeiten."
 
-#: mod/settings.php:710
+#: mod/settings.php:719
 msgid "Connected Apps"
 msgstr "Verbundene Programme"
 
-#: mod/settings.php:714
+#: mod/settings.php:723
 msgid "Client key starts with"
 msgstr "Anwenderschlüssel beginnt mit"
 
-#: mod/settings.php:715
+#: mod/settings.php:724
 msgid "No name"
 msgstr "Kein Name"
 
-#: mod/settings.php:716
+#: mod/settings.php:725
 msgid "Remove authorization"
 msgstr "Autorisierung entziehen"
 
-#: mod/settings.php:728
+#: mod/settings.php:737
 msgid "No Plugin settings configured"
 msgstr "Keine Plugin-Einstellungen konfiguriert"
 
-#: mod/settings.php:736
+#: mod/settings.php:745
 msgid "Plugin Settings"
 msgstr "Plugin-Einstellungen"
 
-#: mod/settings.php:750
-msgid "Off"
-msgstr "Aus"
-
-#: mod/settings.php:750
-msgid "On"
-msgstr "An"
-
-#: mod/settings.php:758
+#: mod/settings.php:767
 msgid "Additional Features"
 msgstr "Zusätzliche Features"
 
-#: mod/settings.php:768 mod/settings.php:772
+#: mod/settings.php:777 mod/settings.php:781
 msgid "General Social Media Settings"
 msgstr "Allgemeine Einstellungen zu Sozialen Medien"
 
-#: mod/settings.php:778
+#: mod/settings.php:787
 msgid "Disable intelligent shortening"
 msgstr "Intelligentes Link kürzen ausschalten"
 
-#: mod/settings.php:780
+#: mod/settings.php:789
 msgid ""
 "Normally the system tries to find the best link to add to shortened posts. "
 "If this option is enabled then every shortened post will always point to the"
 " original friendica post."
 msgstr "Normalerweise versucht das System den besten Link zu finden um ihn zu gekürzten Postings hinzu zu fügen. Wird diese Option ausgewählt wird stets ein Link auf die originale Friendica Nachricht beigefügt."
 
-#: mod/settings.php:786
+#: mod/settings.php:795
 msgid "Automatically follow any GNU Social (OStatus) followers/mentioners"
 msgstr "Automatisch allen GNU Social (OStatus) Followern/Erwähnern folgen"
 
-#: mod/settings.php:788
+#: mod/settings.php:797
 msgid ""
 "If you receive a message from an unknown OStatus user, this option decides "
 "what to do. If it is checked, a new contact will be created for every "
 "unknown user."
 msgstr "Wenn du eine Nachricht eines unbekannten OStatus Nutzers bekommst, entscheidet diese Option wie diese behandelt werden soll. Ist die Option aktiviert, wird ein neuer Kontakt für den Verfasser erstellt,."
 
-#: mod/settings.php:797
+#: mod/settings.php:806
 msgid "Your legacy GNU Social account"
 msgstr "Dein alter GNU Social Account"
 
-#: mod/settings.php:799
+#: mod/settings.php:808
 msgid ""
 "If you enter your old GNU Social/Statusnet account name here (in the format "
 "user@domain.tld), your contacts will be added automatically. The field will "
 "be emptied when done."
 msgstr "Wenn du deinen alten GNU Socual/Statusnet Accountnamen hier angibst (Format name@domain.tld) werden deine Kontakte automatisch hinzugefügt. Dieses Feld wird geleert, wenn die Kontakte hinzugefügt wurden."
 
-#: mod/settings.php:802
+#: mod/settings.php:811
 msgid "Repair OStatus subscriptions"
 msgstr "OStatus Abonnements reparieren"
 
-#: mod/settings.php:811 mod/settings.php:812
+#: mod/settings.php:820 mod/settings.php:821
 #, php-format
 msgid "Built-in support for %s connectivity is %s"
 msgstr "Eingebaute Unterstützung für Verbindungen zu %s ist %s"
 
-#: mod/settings.php:811 mod/dfrn_request.php:858
+#: mod/settings.php:820 mod/dfrn_request.php:865
 #: include/contact_selectors.php:80
 msgid "Diaspora"
 msgstr "Diaspora"
 
-#: mod/settings.php:811 mod/settings.php:812
+#: mod/settings.php:820 mod/settings.php:821
 msgid "enabled"
 msgstr "eingeschaltet"
 
-#: mod/settings.php:811 mod/settings.php:812
+#: mod/settings.php:820 mod/settings.php:821
 msgid "disabled"
 msgstr "ausgeschaltet"
 
-#: mod/settings.php:812
+#: mod/settings.php:821
 msgid "GNU Social (OStatus)"
 msgstr "GNU Social (OStatus)"
 
-#: mod/settings.php:848
+#: mod/settings.php:857
 msgid "Email access is disabled on this site."
 msgstr "Zugriff auf E-Mails für diese Seite deaktiviert."
 
-#: mod/settings.php:860
+#: mod/settings.php:869
 msgid "Email/Mailbox Setup"
 msgstr "E-Mail/Postfach-Einstellungen"
 
-#: mod/settings.php:861
+#: mod/settings.php:870
 msgid ""
 "If you wish to communicate with email contacts using this service "
 "(optional), please specify how to connect to your mailbox."
 msgstr "Wenn Du mit E-Mail-Kontakten über diesen Service kommunizieren möchtest (optional), gib bitte die Einstellungen für Dein Postfach an."
 
-#: mod/settings.php:862
+#: mod/settings.php:871
 msgid "Last successful email check:"
 msgstr "Letzter erfolgreicher E-Mail Check"
 
-#: mod/settings.php:864
+#: mod/settings.php:873
 msgid "IMAP server name:"
 msgstr "IMAP-Server-Name:"
 
-#: mod/settings.php:865
+#: mod/settings.php:874
 msgid "IMAP port:"
 msgstr "IMAP-Port:"
 
-#: mod/settings.php:866
+#: mod/settings.php:875
 msgid "Security:"
 msgstr "Sicherheit:"
 
-#: mod/settings.php:866 mod/settings.php:871
+#: mod/settings.php:875 mod/settings.php:880
 msgid "None"
 msgstr "Keine"
 
-#: mod/settings.php:867
+#: mod/settings.php:876
 msgid "Email login name:"
 msgstr "E-Mail-Login-Name:"
 
-#: mod/settings.php:868
+#: mod/settings.php:877
 msgid "Email password:"
 msgstr "E-Mail-Passwort:"
 
-#: mod/settings.php:869
+#: mod/settings.php:878
 msgid "Reply-to address:"
 msgstr "Reply-to Adresse:"
 
-#: mod/settings.php:870
+#: mod/settings.php:879
 msgid "Send public posts to all email contacts:"
 msgstr "Sende öffentliche Beiträge an alle E-Mail-Kontakte:"
 
-#: mod/settings.php:871
+#: mod/settings.php:880
 msgid "Action after import:"
 msgstr "Aktion nach Import:"
 
-#: mod/settings.php:871
+#: mod/settings.php:880
 msgid "Mark as seen"
 msgstr "Als gelesen markieren"
 
-#: mod/settings.php:871
+#: mod/settings.php:880
 msgid "Move to folder"
 msgstr "In einen Ordner verschieben"
 
-#: mod/settings.php:872
+#: mod/settings.php:881
 msgid "Move to folder:"
 msgstr "In diesen Ordner verschieben:"
 
-#: mod/settings.php:958
+#: mod/settings.php:967
 msgid "Display Settings"
 msgstr "Anzeige-Einstellungen"
 
-#: mod/settings.php:964 mod/settings.php:982
+#: mod/settings.php:973 mod/settings.php:991
 msgid "Display Theme:"
 msgstr "Theme:"
 
-#: mod/settings.php:965
+#: mod/settings.php:974
 msgid "Mobile Theme:"
 msgstr "Mobiles Theme"
 
-#: mod/settings.php:966
+#: mod/settings.php:975
 msgid "Update browser every xx seconds"
 msgstr "Browser alle xx Sekunden aktualisieren"
 
-#: mod/settings.php:966
+#: mod/settings.php:975
 msgid "Minimum of 10 seconds. Enter -1 to disable it."
 msgstr "Minimum sind 10 Sekeunden. Gib -1 ein um abzuschalten."
 
-#: mod/settings.php:967
+#: mod/settings.php:976
 msgid "Number of items to display per page:"
 msgstr "Zahl der Beiträge, die pro Netzwerkseite angezeigt werden sollen: "
 
-#: mod/settings.php:967 mod/settings.php:968
+#: mod/settings.php:976 mod/settings.php:977
 msgid "Maximum of 100 items"
 msgstr "Maximal 100 Beiträge"
 
-#: mod/settings.php:968
+#: mod/settings.php:977
 msgid "Number of items to display per page when viewed from mobile device:"
 msgstr "Zahl der Beiträge, die pro Netzwerkseite auf mobilen Geräten angezeigt werden sollen:"
 
-#: mod/settings.php:969
+#: mod/settings.php:978
 msgid "Don't show emoticons"
 msgstr "Keine Smilies anzeigen"
 
-#: mod/settings.php:970
+#: mod/settings.php:979
 msgid "Calendar"
 msgstr "Kalender"
 
-#: mod/settings.php:971
+#: mod/settings.php:980
 msgid "Beginning of week:"
 msgstr "Wochenbeginn:"
 
-#: mod/settings.php:972
+#: mod/settings.php:981
 msgid "Don't show notices"
 msgstr "Info-Popups nicht anzeigen"
 
-#: mod/settings.php:973
+#: mod/settings.php:982
 msgid "Infinite scroll"
 msgstr "Endloses Scrollen"
 
-#: mod/settings.php:974
+#: mod/settings.php:983
 msgid "Automatic updates only at the top of the network page"
 msgstr "Automatische Updates nur, wenn Du oben auf der Netzwerkseite bist."
 
-#: mod/settings.php:976 view/theme/cleanzero/config.php:82
+#: mod/settings.php:985 view/theme/cleanzero/config.php:82
 #: view/theme/dispy/config.php:72 view/theme/quattro/config.php:66
-#: view/theme/diabook/config.php:150 view/theme/clean/config.php:85
-#: view/theme/vier/config.php:109 view/theme/duepuntozero/config.php:61
+#: view/theme/diabook/config.php:150 view/theme/vier/config.php:109
+#: view/theme/duepuntozero/config.php:61
 msgid "Theme settings"
 msgstr "Themeneinstellungen"
 
-#: mod/settings.php:1053
+#: mod/settings.php:1062
 msgid "User Types"
 msgstr "Nutzer Art"
 
-#: mod/settings.php:1054
+#: mod/settings.php:1063
 msgid "Community Types"
 msgstr "Gemeinschafts Art"
 
-#: mod/settings.php:1055
+#: mod/settings.php:1064
 msgid "Normal Account Page"
 msgstr "Normales Konto"
 
-#: mod/settings.php:1056
+#: mod/settings.php:1065
 msgid "This account is a normal personal profile"
 msgstr "Dieses Konto ist ein normales persönliches Profil"
 
-#: mod/settings.php:1059
+#: mod/settings.php:1068
 msgid "Soapbox Page"
 msgstr "Marktschreier-Konto"
 
-#: mod/settings.php:1060
+#: mod/settings.php:1069
 msgid "Automatically approve all connection/friend requests as read-only fans"
 msgstr "Kontaktanfragen werden automatisch als Nurlese-Fans akzeptiert"
 
-#: mod/settings.php:1063
+#: mod/settings.php:1072
 msgid "Community Forum/Celebrity Account"
 msgstr "Forum/Promi-Konto"
 
-#: mod/settings.php:1064
+#: mod/settings.php:1073
 msgid ""
 "Automatically approve all connection/friend requests as read-write fans"
 msgstr "Kontaktanfragen werden automatisch als Lese-und-Schreib-Fans akzeptiert"
 
-#: mod/settings.php:1067
+#: mod/settings.php:1076
 msgid "Automatic Friend Page"
 msgstr "Automatische Freunde Seite"
 
-#: mod/settings.php:1068
+#: mod/settings.php:1077
 msgid "Automatically approve all connection/friend requests as friends"
 msgstr "Kontaktanfragen werden automatisch als Freund akzeptiert"
 
-#: mod/settings.php:1071
+#: mod/settings.php:1080
 msgid "Private Forum [Experimental]"
 msgstr "Privates Forum [Versuchsstadium]"
 
-#: mod/settings.php:1072
+#: mod/settings.php:1081
 msgid "Private forum - approved members only"
 msgstr "Privates Forum, nur für Mitglieder"
 
-#: mod/settings.php:1084
+#: mod/settings.php:1093
 msgid "OpenID:"
 msgstr "OpenID:"
 
-#: mod/settings.php:1084
+#: mod/settings.php:1093
 msgid "(Optional) Allow this OpenID to login to this account."
 msgstr "(Optional) Erlaube die Anmeldung für dieses Konto mit dieser OpenID."
 
-#: mod/settings.php:1094
+#: mod/settings.php:1103
 msgid "Publish your default profile in your local site directory?"
 msgstr "Darf Dein Standardprofil im Verzeichnis dieses Servers veröffentlicht werden?"
 
-#: mod/settings.php:1100
+#: mod/settings.php:1109
 msgid "Publish your default profile in the global social directory?"
 msgstr "Darf Dein Standardprofil im weltweiten Verzeichnis veröffentlicht werden?"
 
-#: mod/settings.php:1108
+#: mod/settings.php:1117
 msgid "Hide your contact/friend list from viewers of your default profile?"
 msgstr "Liste der Kontakte vor Betrachtern des Standardprofils verbergen?"
 
-#: mod/settings.php:1112 include/acl_selectors.php:331
+#: mod/settings.php:1121 include/acl_selectors.php:331
 msgid "Hide your profile details from unknown viewers?"
 msgstr "Profil-Details vor unbekannten Betrachtern verbergen?"
 
-#: mod/settings.php:1112
+#: mod/settings.php:1121
 msgid ""
 "If enabled, posting public messages to Diaspora and other networks isn't "
 "possible."
 msgstr "Wenn aktiviert, ist das senden öffentliche Nachrichten zu Diaspora und anderen Netzwerken nicht möglich"
 
-#: mod/settings.php:1117
+#: mod/settings.php:1126
 msgid "Allow friends to post to your profile page?"
 msgstr "Dürfen Deine Kontakte auf Deine Pinnwand schreiben?"
 
-#: mod/settings.php:1123
+#: mod/settings.php:1132
 msgid "Allow friends to tag your posts?"
 msgstr "Dürfen Deine Kontakte Deine Beiträge mit Schlagwörtern versehen?"
 
-#: mod/settings.php:1129
+#: mod/settings.php:1138
 msgid "Allow us to suggest you as a potential friend to new members?"
 msgstr "Dürfen wir Dich neuen Mitgliedern als potentiellen Kontakt vorschlagen?"
 
-#: mod/settings.php:1135
+#: mod/settings.php:1144
 msgid "Permit unknown people to send you private mail?"
 msgstr "Dürfen Dir Unbekannte private Nachrichten schicken?"
 
-#: mod/settings.php:1143
+#: mod/settings.php:1152
 msgid "Profile is <strong>not published</strong>."
 msgstr "Profil ist <strong>nicht veröffentlicht</strong>."
 
-#: mod/settings.php:1151
+#: mod/settings.php:1160
 #, php-format
 msgid "Your Identity Address is <strong>'%s'</strong> or '%s'."
 msgstr "Die Adresse deines Profils lautet <strong>'%s'</strong> oder '%s'."
 
-#: mod/settings.php:1158
+#: mod/settings.php:1167
 msgid "Automatically expire posts after this many days:"
 msgstr "Beiträge verfallen automatisch nach dieser Anzahl von Tagen:"
 
-#: mod/settings.php:1158
+#: mod/settings.php:1167
 msgid "If empty, posts will not expire. Expired posts will be deleted"
 msgstr "Wenn leer verfallen Beiträge nie automatisch. Verfallene Beiträge werden gelöscht."
 
-#: mod/settings.php:1159
+#: mod/settings.php:1168
 msgid "Advanced expiration settings"
 msgstr "Erweiterte Verfallseinstellungen"
 
-#: mod/settings.php:1160
+#: mod/settings.php:1169
 msgid "Advanced Expiration"
 msgstr "Erweitertes Verfallen"
 
-#: mod/settings.php:1161
+#: mod/settings.php:1170
 msgid "Expire posts:"
 msgstr "Beiträge verfallen lassen:"
 
-#: mod/settings.php:1162
+#: mod/settings.php:1171
 msgid "Expire personal notes:"
 msgstr "Persönliche Notizen verfallen lassen:"
 
-#: mod/settings.php:1163
+#: mod/settings.php:1172
 msgid "Expire starred posts:"
 msgstr "Markierte Beiträge verfallen lassen:"
 
-#: mod/settings.php:1164
+#: mod/settings.php:1173
 msgid "Expire photos:"
 msgstr "Fotos verfallen lassen:"
 
-#: mod/settings.php:1165
+#: mod/settings.php:1174
 msgid "Only expire posts by others:"
 msgstr "Nur Beiträge anderer verfallen:"
 
-#: mod/settings.php:1193
+#: mod/settings.php:1202
 msgid "Account Settings"
 msgstr "Kontoeinstellungen"
 
-#: mod/settings.php:1201
+#: mod/settings.php:1210
 msgid "Password Settings"
 msgstr "Passwort-Einstellungen"
 
-#: mod/settings.php:1202 mod/register.php:274
+#: mod/settings.php:1211 mod/register.php:274
 msgid "New Password:"
 msgstr "Neues Passwort:"
 
-#: mod/settings.php:1203 mod/register.php:275
+#: mod/settings.php:1212 mod/register.php:275
 msgid "Confirm:"
 msgstr "Bestätigen:"
 
-#: mod/settings.php:1203
+#: mod/settings.php:1212
 msgid "Leave password fields blank unless changing"
 msgstr "Lass die Passwort-Felder leer, außer Du willst das Passwort ändern"
 
-#: mod/settings.php:1204
+#: mod/settings.php:1213
 msgid "Current Password:"
 msgstr "Aktuelles Passwort:"
 
-#: mod/settings.php:1204 mod/settings.php:1205
+#: mod/settings.php:1213 mod/settings.php:1214
 msgid "Your current password to confirm the changes"
 msgstr "Dein aktuelles Passwort um die Änderungen zu bestätigen"
 
-#: mod/settings.php:1205
+#: mod/settings.php:1214
 msgid "Password:"
 msgstr "Passwort:"
 
-#: mod/settings.php:1209
+#: mod/settings.php:1218
 msgid "Basic Settings"
 msgstr "Grundeinstellungen"
 
-#: mod/settings.php:1210 include/identity.php:587
+#: mod/settings.php:1219 include/identity.php:588
 msgid "Full Name:"
 msgstr "Kompletter Name:"
 
-#: mod/settings.php:1211
+#: mod/settings.php:1220
 msgid "Email Address:"
 msgstr "E-Mail-Adresse:"
 
-#: mod/settings.php:1212
+#: mod/settings.php:1221
 msgid "Your Timezone:"
 msgstr "Deine Zeitzone:"
 
-#: mod/settings.php:1213
+#: mod/settings.php:1222
 msgid "Your Language:"
 msgstr "Deine Sprache:"
 
-#: mod/settings.php:1213
+#: mod/settings.php:1222
 msgid ""
 "Set the language we use to show you friendica interface and to send you "
 "emails"
 msgstr "Wähle die Sprache, in der wir Dir die Friendica-Oberfläche präsentieren sollen und Dir E-Mail schicken"
 
-#: mod/settings.php:1214
+#: mod/settings.php:1223
 msgid "Default Post Location:"
 msgstr "Standardstandort:"
 
-#: mod/settings.php:1215
+#: mod/settings.php:1224
 msgid "Use Browser Location:"
 msgstr "Standort des Browsers verwenden:"
 
-#: mod/settings.php:1218
+#: mod/settings.php:1227
 msgid "Security and Privacy Settings"
 msgstr "Sicherheits- und Privatsphäre-Einstellungen"
 
-#: mod/settings.php:1220
+#: mod/settings.php:1229
 msgid "Maximum Friend Requests/Day:"
 msgstr "Maximale Anzahl von Freundschaftsanfragen/Tag:"
 
-#: mod/settings.php:1220 mod/settings.php:1250
+#: mod/settings.php:1229 mod/settings.php:1259
 msgid "(to prevent spam abuse)"
 msgstr "(um SPAM zu vermeiden)"
 
-#: mod/settings.php:1221
+#: mod/settings.php:1230
 msgid "Default Post Permissions"
 msgstr "Standard-Zugriffsrechte für Beiträge"
 
-#: mod/settings.php:1222
+#: mod/settings.php:1231
 msgid "(click to open/close)"
 msgstr "(klicke zum öffnen/schließen)"
 
-#: mod/settings.php:1231 mod/photos.php:1199 mod/photos.php:1584
+#: mod/settings.php:1240 mod/photos.php:1199 mod/photos.php:1584
 msgid "Show to Groups"
 msgstr "Zeige den Gruppen"
 
-#: mod/settings.php:1232 mod/photos.php:1200 mod/photos.php:1585
+#: mod/settings.php:1241 mod/photos.php:1200 mod/photos.php:1585
 msgid "Show to Contacts"
 msgstr "Zeige den Kontakten"
 
-#: mod/settings.php:1233
+#: mod/settings.php:1242
 msgid "Default Private Post"
 msgstr "Privater Standardbeitrag"
 
-#: mod/settings.php:1234
+#: mod/settings.php:1243
 msgid "Default Public Post"
 msgstr "Öffentlicher Standardbeitrag"
 
-#: mod/settings.php:1238
+#: mod/settings.php:1247
 msgid "Default Permissions for New Posts"
 msgstr "Standardberechtigungen für neue Beiträge"
 
-#: mod/settings.php:1250
+#: mod/settings.php:1259
 msgid "Maximum private messages per day from unknown people:"
 msgstr "Maximale Anzahl privater Nachrichten von Unbekannten pro Tag:"
 
-#: mod/settings.php:1253
+#: mod/settings.php:1262
 msgid "Notification Settings"
 msgstr "Benachrichtigungseinstellungen"
 
-#: mod/settings.php:1254
+#: mod/settings.php:1263
 msgid "By default post a status message when:"
 msgstr "Standardmäßig eine Statusnachricht posten, wenn:"
 
-#: mod/settings.php:1255
+#: mod/settings.php:1264
 msgid "accepting a friend request"
 msgstr "– Du eine Kontaktanfrage akzeptierst"
 
-#: mod/settings.php:1256
+#: mod/settings.php:1265
 msgid "joining a forum/community"
 msgstr "– Du einem Forum/einer Gemeinschaftsseite beitrittst"
 
-#: mod/settings.php:1257
+#: mod/settings.php:1266
 msgid "making an <em>interesting</em> profile change"
 msgstr "– Du eine <em>interessante</em> Änderung an Deinem Profil durchführst"
 
-#: mod/settings.php:1258
+#: mod/settings.php:1267
 msgid "Send a notification email when:"
 msgstr "Benachrichtigungs-E-Mail senden wenn:"
 
-#: mod/settings.php:1259
+#: mod/settings.php:1268
 msgid "You receive an introduction"
 msgstr "– Du eine Kontaktanfrage erhältst"
 
-#: mod/settings.php:1260
+#: mod/settings.php:1269
 msgid "Your introductions are confirmed"
 msgstr "– eine Deiner Kontaktanfragen akzeptiert wurde"
 
-#: mod/settings.php:1261
+#: mod/settings.php:1270
 msgid "Someone writes on your profile wall"
 msgstr "– jemand etwas auf Deine Pinnwand schreibt"
 
-#: mod/settings.php:1262
+#: mod/settings.php:1271
 msgid "Someone writes a followup comment"
 msgstr "– jemand auch einen Kommentar verfasst"
 
-#: mod/settings.php:1263
+#: mod/settings.php:1272
 msgid "You receive a private message"
 msgstr "– Du eine private Nachricht erhältst"
 
-#: mod/settings.php:1264
+#: mod/settings.php:1273
 msgid "You receive a friend suggestion"
 msgstr "– Du eine Empfehlung erhältst"
 
-#: mod/settings.php:1265
+#: mod/settings.php:1274
 msgid "You are tagged in a post"
 msgstr "– Du in einem Beitrag erwähnt wirst"
 
-#: mod/settings.php:1266
+#: mod/settings.php:1275
 msgid "You are poked/prodded/etc. in a post"
 msgstr "– Du von jemandem angestupst oder sonstwie behandelt wirst"
 
-#: mod/settings.php:1268
+#: mod/settings.php:1277
 msgid "Activate desktop notifications"
 msgstr "Desktop Benachrichtigungen einschalten"
 
-#: mod/settings.php:1268
+#: mod/settings.php:1277
 msgid "Show desktop popup on new notifications"
 msgstr "Desktop Benachrichtigungen einschalten"
 
-#: mod/settings.php:1270
+#: mod/settings.php:1279
 msgid "Text-only notification emails"
 msgstr "Benachrichtigungs E-Mail als Rein-Text."
 
-#: mod/settings.php:1272
+#: mod/settings.php:1281
 msgid "Send text only notification emails, without the html part"
 msgstr "Sende Benachrichtigungs E-Mail als Rein-Text - ohne HTML-Teil"
 
-#: mod/settings.php:1274
+#: mod/settings.php:1283
 msgid "Advanced Account/Page Type Settings"
 msgstr "Erweiterte Konto-/Seitentyp-Einstellungen"
 
-#: mod/settings.php:1275
+#: mod/settings.php:1284
 msgid "Change the behaviour of this account for special situations"
 msgstr "Verhalten dieses Kontos in bestimmten Situationen:"
 
-#: mod/settings.php:1278
+#: mod/settings.php:1287
 msgid "Relocate"
 msgstr "Umziehen"
 
-#: mod/settings.php:1279
+#: mod/settings.php:1288
 msgid ""
 "If you have moved this profile from another server, and some of your "
 "contacts don't receive your updates, try pushing this button."
 msgstr "Wenn Du Dein Profil von einem anderen Server umgezogen hast und einige Deiner Kontakte Deine Beiträge nicht erhalten, verwende diesen Button."
 
-#: mod/settings.php:1280
+#: mod/settings.php:1289
 msgid "Resend relocate message to contacts"
 msgstr "Umzugsbenachrichtigung erneut an Kontakte senden"
 
-#: mod/dfrn_request.php:95
+#: mod/dfrn_request.php:96
 msgid "This introduction has already been accepted."
 msgstr "Diese Kontaktanfrage wurde bereits akzeptiert."
 
-#: mod/dfrn_request.php:120 mod/dfrn_request.php:519
+#: mod/dfrn_request.php:119 mod/dfrn_request.php:516
 msgid "Profile location is not valid or does not contain profile information."
 msgstr "Profiladresse ist ungültig oder stellt keine Profildaten zur Verfügung."
 
-#: mod/dfrn_request.php:125 mod/dfrn_request.php:524
+#: mod/dfrn_request.php:124 mod/dfrn_request.php:521
 msgid "Warning: profile location has no identifiable owner name."
 msgstr "Warnung: Es konnte kein Name des Besitzers von der angegebenen Profiladresse gefunden werden."
 
-#: mod/dfrn_request.php:127 mod/dfrn_request.php:526
+#: mod/dfrn_request.php:126 mod/dfrn_request.php:523
 msgid "Warning: profile location has no profile photo."
 msgstr "Warnung: Es gibt kein Profilbild bei der angegebenen Profiladresse."
 
-#: mod/dfrn_request.php:130 mod/dfrn_request.php:529
+#: mod/dfrn_request.php:129 mod/dfrn_request.php:526
 #, php-format
 msgid "%d required parameter was not found at the given location"
 msgid_plural "%d required parameters were not found at the given location"
 msgstr[0] "%d benötigter Parameter wurde an der angegebenen Stelle nicht gefunden"
 msgstr[1] "%d benötigte Parameter wurden an der angegebenen Stelle nicht gefunden"
 
-#: mod/dfrn_request.php:173
+#: mod/dfrn_request.php:172
 msgid "Introduction complete."
 msgstr "Kontaktanfrage abgeschlossen."
 
-#: mod/dfrn_request.php:215
+#: mod/dfrn_request.php:214
 msgid "Unrecoverable protocol error."
 msgstr "Nicht behebbarer Protokollfehler."
 
-#: mod/dfrn_request.php:243
+#: mod/dfrn_request.php:242
 msgid "Profile unavailable."
 msgstr "Profil nicht verfügbar."
 
-#: mod/dfrn_request.php:268
+#: mod/dfrn_request.php:267
 #, php-format
 msgid "%s has received too many connection requests today."
 msgstr "%s hat heute zu viele Freundschaftsanfragen erhalten."
 
-#: mod/dfrn_request.php:269
+#: mod/dfrn_request.php:268
 msgid "Spam protection measures have been invoked."
 msgstr "Maßnahmen zum Spamschutz wurden ergriffen."
 
-#: mod/dfrn_request.php:270
+#: mod/dfrn_request.php:269
 msgid "Friends are advised to please try again in 24 hours."
 msgstr "Freunde sind angehalten, es in 24 Stunden erneut zu versuchen."
 
-#: mod/dfrn_request.php:332
+#: mod/dfrn_request.php:331
 msgid "Invalid locator"
 msgstr "Ungültiger Locator"
 
-#: mod/dfrn_request.php:341
+#: mod/dfrn_request.php:340
 msgid "Invalid email address."
 msgstr "Ungültige E-Mail-Adresse."
 
-#: mod/dfrn_request.php:368
+#: mod/dfrn_request.php:367
 msgid "This account has not been configured for email. Request failed."
 msgstr "Dieses Konto ist nicht für E-Mail konfiguriert. Anfrage fehlgeschlagen."
 
-#: mod/dfrn_request.php:464
-msgid "Unable to resolve your name at the provided location."
-msgstr "Konnte Deinen Namen an der angegebenen Stelle nicht finden."
-
-#: mod/dfrn_request.php:477
+#: mod/dfrn_request.php:474
 msgid "You have already introduced yourself here."
 msgstr "Du hast Dich hier bereits vorgestellt."
 
-#: mod/dfrn_request.php:481
+#: mod/dfrn_request.php:478
 #, php-format
 msgid "Apparently you are already friends with %s."
 msgstr "Es scheint so, als ob Du bereits mit %s befreundet bist."
 
-#: mod/dfrn_request.php:502
+#: mod/dfrn_request.php:499
 msgid "Invalid profile URL."
 msgstr "Ungültige Profil-URL."
 
-#: mod/dfrn_request.php:508 include/follow.php:72
+#: mod/dfrn_request.php:505 include/follow.php:72
 msgid "Disallowed profile URL."
 msgstr "Nicht erlaubte Profil-URL."
 
-#: mod/dfrn_request.php:599
+#: mod/dfrn_request.php:596
 msgid "Your introduction has been sent."
 msgstr "Deine Kontaktanfrage wurde gesendet."
 
-#: mod/dfrn_request.php:652
+#: mod/dfrn_request.php:636
+msgid ""
+"Remote subscription can't be done for your network. Please subscribe "
+"directly on your system."
+msgstr "Entferntes abon­nie­ren kann für dein Netzwerk nicht durchgeführt werden. Bitte nutze direkt die Abonnieren-Funktion deines Systems.   "
+
+#: mod/dfrn_request.php:659
 msgid "Please login to confirm introduction."
 msgstr "Bitte melde Dich an, um die Kontaktanfrage zu bestätigen."
 
-#: mod/dfrn_request.php:662
+#: mod/dfrn_request.php:669
 msgid ""
 "Incorrect identity currently logged in. Please login to "
 "<strong>this</strong> profile."
 msgstr "Momentan bist Du mit einer anderen Identität angemeldet. Bitte melde Dich mit <strong>diesem</strong> Profil an."
 
-#: mod/dfrn_request.php:676 mod/dfrn_request.php:693
+#: mod/dfrn_request.php:683 mod/dfrn_request.php:700
 msgid "Confirm"
 msgstr "Bestätigen"
 
-#: mod/dfrn_request.php:688
+#: mod/dfrn_request.php:695
 msgid "Hide this contact"
 msgstr "Verberge diesen Kontakt"
 
-#: mod/dfrn_request.php:691
+#: mod/dfrn_request.php:698
 #, php-format
 msgid "Welcome home %s."
 msgstr "Willkommen zurück %s."
 
-#: mod/dfrn_request.php:692
+#: mod/dfrn_request.php:699
 #, php-format
 msgid "Please confirm your introduction/connection request to %s."
 msgstr "Bitte bestätige Deine Kontaktanfrage bei %s."
 
-#: mod/dfrn_request.php:821
+#: mod/dfrn_request.php:828
 msgid ""
 "Please enter your 'Identity Address' from one of the following supported "
 "communications networks:"
 msgstr "Bitte gib die Adresse Deines Profils in einem der unterstützten sozialen Netzwerke an:"
 
-#: mod/dfrn_request.php:842
+#: mod/dfrn_request.php:849
 #, php-format
 msgid ""
 "If you are not yet a member of the free social web, <a "
@@ -5008,25 +5004,25 @@ msgid ""
 "join us today</a>."
 msgstr "Wenn du noch kein Mitglied dieses freien sozialen Netzwerks bist, <a href=\"%s/siteinfo\">folge diesem Link</a> um einen öffentlichen Friendica-Server zu finden und beizutreten."
 
-#: mod/dfrn_request.php:847
+#: mod/dfrn_request.php:854
 msgid "Friend/Connection Request"
 msgstr "Freundschafts-/Kontaktanfrage"
 
-#: mod/dfrn_request.php:848
+#: mod/dfrn_request.php:855
 msgid ""
 "Examples: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, "
 "testuser@identi.ca"
 msgstr "Beispiele: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, testuser@identi.ca"
 
-#: mod/dfrn_request.php:856 include/contact_selectors.php:76
+#: mod/dfrn_request.php:863 include/contact_selectors.php:76
 msgid "Friendica"
 msgstr "Friendica"
 
-#: mod/dfrn_request.php:857
+#: mod/dfrn_request.php:864
 msgid "StatusNet/Federated Social Web"
 msgstr "StatusNet/Federated Social Web"
 
-#: mod/dfrn_request.php:859
+#: mod/dfrn_request.php:866
 #, php-format
 msgid ""
 " - please do not use this form.  Instead, enter %s into your Diaspora search"
@@ -5114,7 +5110,7 @@ msgstr "Wähle einen Spitznamen für Dein Profil. Dieser muss mit einem Buchstab
 msgid "Choose a nickname: "
 msgstr "Spitznamen wählen: "
 
-#: mod/register.php:280 boot.php:1271 include/nav.php:108
+#: mod/register.php:280 boot.php:1405 include/nav.php:108
 msgid "Register"
 msgstr "Registrieren"
 
@@ -5156,11 +5152,11 @@ msgstr "Beiträge markiert mit: %s"
 msgid "Search results for: %s"
 msgstr "Suchergebnisse für: %s"
 
-#: mod/directory.php:149 include/identity.php:309 include/identity.php:609
+#: mod/directory.php:149 include/identity.php:313 include/identity.php:610
 msgid "Status:"
 msgstr "Status:"
 
-#: mod/directory.php:151 include/identity.php:311 include/identity.php:620
+#: mod/directory.php:151 include/identity.php:315 include/identity.php:621
 msgid "Homepage:"
 msgstr "Homepage:"
 
@@ -5488,7 +5484,7 @@ msgstr "Beispiele: cathy123, Cathy Williams, cathy@example.com"
 msgid "Since [date]:"
 msgstr "Seit [Datum]:"
 
-#: mod/profiles.php:724 include/identity.php:618
+#: mod/profiles.php:724 include/identity.php:619
 msgid "Sexual Preference:"
 msgstr "Sexuelle Vorlieben:"
 
@@ -5496,11 +5492,11 @@ msgstr "Sexuelle Vorlieben:"
 msgid "Homepage URL:"
 msgstr "Adresse der Homepage:"
 
-#: mod/profiles.php:726 include/identity.php:622
+#: mod/profiles.php:726 include/identity.php:623
 msgid "Hometown:"
 msgstr "Heimatort:"
 
-#: mod/profiles.php:727 include/identity.php:626
+#: mod/profiles.php:727 include/identity.php:627
 msgid "Political Views:"
 msgstr "Politische Ansichten:"
 
@@ -5516,11 +5512,11 @@ msgstr "Öffentliche Schlüsselwörter:"
 msgid "Private Keywords:"
 msgstr "Private Schlüsselwörter:"
 
-#: mod/profiles.php:731 include/identity.php:634
+#: mod/profiles.php:731 include/identity.php:635
 msgid "Likes:"
 msgstr "Likes:"
 
-#: mod/profiles.php:732 include/identity.php:636
+#: mod/profiles.php:732 include/identity.php:637
 msgid "Dislikes:"
 msgstr "Dislikes:"
 
@@ -5590,23 +5586,23 @@ msgstr "Alter: "
 msgid "Edit/Manage Profiles"
 msgstr "Bearbeite/Verwalte Profile"
 
-#: mod/profiles.php:814 include/identity.php:257 include/identity.php:283
+#: mod/profiles.php:814 include/identity.php:260 include/identity.php:286
 msgid "Change profile photo"
 msgstr "Profilbild ändern"
 
-#: mod/profiles.php:815 include/identity.php:258
+#: mod/profiles.php:815 include/identity.php:261
 msgid "Create New Profile"
 msgstr "Neues Profil anlegen"
 
-#: mod/profiles.php:826 include/identity.php:268
+#: mod/profiles.php:826 include/identity.php:271
 msgid "Profile Image"
 msgstr "Profilbild"
 
-#: mod/profiles.php:828 include/identity.php:271
+#: mod/profiles.php:828 include/identity.php:274
 msgid "visible to everybody"
 msgstr "sichtbar für jeden"
 
-#: mod/profiles.php:829 include/identity.php:272
+#: mod/profiles.php:829 include/identity.php:275
 msgid "Edit visibility"
 msgstr "Sichtbarkeit bearbeiten"
 
@@ -5618,55 +5614,55 @@ msgstr "Beitrag nicht gefunden"
 msgid "Edit post"
 msgstr "Beitrag bearbeiten"
 
-#: mod/editpost.php:111 include/conversation.php:1185
+#: mod/editpost.php:111 include/conversation.php:1184
 msgid "upload photo"
 msgstr "Bild hochladen"
 
-#: mod/editpost.php:112 include/conversation.php:1186
+#: mod/editpost.php:112 include/conversation.php:1185
 msgid "Attach file"
 msgstr "Datei anhängen"
 
-#: mod/editpost.php:113 include/conversation.php:1187
+#: mod/editpost.php:113 include/conversation.php:1186
 msgid "attach file"
 msgstr "Datei anhängen"
 
-#: mod/editpost.php:115 include/conversation.php:1189
+#: mod/editpost.php:115 include/conversation.php:1188
 msgid "web link"
 msgstr "Weblink"
 
-#: mod/editpost.php:116 include/conversation.php:1190
+#: mod/editpost.php:116 include/conversation.php:1189
 msgid "Insert video link"
 msgstr "Video-Adresse einfügen"
 
-#: mod/editpost.php:117 include/conversation.php:1191
+#: mod/editpost.php:117 include/conversation.php:1190
 msgid "video link"
 msgstr "Video-Link"
 
-#: mod/editpost.php:118 include/conversation.php:1192
+#: mod/editpost.php:118 include/conversation.php:1191
 msgid "Insert audio link"
 msgstr "Audio-Adresse einfügen"
 
-#: mod/editpost.php:119 include/conversation.php:1193
+#: mod/editpost.php:119 include/conversation.php:1192
 msgid "audio link"
 msgstr "Audio-Link"
 
-#: mod/editpost.php:120 include/conversation.php:1194
+#: mod/editpost.php:120 include/conversation.php:1193
 msgid "Set your location"
 msgstr "Deinen Standort festlegen"
 
-#: mod/editpost.php:121 include/conversation.php:1195
+#: mod/editpost.php:121 include/conversation.php:1194
 msgid "set location"
 msgstr "Ort setzen"
 
-#: mod/editpost.php:122 include/conversation.php:1196
+#: mod/editpost.php:122 include/conversation.php:1195
 msgid "Clear browser location"
 msgstr "Browser-Standort leeren"
 
-#: mod/editpost.php:123 include/conversation.php:1197
+#: mod/editpost.php:123 include/conversation.php:1196
 msgid "clear location"
 msgstr "Ort löschen"
 
-#: mod/editpost.php:125 include/conversation.php:1203
+#: mod/editpost.php:125 include/conversation.php:1202
 msgid "Permission settings"
 msgstr "Berechtigungseinstellungen"
 
@@ -5674,15 +5670,15 @@ msgstr "Berechtigungseinstellungen"
 msgid "CC: email addresses"
 msgstr "Cc: E-Mail-Addressen"
 
-#: mod/editpost.php:134 include/conversation.php:1212
+#: mod/editpost.php:134 include/conversation.php:1211
 msgid "Public post"
 msgstr "Öffentlicher Beitrag"
 
-#: mod/editpost.php:137 include/conversation.php:1199
+#: mod/editpost.php:137 include/conversation.php:1198
 msgid "Set title"
 msgstr "Titel setzen"
 
-#: mod/editpost.php:139 include/conversation.php:1201
+#: mod/editpost.php:139 include/conversation.php:1200
 msgid "Categories (comma-separated list)"
 msgstr "Kategorien (kommasepariert)"
 
@@ -5690,39 +5686,39 @@ msgstr "Kategorien (kommasepariert)"
 msgid "Example: bob@example.com, mary@example.com"
 msgstr "Z.B.: bob@example.com, mary@example.com"
 
-#: mod/friendica.php:59
+#: mod/friendica.php:70
 msgid "This is Friendica, version"
 msgstr "Dies ist Friendica, Version"
 
-#: mod/friendica.php:60
+#: mod/friendica.php:71
 msgid "running at web location"
 msgstr "die unter folgender Webadresse zu finden ist"
 
-#: mod/friendica.php:62
+#: mod/friendica.php:73
 msgid ""
 "Please visit <a href=\"http://friendica.com\">Friendica.com</a> to learn "
 "more about the Friendica project."
 msgstr "Bitte besuche <a href=\"http://friendica.com\">Friendica.com</a>, um mehr über das Friendica Projekt zu erfahren."
 
-#: mod/friendica.php:64
+#: mod/friendica.php:75
 msgid "Bug reports and issues: please visit"
 msgstr "Probleme oder Fehler gefunden? Bitte besuche"
 
-#: mod/friendica.php:64
+#: mod/friendica.php:75
 msgid "the bugtracker at github"
 msgstr "dem Bugtracker auf github"
 
-#: mod/friendica.php:65
+#: mod/friendica.php:76
 msgid ""
 "Suggestions, praise, donations, etc. - please email \"Info\" at Friendica - "
 "dot com"
 msgstr "Vorschläge, Lob, Spenden usw.: E-Mail an \"Info\" at Friendica - dot com"
 
-#: mod/friendica.php:79
+#: mod/friendica.php:90
 msgid "Installed plugins/addons/apps:"
 msgstr "Installierte Plugins/Erweiterungen/Apps"
 
-#: mod/friendica.php:92
+#: mod/friendica.php:103
 msgid "No installed plugins/addons/apps"
 msgstr "Keine Plugins/Erweiterungen/Apps installiert"
 
@@ -6080,12 +6076,12 @@ msgstr "Privates Foto"
 msgid "Public photo"
 msgstr "Öffentliches Foto"
 
-#: mod/photos.php:1609 include/conversation.php:1183
+#: mod/photos.php:1609 include/conversation.php:1182
 msgid "Share"
 msgstr "Teilen"
 
 #: mod/photos.php:1648 include/conversation.php:509
-#: include/conversation.php:1414
+#: include/conversation.php:1413
 msgid "Attending"
 msgid_plural "Attending"
 msgstr[0] "Teilnehmend"
@@ -6159,60 +6155,60 @@ msgstr "Beitrag nicht verfügbar."
 msgid "Item was not found."
 msgstr "Beitrag konnte nicht gefunden werden."
 
-#: boot.php:786
+#: boot.php:868
 msgid "Delete this item?"
 msgstr "Diesen Beitrag löschen?"
 
-#: boot.php:789
+#: boot.php:871
 msgid "show fewer"
 msgstr "weniger anzeigen"
 
-#: boot.php:1163
+#: boot.php:1292
 #, php-format
 msgid "Update %s failed. See error logs."
 msgstr "Update %s fehlgeschlagen. Bitte Fehlerprotokoll überprüfen."
 
-#: boot.php:1270
+#: boot.php:1404
 msgid "Create a New Account"
 msgstr "Neues Konto erstellen"
 
-#: boot.php:1295 include/nav.php:72
+#: boot.php:1429 include/nav.php:72
 msgid "Logout"
 msgstr "Abmelden"
 
-#: boot.php:1298
+#: boot.php:1432
 msgid "Nickname or Email address: "
 msgstr "Spitzname oder E-Mail-Adresse: "
 
-#: boot.php:1299
+#: boot.php:1433
 msgid "Password: "
 msgstr "Passwort: "
 
-#: boot.php:1300
+#: boot.php:1434
 msgid "Remember me"
 msgstr "Anmeldedaten merken"
 
-#: boot.php:1303
+#: boot.php:1437
 msgid "Or login using OpenID: "
 msgstr "Oder melde Dich mit Deiner OpenID an: "
 
-#: boot.php:1309
+#: boot.php:1443
 msgid "Forgot your password?"
 msgstr "Passwort vergessen?"
 
-#: boot.php:1312
+#: boot.php:1446
 msgid "Website Terms of Service"
 msgstr "Website Nutzungsbedingungen"
 
-#: boot.php:1313
+#: boot.php:1447
 msgid "terms of service"
 msgstr "Nutzungsbedingungen"
 
-#: boot.php:1315
+#: boot.php:1449
 msgid "Website Privacy Policy"
 msgstr "Website Datenschutzerklärung"
 
-#: boot.php:1316
+#: boot.php:1450
 msgid "privacy policy"
 msgstr "Datenschutzerklärung"
 
@@ -6273,25 +6269,25 @@ msgid ""
 "[pre]%s[/pre]"
 msgstr "Die Fehlermeldung lautet\n[pre]%s[/pre]"
 
-#: include/dbstructure.php:151
+#: include/dbstructure.php:153
 msgid "Errors encountered creating database tables."
 msgstr "Fehler aufgetreten während der Erzeugung der Datenbanktabellen."
 
-#: include/dbstructure.php:209
+#: include/dbstructure.php:230
 msgid "Errors encountered performing database changes."
 msgstr "Es sind Fehler beim Bearbeiten der Datenbank aufgetreten."
 
-#: include/auth.php:38
+#: include/auth.php:44
 msgid "Logged out."
 msgstr "Abgemeldet."
 
-#: include/auth.php:128 include/user.php:75
+#: include/auth.php:134 include/user.php:75
 msgid ""
 "We encountered a problem while logging in with the OpenID you provided. "
 "Please check the correct spelling of the ID."
 msgstr "Beim Versuch Dich mit der von Dir angegebenen OpenID anzumelden trat ein Problem auf. Bitte überprüfe, dass Du die OpenID richtig geschrieben hast."
 
-#: include/auth.php:128 include/user.php:75
+#: include/auth.php:134 include/user.php:75
 msgid "The error message was:"
 msgstr "Die Fehlermeldung lautete:"
 
@@ -6348,7 +6344,7 @@ msgstr "Netzwerke"
 msgid "All Networks"
 msgstr "Alle Netzwerke"
 
-#: include/contact_widgets.php:141 include/features.php:97
+#: include/contact_widgets.php:141 include/features.php:102
 msgid "Saved Folders"
 msgstr "Gespeicherte Ordner"
 
@@ -6367,194 +6363,194 @@ msgid_plural "%d contacts in common"
 msgstr[0] "%d gemeinsamer Kontakt"
 msgstr[1] "%d gemeinsame Kontakte"
 
-#: include/features.php:58
+#: include/features.php:63
 msgid "General Features"
 msgstr "Allgemeine Features"
 
-#: include/features.php:60
+#: include/features.php:65
 msgid "Multiple Profiles"
 msgstr "Mehrere Profile"
 
-#: include/features.php:60
+#: include/features.php:65
 msgid "Ability to create multiple profiles"
 msgstr "Möglichkeit mehrere Profile zu erstellen"
 
-#: include/features.php:61
+#: include/features.php:66
 msgid "Photo Location"
 msgstr "Aufnahmeort"
 
-#: include/features.php:61
+#: include/features.php:66
 msgid ""
 "Photo metadata is normally stripped. This extracts the location (if present)"
 " prior to stripping metadata and links it to a map."
 msgstr "Die Foto-Metadaten werden ausgelesen. Dadurch kann der Aufnahmeort (wenn vorhanden) in einer Karte angezeigt werden."
 
-#: include/features.php:66
+#: include/features.php:71
 msgid "Post Composition Features"
 msgstr "Beitragserstellung Features"
 
-#: include/features.php:67
+#: include/features.php:72
 msgid "Richtext Editor"
 msgstr "Web-Editor"
 
-#: include/features.php:67
+#: include/features.php:72
 msgid "Enable richtext editor"
 msgstr "Den Web-Editor für neue Beiträge aktivieren"
 
-#: include/features.php:68
+#: include/features.php:73
 msgid "Post Preview"
 msgstr "Beitragsvorschau"
 
-#: include/features.php:68
+#: include/features.php:73
 msgid "Allow previewing posts and comments before publishing them"
 msgstr "Die Vorschau von Beiträgen und Kommentaren vor dem absenden erlauben."
 
-#: include/features.php:69
+#: include/features.php:74
 msgid "Auto-mention Forums"
 msgstr "Foren automatisch erwähnen"
 
-#: include/features.php:69
+#: include/features.php:74
 msgid ""
 "Add/remove mention when a fourm page is selected/deselected in ACL window."
 msgstr "Automatisch eine @-Erwähnung eines Forums einfügen/entfehrnen, wenn dieses im ACL Fenster de-/markiert  wurde."
 
-#: include/features.php:74
+#: include/features.php:79
 msgid "Network Sidebar Widgets"
 msgstr "Widgets für Netzwerk und Seitenleiste"
 
-#: include/features.php:75
+#: include/features.php:80
 msgid "Search by Date"
 msgstr "Archiv"
 
-#: include/features.php:75
+#: include/features.php:80
 msgid "Ability to select posts by date ranges"
 msgstr "Möglichkeit die Beiträge nach Datumsbereichen zu sortieren"
 
-#: include/features.php:76 include/features.php:106
+#: include/features.php:81 include/features.php:111
 msgid "List Forums"
 msgstr "Zeige Foren"
 
-#: include/features.php:76
+#: include/features.php:81
 msgid "Enable widget to display the forums your are connected with"
 msgstr "Aktiviere Widget, um die Foren mit denen du verbunden bist anzuzeigen"
 
-#: include/features.php:77
+#: include/features.php:82
 msgid "Group Filter"
 msgstr "Gruppen Filter"
 
-#: include/features.php:77
+#: include/features.php:82
 msgid "Enable widget to display Network posts only from selected group"
 msgstr "Widget zur Darstellung der Beiträge nach Kontaktgruppen sortiert aktivieren."
 
-#: include/features.php:78
+#: include/features.php:83
 msgid "Network Filter"
 msgstr "Netzwerk Filter"
 
-#: include/features.php:78
+#: include/features.php:83
 msgid "Enable widget to display Network posts only from selected network"
 msgstr "Widget zum filtern der Beiträge in Abhängigkeit des Netzwerks aus dem der Ersteller sendet aktivieren."
 
-#: include/features.php:79
+#: include/features.php:84
 msgid "Save search terms for re-use"
 msgstr "Speichere Suchanfragen für spätere Wiederholung."
 
-#: include/features.php:84
+#: include/features.php:89
 msgid "Network Tabs"
 msgstr "Netzwerk Reiter"
 
-#: include/features.php:85
+#: include/features.php:90
 msgid "Network Personal Tab"
 msgstr "Netzwerk-Reiter: Persönlich"
 
-#: include/features.php:85
+#: include/features.php:90
 msgid "Enable tab to display only Network posts that you've interacted on"
 msgstr "Aktiviert einen Netzwerk-Reiter in dem Nachrichten angezeigt werden mit denen Du interagiert hast"
 
-#: include/features.php:86
+#: include/features.php:91
 msgid "Network New Tab"
 msgstr "Netzwerk-Reiter: Neue"
 
-#: include/features.php:86
+#: include/features.php:91
 msgid "Enable tab to display only new Network posts (from the last 12 hours)"
 msgstr "Aktiviert einen Netzwerk-Reiter in dem ausschließlich neue Beiträge (der letzten 12 Stunden) angezeigt werden"
 
-#: include/features.php:87
+#: include/features.php:92
 msgid "Network Shared Links Tab"
 msgstr "Netzwerk-Reiter: Geteilte Links"
 
-#: include/features.php:87
+#: include/features.php:92
 msgid "Enable tab to display only Network posts with links in them"
 msgstr "Aktiviert einen Netzwerk-Reiter der ausschließlich Nachrichten mit Links enthält"
 
-#: include/features.php:92
+#: include/features.php:97
 msgid "Post/Comment Tools"
 msgstr "Werkzeuge für Beiträge und Kommentare"
 
-#: include/features.php:93
+#: include/features.php:98
 msgid "Multiple Deletion"
 msgstr "Mehrere Beiträge löschen"
 
-#: include/features.php:93
+#: include/features.php:98
 msgid "Select and delete multiple posts/comments at once"
 msgstr "Mehrere Beiträge/Kommentare markieren und gleichzeitig löschen"
 
-#: include/features.php:94
+#: include/features.php:99
 msgid "Edit Sent Posts"
 msgstr "Gesendete Beiträge editieren"
 
-#: include/features.php:94
+#: include/features.php:99
 msgid "Edit and correct posts and comments after sending"
 msgstr "Erlaubt es Beiträge und Kommentare nach dem Senden zu editieren bzw.zu  korrigieren."
 
-#: include/features.php:95
+#: include/features.php:100
 msgid "Tagging"
 msgstr "Tagging"
 
-#: include/features.php:95
+#: include/features.php:100
 msgid "Ability to tag existing posts"
 msgstr "Möglichkeit bereits existierende Beiträge nachträglich mit Tags zu versehen."
 
-#: include/features.php:96
+#: include/features.php:101
 msgid "Post Categories"
 msgstr "Beitragskategorien"
 
-#: include/features.php:96
+#: include/features.php:101
 msgid "Add categories to your posts"
 msgstr "Eigene Beiträge mit Kategorien versehen"
 
-#: include/features.php:97
+#: include/features.php:102
 msgid "Ability to file posts under folders"
 msgstr "Beiträge in Ordnern speichern aktivieren"
 
-#: include/features.php:98
+#: include/features.php:103
 msgid "Dislike Posts"
 msgstr "Beiträge 'nicht mögen'"
 
-#: include/features.php:98
+#: include/features.php:103
 msgid "Ability to dislike posts/comments"
 msgstr "Ermöglicht es Beiträge mit einem Klick 'nicht zu mögen'"
 
-#: include/features.php:99
+#: include/features.php:104
 msgid "Star Posts"
 msgstr "Beiträge Markieren"
 
-#: include/features.php:99
+#: include/features.php:104
 msgid "Ability to mark special posts with a star indicator"
 msgstr "Erlaubt es Beiträge mit einem Stern-Indikator zu  markieren"
 
-#: include/features.php:100
+#: include/features.php:105
 msgid "Mute Post Notifications"
 msgstr "Benachrichtigungen für Beiträge Stumm schalten"
 
-#: include/features.php:100
+#: include/features.php:105
 msgid "Ability to mute notifications for a thread"
 msgstr "Möglichkeit Benachrichtigungen für einen Thread abbestellen zu können"
 
-#: include/features.php:105
+#: include/features.php:110
 msgid "Advanced Profile Settings"
 msgstr "Erweiterte Profil-Einstellungen"
 
-#: include/features.php:106
+#: include/features.php:111
 msgid "Show visitors public community forums at the Advanced Profile Page"
 msgstr "Zeige Besuchern öffentliche Gemeinschafts-Foren auf der Erweiterten Profil-Seite"
 
@@ -6713,134 +6709,134 @@ msgstr "Sekunden"
 msgid "%1$d %2$s ago"
 msgstr "%1$d %2$s her"
 
-#: include/datetime.php:474 include/items.php:2473
+#: include/datetime.php:474 include/items.php:2500
 #, php-format
 msgid "%s's birthday"
 msgstr "%ss Geburtstag"
 
-#: include/datetime.php:475 include/items.php:2474
+#: include/datetime.php:475 include/items.php:2501
 #, php-format
 msgid "Happy Birthday %s"
 msgstr "Herzlichen Glückwunsch %s"
 
-#: include/identity.php:44
+#: include/identity.php:42
 msgid "Requested account is not available."
 msgstr "Das angefragte Profil ist nicht vorhanden."
 
-#: include/identity.php:97 include/identity.php:281 include/identity.php:661
+#: include/identity.php:95 include/identity.php:284 include/identity.php:662
 msgid "Edit profile"
 msgstr "Profil bearbeiten"
 
-#: include/identity.php:241
+#: include/identity.php:244
 msgid "Atom feed"
 msgstr "Atom-Feed"
 
-#: include/identity.php:246
+#: include/identity.php:249
 msgid "Message"
 msgstr "Nachricht"
 
-#: include/identity.php:252 include/nav.php:185
+#: include/identity.php:255 include/nav.php:185
 msgid "Profiles"
 msgstr "Profile"
 
-#: include/identity.php:252
+#: include/identity.php:255
 msgid "Manage/edit profiles"
 msgstr "Profile verwalten/editieren"
 
-#: include/identity.php:421 include/identity.php:507
+#: include/identity.php:425 include/identity.php:509
 msgid "g A l F d"
 msgstr "l, d. F G \\U\\h\\r"
 
-#: include/identity.php:422 include/identity.php:508
+#: include/identity.php:426 include/identity.php:510
 msgid "F d"
 msgstr "d. F"
 
-#: include/identity.php:467 include/identity.php:554
+#: include/identity.php:471 include/identity.php:556
 msgid "[today]"
 msgstr "[heute]"
 
-#: include/identity.php:479
+#: include/identity.php:483
 msgid "Birthday Reminders"
 msgstr "Geburtstagserinnerungen"
 
-#: include/identity.php:480
+#: include/identity.php:484
 msgid "Birthdays this week:"
 msgstr "Geburtstage diese Woche:"
 
-#: include/identity.php:541
+#: include/identity.php:543
 msgid "[No description]"
 msgstr "[keine Beschreibung]"
 
-#: include/identity.php:565
+#: include/identity.php:567
 msgid "Event Reminders"
 msgstr "Veranstaltungserinnerungen"
 
-#: include/identity.php:566
+#: include/identity.php:568
 msgid "Events this week:"
 msgstr "Veranstaltungen diese Woche"
 
-#: include/identity.php:594
+#: include/identity.php:595
 msgid "j F, Y"
 msgstr "j F, Y"
 
-#: include/identity.php:595
+#: include/identity.php:596
 msgid "j F"
 msgstr "j F"
 
-#: include/identity.php:602
+#: include/identity.php:603
 msgid "Birthday:"
 msgstr "Geburtstag:"
 
-#: include/identity.php:606
+#: include/identity.php:607
 msgid "Age:"
 msgstr "Alter:"
 
-#: include/identity.php:615
+#: include/identity.php:616
 #, php-format
 msgid "for %1$d %2$s"
 msgstr "für %1$d %2$s"
 
-#: include/identity.php:628
+#: include/identity.php:629
 msgid "Religion:"
 msgstr "Religion:"
 
-#: include/identity.php:632
+#: include/identity.php:633
 msgid "Hobbies/Interests:"
 msgstr "Hobbies/Interessen:"
 
-#: include/identity.php:639
+#: include/identity.php:640
 msgid "Contact information and Social Networks:"
 msgstr "Kontaktinformationen und Soziale Netzwerke:"
 
-#: include/identity.php:641
+#: include/identity.php:642
 msgid "Musical interests:"
 msgstr "Musikalische Interessen:"
 
-#: include/identity.php:643
+#: include/identity.php:644
 msgid "Books, literature:"
 msgstr "Literatur/Bücher:"
 
-#: include/identity.php:645
+#: include/identity.php:646
 msgid "Television:"
 msgstr "Fernsehen:"
 
-#: include/identity.php:647
+#: include/identity.php:648
 msgid "Film/dance/culture/entertainment:"
 msgstr "Filme/Tänze/Kultur/Unterhaltung:"
 
-#: include/identity.php:649
+#: include/identity.php:650
 msgid "Love/Romance:"
 msgstr "Liebesleben:"
 
-#: include/identity.php:651
+#: include/identity.php:652
 msgid "Work/employment:"
 msgstr "Arbeit/Beschäftigung:"
 
-#: include/identity.php:653
+#: include/identity.php:654
 msgid "School/education:"
 msgstr "Schule/Ausbildung:"
 
-#: include/identity.php:657
+#: include/identity.php:658
 msgid "Forums:"
 msgstr "Foren:"
 
@@ -6856,6 +6852,38 @@ msgstr "Ereignisse und Kalender"
 msgid "Only You Can See This"
 msgstr "Nur Du kannst das sehen"
 
+#: include/like.php:167 include/conversation.php:122
+#: include/conversation.php:258 include/text.php:1998
+#: view/theme/diabook/theme.php:463
+msgid "event"
+msgstr "Event"
+
+#: include/like.php:184 include/conversation.php:141 include/diaspora.php:2185
+#: view/theme/diabook/theme.php:480
+#, php-format
+msgid "%1$s likes %2$s's %3$s"
+msgstr "%1$s mag %2$ss %3$s"
+
+#: include/like.php:186 include/conversation.php:144
+#, php-format
+msgid "%1$s doesn't like %2$s's %3$s"
+msgstr "%1$s mag %2$ss %3$s nicht"
+
+#: include/like.php:188
+#, php-format
+msgid "%1$s is attending %2$s's %3$s"
+msgstr "%1$s nimmt an %2$ss %3$s teil."
+
+#: include/like.php:190
+#, php-format
+msgid "%1$s is not attending %2$s's %3$s"
+msgstr "%1$s nimmt nicht an %2$ss %3$s teil."
+
+#: include/like.php:192
+#, php-format
+msgid "%1$s may attend %2$s's %3$s"
+msgstr "%1$s nimmt eventuell an %2$ss %3$s teil."
+
 #: include/acl_selectors.php:325
 msgid "Post to Email"
 msgstr "An E-Mail senden"
@@ -6879,6 +6907,10 @@ msgstr "zeigen"
 msgid "don't show"
 msgstr "nicht zeigen"
 
+#: include/acl_selectors.php:348
+msgid "Close"
+msgstr "Schließen"
+
 #: include/message.php:15 include/message.php:173
 msgid "[no subject]"
 msgstr "[kein Betreff]"
@@ -6887,31 +6919,31 @@ msgstr "[kein Betreff]"
 msgid "stopped following"
 msgstr "wird nicht mehr gefolgt"
 
-#: include/Contact.php:350 include/conversation.php:911
+#: include/Contact.php:337 include/conversation.php:911
 msgid "View Status"
 msgstr "Pinnwand anschauen"
 
-#: include/Contact.php:352 include/conversation.php:913
+#: include/Contact.php:339 include/conversation.php:913
 msgid "View Photos"
 msgstr "Bilder anschauen"
 
-#: include/Contact.php:353 include/conversation.php:914
+#: include/Contact.php:340 include/conversation.php:914
 msgid "Network Posts"
 msgstr "Netzwerkbeiträge"
 
-#: include/Contact.php:354 include/conversation.php:915
+#: include/Contact.php:341 include/conversation.php:915
 msgid "Edit Contact"
 msgstr "Kontakt bearbeiten"
 
-#: include/Contact.php:355
+#: include/Contact.php:342
 msgid "Drop Contact"
 msgstr "Kontakt löschen"
 
-#: include/Contact.php:356 include/conversation.php:916
+#: include/Contact.php:343 include/conversation.php:916
 msgid "Send PM"
 msgstr "Private Nachricht senden"
 
-#: include/Contact.php:357 include/conversation.php:920
+#: include/Contact.php:344 include/conversation.php:920
 msgid "Poke"
 msgstr "Anstupsen"
 
@@ -6974,153 +7006,153 @@ msgstr "Lösche die markierten Beiträge"
 msgid "Follow Thread"
 msgstr "Folge der Unterhaltung"
 
-#: include/conversation.php:1035
+#: include/conversation.php:1034
 #, php-format
 msgid "%s likes this."
 msgstr "%s mag das."
 
-#: include/conversation.php:1038
+#: include/conversation.php:1037
 #, php-format
 msgid "%s doesn't like this."
 msgstr "%s mag das nicht."
 
-#: include/conversation.php:1041
+#: include/conversation.php:1040
 #, php-format
 msgid "%s attends."
 msgstr "%s nimmt teil."
 
-#: include/conversation.php:1044
+#: include/conversation.php:1043
 #, php-format
 msgid "%s doesn't attend."
 msgstr "%s nimmt nicht teil."
 
-#: include/conversation.php:1047
+#: include/conversation.php:1046
 #, php-format
 msgid "%s attends maybe."
 msgstr "%s nimmt eventuell teil."
 
-#: include/conversation.php:1057
+#: include/conversation.php:1056
 msgid "and"
 msgstr "und"
 
-#: include/conversation.php:1063
+#: include/conversation.php:1062
 #, php-format
 msgid ", and %d other people"
 msgstr " und %d andere"
 
-#: include/conversation.php:1072
+#: include/conversation.php:1071
 #, php-format
 msgid "<span  %1$s>%2$d people</span> like this"
 msgstr "<span  %1$s>%2$d Personen</span> mögen das"
 
-#: include/conversation.php:1073
+#: include/conversation.php:1072
 #, php-format
 msgid "%s like this."
 msgstr "%s mögen das."
 
-#: include/conversation.php:1076
+#: include/conversation.php:1075
 #, php-format
 msgid "<span  %1$s>%2$d people</span> don't like this"
 msgstr "<span  %1$s>%2$d Personen</span> mögen das nicht"
 
-#: include/conversation.php:1077
+#: include/conversation.php:1076
 #, php-format
 msgid "%s don't like this."
 msgstr "%s mögen dies nicht."
 
-#: include/conversation.php:1080
+#: include/conversation.php:1079
 #, php-format
 msgid "<span  %1$s>%2$d people</span> attend"
 msgstr "<span %1$s>%2$d Personen</span> nehmen teil"
 
-#: include/conversation.php:1081
+#: include/conversation.php:1080
 #, php-format
 msgid "%s attend."
 msgstr "%s nehmen teil."
 
-#: include/conversation.php:1084
+#: include/conversation.php:1083
 #, php-format
 msgid "<span  %1$s>%2$d people</span> don't attend"
 msgstr "<span %1$s>%2$d Personen</span> nehmen nicht teil"
 
-#: include/conversation.php:1085
+#: include/conversation.php:1084
 #, php-format
 msgid "%s don't attend."
 msgstr "%s nehmen nicht teil."
 
-#: include/conversation.php:1088
+#: include/conversation.php:1087
 #, php-format
 msgid "<span  %1$s>%2$d people</span> anttend maybe"
 msgstr "<span %1$s>%2$d Personen</span> nehmen eventuell teil"
 
-#: include/conversation.php:1089
+#: include/conversation.php:1088
 #, php-format
 msgid "%s anttend maybe."
 msgstr "%s  nehmen vielleicht teil."
 
-#: include/conversation.php:1128 include/conversation.php:1146
+#: include/conversation.php:1127 include/conversation.php:1145
 msgid "Visible to <strong>everybody</strong>"
 msgstr "Für <strong>jedermann</strong> sichtbar"
 
-#: include/conversation.php:1130 include/conversation.php:1148
+#: include/conversation.php:1129 include/conversation.php:1147
 msgid "Please enter a video link/URL:"
 msgstr "Bitte Link/URL zum Video einfügen:"
 
-#: include/conversation.php:1131 include/conversation.php:1149
+#: include/conversation.php:1130 include/conversation.php:1148
 msgid "Please enter an audio link/URL:"
 msgstr "Bitte Link/URL zum Audio einfügen:"
 
-#: include/conversation.php:1132 include/conversation.php:1150
+#: include/conversation.php:1131 include/conversation.php:1149
 msgid "Tag term:"
 msgstr "Tag:"
 
-#: include/conversation.php:1134 include/conversation.php:1152
+#: include/conversation.php:1133 include/conversation.php:1151
 msgid "Where are you right now?"
 msgstr "Wo hältst Du Dich jetzt gerade auf?"
 
-#: include/conversation.php:1135
+#: include/conversation.php:1134
 msgid "Delete item(s)?"
 msgstr "Einträge löschen?"
 
-#: include/conversation.php:1204
+#: include/conversation.php:1203
 msgid "permissions"
 msgstr "Zugriffsrechte"
 
-#: include/conversation.php:1227
+#: include/conversation.php:1226
 msgid "Post to Groups"
 msgstr "Poste an Gruppe"
 
-#: include/conversation.php:1228
+#: include/conversation.php:1227
 msgid "Post to Contacts"
 msgstr "Poste an Kontakte"
 
-#: include/conversation.php:1229
+#: include/conversation.php:1228
 msgid "Private post"
 msgstr "Privater Beitrag"
 
-#: include/conversation.php:1386
+#: include/conversation.php:1385
 msgid "View all"
 msgstr "Zeige alle"
 
-#: include/conversation.php:1408
+#: include/conversation.php:1407
 msgid "Like"
 msgid_plural "Likes"
 msgstr[0] "mag ich"
 msgstr[1] "Mag ich"
 
-#: include/conversation.php:1411
+#: include/conversation.php:1410
 msgid "Dislike"
 msgid_plural "Dislikes"
 msgstr[0] "mag ich nicht"
 msgstr[1] "Mag ich nicht"
 
-#: include/conversation.php:1417
+#: include/conversation.php:1416
 msgid "Not Attending"
 msgid_plural "Not Attending"
 msgstr[0] "Nicht teilnehmend "
 msgstr[1] "Nicht teilnehmend"
 
-#: include/conversation.php:1420 include/profile_selectors.php:6
+#: include/conversation.php:1419 include/profile_selectors.php:6
 msgid "Undecided"
 msgid_plural "Undecided"
 msgstr[0] "Unentschieden"
@@ -7322,66 +7354,58 @@ msgstr "entspannt"
 msgid "surprised"
 msgstr "überrascht"
 
-#: include/text.php:1497
+#: include/text.php:1504
 msgid "bytes"
 msgstr "Byte"
 
-#: include/text.php:1529 include/text.php:1541
+#: include/text.php:1536 include/text.php:1548
 msgid "Click to open/close"
 msgstr "Zum öffnen/schließen klicken"
 
-#: include/text.php:1715
+#: include/text.php:1722
 msgid "View on separate page"
 msgstr "Auf separater Seite ansehen"
 
-#: include/text.php:1716
+#: include/text.php:1723
 msgid "view on separate page"
 msgstr "auf separater Seite ansehen"
 
-#: include/text.php:1995
+#: include/text.php:2002
 msgid "activity"
 msgstr "Aktivität"
 
-#: include/text.php:1998
+#: include/text.php:2005
 msgid "post"
 msgstr "Beitrag"
 
-#: include/text.php:2166
+#: include/text.php:2173
 msgid "Item filed"
 msgstr "Beitrag abgelegt"
 
-#: include/bbcode.php:483 include/bbcode.php:1143 include/bbcode.php:1144
+#: include/bbcode.php:482 include/bbcode.php:1157 include/bbcode.php:1158
 msgid "Image/photo"
 msgstr "Bild/Foto"
 
-#: include/bbcode.php:581
+#: include/bbcode.php:595
 #, php-format
 msgid "<a href=\"%1$s\" target=\"_blank\">%2$s</a> %3$s"
 msgstr "<a href=\"%1$s\" target=\"_blank\">%2$s</a> %3$s"
 
-#: include/bbcode.php:615
+#: include/bbcode.php:629
 #, php-format
 msgid ""
 "<span><a href=\"%s\" target=\"_blank\">%s</a> wrote the following <a "
 "href=\"%s\" target=\"_blank\">post</a>"
 msgstr "<span><a href=\"%s\" target=\"_blank\">%s</a> schrieb den folgenden <a href=\"%s\" target=\"_blank\">Beitrag</a>"
 
-#: include/bbcode.php:1103 include/bbcode.php:1123
+#: include/bbcode.php:1117 include/bbcode.php:1137
 msgid "$1 wrote:"
 msgstr "$1 hat geschrieben:"
 
-#: include/bbcode.php:1152 include/bbcode.php:1153
+#: include/bbcode.php:1166 include/bbcode.php:1167
 msgid "Encrypted content"
 msgstr "Verschlüsselter Inhalt"
 
-#: include/notifier.php:843 include/delivery.php:459
-msgid "(no subject)"
-msgstr "(kein Betreff)"
-
-#: include/notifier.php:853 include/delivery.php:470 include/enotify.php:37
-msgid "noreply"
-msgstr "noreply"
-
 #: include/dba_pdo.php:72 include/dba.php:55
 #, php-format
 msgid "Cannot locate DNS info for database server '%s'"
@@ -7427,6 +7451,10 @@ msgstr "OStatus"
 msgid "RSS/Atom"
 msgstr "RSS/Atom"
 
+#: include/contact_selectors.php:81
+msgid "Facebook"
+msgstr "Facebook"
+
 #: include/contact_selectors.php:82
 msgid "Zot!"
 msgstr "Zott"
@@ -7471,7 +7499,7 @@ msgstr "App.net"
 msgid "Redmatrix"
 msgstr "Redmatrix"
 
-#: include/Scrape.php:610
+#: include/Scrape.php:624
 msgid " on Last.fm"
 msgstr " bei Last.fm"
 
@@ -7647,46 +7675,21 @@ msgstr "Navigation"
 msgid "Site map"
 msgstr "Sitemap"
 
-#: include/api.php:345 include/api.php:356 include/api.php:465
-#: include/api.php:1184 include/api.php:1186
-msgid "User not found."
-msgstr "Nutzer nicht gefunden."
-
-#: include/api.php:832
+#: include/api.php:878
 #, php-format
 msgid "Daily posting limit of %d posts reached. The post was rejected."
 msgstr "Das tägliche Nachrichtenlimit von %d Nachrichten wurde erreicht. Die Nachtricht wurde verworfen."
 
-#: include/api.php:851
+#: include/api.php:897
 #, php-format
 msgid "Weekly posting limit of %d posts reached. The post was rejected."
 msgstr "Das wöchentliche Nachrichtenlimit von %d Nachrichten wurde erreicht. Die Nachtricht wurde verworfen."
 
-#: include/api.php:870
+#: include/api.php:916
 #, php-format
 msgid "Monthly posting limit of %d posts reached. The post was rejected."
 msgstr "Das monatliche Nachrichtenlimit von %d Nachrichten wurde erreicht. Die Nachtricht wurde verworfen."
 
-#: include/api.php:1393
-msgid "There is no status with this id."
-msgstr "Es gibt keinen Status mit dieser ID."
-
-#: include/api.php:1467
-msgid "There is no conversation with this id."
-msgstr "Es existiert keine Unterhaltung mit dieser ID."
-
-#: include/api.php:1746
-msgid "Invalid item."
-msgstr "Ungültiges Objekt"
-
-#: include/api.php:1756
-msgid "Invalid action. "
-msgstr "Ungültige Aktion"
-
-#: include/api.php:1764
-msgid "DB error"
-msgstr "DB Error"
-
 #: include/user.php:48
 msgid "An invitation is required."
 msgstr "Du benötigst eine Einladung."
@@ -7749,8 +7752,7 @@ msgstr "FATALER FEHLER: Sicherheitsschlüssel konnten nicht erzeugt werden."
 msgid "An error occurred during registration. Please try again."
 msgstr "Während der Anmeldung ist ein Fehler aufgetreten. Bitte versuche es noch einmal."
 
-#: include/user.php:256 view/theme/clean/config.php:56
-#: view/theme/duepuntozero/config.php:44
+#: include/user.php:256 view/theme/duepuntozero/config.php:44
 msgid "default"
 msgstr "Standard"
 
@@ -7805,15 +7807,23 @@ msgstr "\nDie Anmelde-Details sind die folgenden:\n\tAdresse der Seite:\t%3$s\n\
 msgid "Sharing notification from Diaspora network"
 msgstr "Freigabe-Benachrichtigung von Diaspora"
 
-#: include/diaspora.php:2607
+#: include/diaspora.php:2625
 msgid "Attachments:"
 msgstr "Anhänge:"
 
-#: include/items.php:4900
+#: include/delivery.php:533
+msgid "(no subject)"
+msgstr "(kein Betreff)"
+
+#: include/delivery.php:544 include/enotify.php:37
+msgid "noreply"
+msgstr "noreply"
+
+#: include/items.php:4926
 msgid "Do you really want to delete this item?"
 msgstr "Möchtest Du wirklich dieses Item löschen?"
 
-#: include/items.php:5175
+#: include/items.php:5201
 msgid "Archives"
 msgstr "Archiv"
 
@@ -8329,11 +8339,11 @@ msgstr "Kompletter Name:\t%1$s\\nURL der Seite:\t%2$s\\nLogin Name:\t%3$s (%4$s)
 msgid "Please visit %s to approve or reject the request."
 msgstr "Bitte besuche %s um die Anfrage zu bearbeiten."
 
-#: include/oembed.php:220
+#: include/oembed.php:226
 msgid "Embedded content"
 msgstr "Eingebetteter Inhalt"
 
-#: include/oembed.php:229
+#: include/oembed.php:235
 msgid "Embedding disabled"
 msgstr "Einbettungen deaktiviert"
 
@@ -8391,7 +8401,6 @@ msgid "Set theme width"
 msgstr "Theme Breite festlegen"
 
 #: view/theme/cleanzero/config.php:86 view/theme/quattro/config.php:68
-#: view/theme/clean/config.php:88
 msgid "Color scheme"
 msgstr "Farbschema"
 
@@ -8513,56 +8522,6 @@ msgstr "Zoomfaktor der Earth Layer"
 msgid "Show/hide boxes at right-hand column:"
 msgstr "Rahmen auf der rechten Seite anzeigen/verbergen"
 
-#: view/theme/clean/config.php:57
-msgid "Midnight"
-msgstr "Mitternacht"
-
-#: view/theme/clean/config.php:58
-msgid "Zenburn"
-msgstr "Zenburn"
-
-#: view/theme/clean/config.php:59
-msgid "Bootstrap"
-msgstr "Bootstrap"
-
-#: view/theme/clean/config.php:60
-msgid "Shades of Pink"
-msgstr "Shades of Pink"
-
-#: view/theme/clean/config.php:61
-msgid "Lime and Orange"
-msgstr "Lime and Orange"
-
-#: view/theme/clean/config.php:62
-msgid "GeoCities Retro"
-msgstr "GeoCities Retro"
-
-#: view/theme/clean/config.php:86
-msgid "Background Image"
-msgstr "Hintergrundbild"
-
-#: view/theme/clean/config.php:86
-msgid ""
-"The URL to a picture (e.g. from your photo album) that should be used as "
-"background image."
-msgstr "Die URL eines Bildes (z.B. aus deinem Fotoalbum), das als Hintergrundbild verwendet werden soll."
-
-#: view/theme/clean/config.php:87
-msgid "Background Color"
-msgstr "Hintergrundfarbe"
-
-#: view/theme/clean/config.php:87
-msgid "HEX value for the background color. Don't include the #"
-msgstr "HEX Wert der Hintergrundfarbe. Gib die # nicht mit an."
-
-#: view/theme/clean/config.php:89
-msgid "font size"
-msgstr "Schriftgröße"
-
-#: view/theme/clean/config.php:89
-msgid "base font size for your interface"
-msgstr "Basis-Schriftgröße für dein Interface."
-
 #: view/theme/vier/config.php:64
 msgid "Comma separated list of helper forums"
 msgstr "Komma-Separierte Liste der Helfer-Foren"
index 0c616cee2d0ee473d2ae6ad94609bbf47f8e1ef3..1a1fc07830cc47f6202f8a89f8be811922cac785 100644 (file)
@@ -9,7 +9,7 @@ $a->strings["Network:"] = "Netzwerk";
 $a->strings["Forum"] = "Forum";
 $a->strings["%d contact edited."] = array(
        0 => "%d Kontakt bearbeitet.",
-       1 => "%d Kontakte bearbeitet",
+       1 => "%d Kontakte bearbeitet.",
 );
 $a->strings["Could not access contact record."] = "Konnte nicht auf die Kontaktdaten zugreifen.";
 $a->strings["Could not locate selected profile."] = "Konnte das ausgewählte Profil nicht finden.";
@@ -142,9 +142,6 @@ $a->strings["Edit your <strong>default</strong> profile to your liking. Review t
 $a->strings["Profile Keywords"] = "Profil Schlüsselbegriffe";
 $a->strings["Set some public keywords for your default profile which describe your interests. We may be able to find other people with similar interests and suggest friendships."] = "Trage ein paar öffentliche Stichwörter in Dein Standardprofil ein, die Deine Interessen beschreiben. Eventuell sind wir in der Lage Leute zu finden, die Deine Interessen teilen und können Dir dann Kontakte vorschlagen.";
 $a->strings["Connecting"] = "Verbindungen knüpfen";
-$a->strings["Facebook"] = "Facebook";
-$a->strings["Authorise the Facebook Connector if you currently have a Facebook account and we will (optionally) import all your Facebook friends and conversations."] = "Richte die Verbindung zu Facebook ein, wenn Du im Augenblick ein Facebook-Konto hast und (optional) Deine Facebook-Freunde und -Unterhaltungen importieren willst.";
-$a->strings["<em>If</em> this is your own personal server, installing the Facebook addon may ease your transition to the free social web."] = "<em>Wenn</em> dies Dein privater Server ist, könnte die Installation des Facebook Connectors Deinen Umzug ins freie soziale Netz angenehmer gestalten.";
 $a->strings["Importing Emails"] = "Emails Importieren";
 $a->strings["Enter your email access information on your Connector Settings page if you wish to import and interact with friends or mailing lists from your email INBOX"] = "Gib Deine E-Mail-Zugangsinformationen auf der Connector-Einstellungsseite ein, falls Du E-Mails aus Deinem Posteingang importieren und mit Freunden und Mailinglisten interagieren willst.";
 $a->strings["Go to Your Contacts Page"] = "Gehe zu deiner Kontakt-Seite";
@@ -290,12 +287,6 @@ $a->strings["Forgot your Password?"] = "Hast Du Dein Passwort vergessen?";
 $a->strings["Enter your email address and submit to have your password reset. Then check your email for further instructions."] = "Gib Deine E-Mail-Adresse an und fordere ein neues Passwort an. Es werden Dir dann weitere Informationen per Mail zugesendet.";
 $a->strings["Nickname or Email: "] = "Spitzname oder E-Mail:";
 $a->strings["Reset"] = "Zurücksetzen";
-$a->strings["event"] = "Event";
-$a->strings["%1\$s likes %2\$s's %3\$s"] = "%1\$s mag %2\$ss %3\$s";
-$a->strings["%1\$s doesn't like %2\$s's %3\$s"] = "%1\$s mag %2\$ss %3\$s nicht";
-$a->strings["%1\$s is attending %2\$s's %3\$s"] = "%1\$s nimmt an %2\$ss %3\$s teil.";
-$a->strings["%1\$s is not attending %2\$s's %3\$s"] = "%1\$s nimmt nicht an %2\$ss %3\$s teil.";
-$a->strings["%1\$s may attend %2\$s's %3\$s"] = "%1\$s nimmt eventuell an %2\$ss %3\$s teil.";
 $a->strings["{0} wants to be your friend"] = "{0} möchte mit Dir in Kontakt treten";
 $a->strings["{0} sent you a message"] = "{0} schickte Dir eine Nachricht";
 $a->strings["{0} requested registration"] = "{0} möchte sich registrieren";
@@ -425,16 +416,22 @@ $a->strings["Site"] = "Seite";
 $a->strings["Users"] = "Nutzer";
 $a->strings["Plugins"] = "Plugins";
 $a->strings["Themes"] = "Themen";
+$a->strings["Additional features"] = "Zusätzliche Features";
 $a->strings["DB updates"] = "DB Updates";
 $a->strings["Inspect Queue"] = "Warteschlange Inspizieren";
+$a->strings["Federation Statistics"] = "Federation Statistik";
 $a->strings["Logs"] = "Protokolle";
+$a->strings["View Logs"] = "Protokolle anzeigen";
 $a->strings["probe address"] = "Adresse untersuchen";
 $a->strings["check webfinger"] = "Webfinger überprüfen";
 $a->strings["Admin"] = "Administration";
 $a->strings["Plugin Features"] = "Plugin Features";
 $a->strings["diagnostics"] = "Diagnose";
 $a->strings["User registrations waiting for confirmation"] = "Nutzeranmeldungen die auf Bestätigung warten";
+$a->strings["This page offers you some numbers to the known part of the federated social network your Friendica node is part of. These numbers are not complete but only reflect the part of the network your node is aware of."] = "Diese Seite präsentiert einige Zahlen zu dem bekannten Teil des föderalen sozialen Netzwerks, von dem deine Friendica Installation ein Teil ist. Diese Zahlen sind nicht absolut und reflektieren nur den Teil des Netzwerks, den dein Knoten kennt.";
+$a->strings["The <em>Auto Discovered Contact Directory</em> feature is not enabled, it will improve the data displayed here."] = "Die Funktion um <em>Automatisch ein Kontaktverzeichnis erstellen</em> ist nicht aktiv. Es wird die hier angezeigten Daten verbessern.";
 $a->strings["Administration"] = "Administration";
+$a->strings["Currently this node is aware of %d nodes from the following platforms:"] = "Momentan kennt dieser Knoten %d andere Knoten der folgenden Plattformen:";
 $a->strings["ID"] = "ID";
 $a->strings["Recipient Name"] = "Empfänger Name";
 $a->strings["Recipient Profile"] = "Empfänger Profil";
@@ -585,7 +582,7 @@ $a->strings["Maximum Load Average (Frontend)"] = "Maximum Load Average (Frontend
 $a->strings["Maximum system load before the frontend quits service - default 50."] = "Maximale Systemlast bevor Vordergrundprozesse pausiert werden - Standard 50.";
 $a->strings["Maximum table size for optimization"] = "Maximale Tabellengröße zur Optimierung";
 $a->strings["Maximum table size (in MB) for the automatic optimization - default 100 MB. Enter -1 to disable it."] = "Maximale Tabellengröße (in MB) für die automatische Optimierung - Standard 100 MB. Gib -1 für Deaktivierung ein.";
-$a->strings["Minimum level of fragmentation"] = "Minimales Fragmentationsgrad";
+$a->strings["Minimum level of fragmentation"] = "Minimaler Fragmentationsgrad";
 $a->strings["Minimum fragmenation level to start the automatic optimization - default value is 30%."] = "Minimales Fragmentationsgrad von Datenbanktabellen um die automatische Optimierung einzuleiten - Standardwert ist 30%";
 $a->strings["Periodical check of global contacts"] = "Regelmäßig globale Kontakte überprüfen";
 $a->strings["If enabled, the global contacts are checked periodically for missing or outdated data and the vitality of the contacts and servers."] = "Wenn diese Option aktiviert ist, werden die globalen Kontakte regelmäßig auf fehlende oder veraltete Daten sowie auf Erreichbarkeit des Kontakts und des Servers überprüft.";
@@ -686,9 +683,11 @@ $a->strings["Toggle"] = "Umschalten";
 $a->strings["Author: "] = "Autor:";
 $a->strings["Maintainer: "] = "Betreuer:";
 $a->strings["Reload active plugins"] = "Aktive Plugins neu laden";
+$a->strings["There are currently no plugins available on your node. You can find the official plugin repository at %1\$s and might find other interesting plugins in the open plugin registry at %2\$s"] = "Es sind derzeit keine Plugins auf diesem Knoten verfügbar. Du findest das offizielle Plugin-Repository unter %1\$s und weitere eventuell interessante Plugins im offenen Plugins-Verzeichnis auf %2\$s.";
 $a->strings["No themes found."] = "Keine Themen gefunden.";
 $a->strings["Screenshot"] = "Bildschirmfoto";
 $a->strings["Reload active themes"] = "Aktives Theme neu laden";
+$a->strings["No themes found on the system. They should be paced in %1\$s"] = "Es wurden keine Themes auf dem System gefunden. Diese sollten in %1\$s patziert werden.";
 $a->strings["[Experimental]"] = "[Experimentell]";
 $a->strings["[Unsupported]"] = "[Nicht unterstützt]";
 $a->strings["Log settings updated."] = "Protokolleinstellungen aktualisiert.";
@@ -697,11 +696,12 @@ $a->strings["Enable Debugging"] = "Protokoll führen";
 $a->strings["Log file"] = "Protokolldatei";
 $a->strings["Must be writable by web server. Relative to your Friendica top-level directory."] = "Webserver muss Schreibrechte besitzen. Abhängig vom Friendica-Installationsverzeichnis.";
 $a->strings["Log level"] = "Protokoll-Level";
-$a->strings["Close"] = "Schließen";
-$a->strings["FTP Host"] = "FTP Host";
-$a->strings["FTP Path"] = "FTP Pfad";
-$a->strings["FTP User"] = "FTP Nutzername";
-$a->strings["FTP Password"] = "FTP Passwort";
+$a->strings["PHP logging"] = "PHP Protokollieren";
+$a->strings["To enable logging of PHP errors and warnings you can add the following to the .htconfig.php file of your installation. The filename set in the 'error_log' line is relative to the friendica top-level directory and must be writeable by the web server. The option '1' for 'log_errors' and 'display_errors' is to enable these options, set to '0' to disable them."] = "Um PHP Warnungen und Fehler zu protokollieren, kannst du die folgenden Zeilen zur .htconfig.php Datei deiner Installation hinzufügen. Den Dateinamen der Log-Datei legst du in der Zeile mit dem 'error_log' fest,  Er ist relativ zum Friendica-Stammverzeichnis und muss schreibbar durch den Webserver sein. Eine \"1\" als Option für die Punkte 'log_errors' und 'display_errors' aktiviert die Funktionen zum Protokollieren bzw. Anzeigen der Fehler, eine \"0\" deaktiviert sie.";
+$a->strings["Off"] = "Aus";
+$a->strings["On"] = "An";
+$a->strings["Lock feature %s"] = "Feature festlegen: %s";
+$a->strings["Manage Additional Features"] = "Zusätzliche Features Verwalten";
 $a->strings["Search Results For: %s"] = "Suchergebnisse für: %s";
 $a->strings["Remove term"] = "Begriff entfernen";
 $a->strings["Saved Searches"] = "Gespeicherte Suchen";
@@ -923,7 +923,6 @@ $a->strings["Not available."] = "Nicht verfügbar.";
 $a->strings["Community"] = "Gemeinschaft";
 $a->strings["No results."] = "Keine Ergebnisse.";
 $a->strings["everybody"] = "jeder";
-$a->strings["Additional features"] = "Zusätzliche Features";
 $a->strings["Display"] = "Anzeige";
 $a->strings["Social Networks"] = "Soziale Netzwerke";
 $a->strings["Delegations"] = "Delegationen";
@@ -960,8 +959,6 @@ $a->strings["No name"] = "Kein Name";
 $a->strings["Remove authorization"] = "Autorisierung entziehen";
 $a->strings["No Plugin settings configured"] = "Keine Plugin-Einstellungen konfiguriert";
 $a->strings["Plugin Settings"] = "Plugin-Einstellungen";
-$a->strings["Off"] = "Aus";
-$a->strings["On"] = "An";
 $a->strings["Additional Features"] = "Zusätzliche Features";
 $a->strings["General Social Media Settings"] = "Allgemeine Einstellungen zu Sozialen Medien";
 $a->strings["Disable intelligent shortening"] = "Intelligentes Link kürzen ausschalten";
@@ -1108,12 +1105,12 @@ $a->strings["Friends are advised to please try again in 24 hours."] = "Freunde s
 $a->strings["Invalid locator"] = "Ungültiger Locator";
 $a->strings["Invalid email address."] = "Ungültige E-Mail-Adresse.";
 $a->strings["This account has not been configured for email. Request failed."] = "Dieses Konto ist nicht für E-Mail konfiguriert. Anfrage fehlgeschlagen.";
-$a->strings["Unable to resolve your name at the provided location."] = "Konnte Deinen Namen an der angegebenen Stelle nicht finden.";
 $a->strings["You have already introduced yourself here."] = "Du hast Dich hier bereits vorgestellt.";
 $a->strings["Apparently you are already friends with %s."] = "Es scheint so, als ob Du bereits mit %s befreundet bist.";
 $a->strings["Invalid profile URL."] = "Ungültige Profil-URL.";
 $a->strings["Disallowed profile URL."] = "Nicht erlaubte Profil-URL.";
 $a->strings["Your introduction has been sent."] = "Deine Kontaktanfrage wurde gesendet.";
+$a->strings["Remote subscription can't be done for your network. Please subscribe directly on your system."] = "Entferntes abon­nie­ren kann für dein Netzwerk nicht durchgeführt werden. Bitte nutze direkt die Abonnieren-Funktion deines Systems.   ";
 $a->strings["Please login to confirm introduction."] = "Bitte melde Dich an, um die Kontaktanfrage zu bestätigen.";
 $a->strings["Incorrect identity currently logged in. Please login to <strong>this</strong> profile."] = "Momentan bist Du mit einer anderen Identität angemeldet. Bitte melde Dich mit <strong>diesem</strong> Profil an.";
 $a->strings["Confirm"] = "Bestätigen";
@@ -1565,11 +1562,18 @@ $a->strings["Forums:"] = "Foren:";
 $a->strings["Videos"] = "Videos";
 $a->strings["Events and Calendar"] = "Ereignisse und Kalender";
 $a->strings["Only You Can See This"] = "Nur Du kannst das sehen";
+$a->strings["event"] = "Event";
+$a->strings["%1\$s likes %2\$s's %3\$s"] = "%1\$s mag %2\$ss %3\$s";
+$a->strings["%1\$s doesn't like %2\$s's %3\$s"] = "%1\$s mag %2\$ss %3\$s nicht";
+$a->strings["%1\$s is attending %2\$s's %3\$s"] = "%1\$s nimmt an %2\$ss %3\$s teil.";
+$a->strings["%1\$s is not attending %2\$s's %3\$s"] = "%1\$s nimmt nicht an %2\$ss %3\$s teil.";
+$a->strings["%1\$s may attend %2\$s's %3\$s"] = "%1\$s nimmt eventuell an %2\$ss %3\$s teil.";
 $a->strings["Post to Email"] = "An E-Mail senden";
 $a->strings["Connectors disabled, since \"%s\" is enabled."] = "Konnektoren sind nicht verfügbar, da \"%s\" aktiv ist.";
 $a->strings["Visible to everybody"] = "Für jeden sichtbar";
 $a->strings["show"] = "zeigen";
 $a->strings["don't show"] = "nicht zeigen";
+$a->strings["Close"] = "Schließen";
 $a->strings["[no subject]"] = "[kein Betreff]";
 $a->strings["stopped following"] = "wird nicht mehr gefolgt";
 $a->strings["View Status"] = "Pinnwand anschauen";
@@ -1699,8 +1703,6 @@ $a->strings["<a href=\"%1\$s\" target=\"_blank\">%2\$s</a> %3\$s"] = "<a href=\"
 $a->strings["<span><a href=\"%s\" target=\"_blank\">%s</a> wrote the following <a href=\"%s\" target=\"_blank\">post</a>"] = "<span><a href=\"%s\" target=\"_blank\">%s</a> schrieb den folgenden <a href=\"%s\" target=\"_blank\">Beitrag</a>";
 $a->strings["$1 wrote:"] = "$1 hat geschrieben:";
 $a->strings["Encrypted content"] = "Verschlüsselter Inhalt";
-$a->strings["(no subject)"] = "(kein Betreff)";
-$a->strings["noreply"] = "noreply";
 $a->strings["Cannot locate DNS info for database server '%s'"] = "Kann die DNS Informationen für den Datenbankserver '%s' nicht ermitteln.";
 $a->strings["Unknown | Not categorised"] = "Unbekannt | Nicht kategorisiert";
 $a->strings["Block immediately"] = "Sofort blockieren";
@@ -1712,6 +1714,7 @@ $a->strings["Weekly"] = "Wöchentlich";
 $a->strings["Monthly"] = "Monatlich";
 $a->strings["OStatus"] = "OStatus";
 $a->strings["RSS/Atom"] = "RSS/Atom";
+$a->strings["Facebook"] = "Facebook";
 $a->strings["Zot!"] = "Zott";
 $a->strings["LinkedIn"] = "LinkedIn";
 $a->strings["XMPP/IM"] = "XMPP/Chat";
@@ -1767,15 +1770,9 @@ $a->strings["Manage/edit friends and contacts"] = "Freunde und Kontakte verwalte
 $a->strings["Site setup and configuration"] = "Einstellungen der Seite und Konfiguration";
 $a->strings["Navigation"] = "Navigation";
 $a->strings["Site map"] = "Sitemap";
-$a->strings["User not found."] = "Nutzer nicht gefunden.";
 $a->strings["Daily posting limit of %d posts reached. The post was rejected."] = "Das tägliche Nachrichtenlimit von %d Nachrichten wurde erreicht. Die Nachtricht wurde verworfen.";
 $a->strings["Weekly posting limit of %d posts reached. The post was rejected."] = "Das wöchentliche Nachrichtenlimit von %d Nachrichten wurde erreicht. Die Nachtricht wurde verworfen.";
 $a->strings["Monthly posting limit of %d posts reached. The post was rejected."] = "Das monatliche Nachrichtenlimit von %d Nachrichten wurde erreicht. Die Nachtricht wurde verworfen.";
-$a->strings["There is no status with this id."] = "Es gibt keinen Status mit dieser ID.";
-$a->strings["There is no conversation with this id."] = "Es existiert keine Unterhaltung mit dieser ID.";
-$a->strings["Invalid item."] = "Ungültiges Objekt";
-$a->strings["Invalid action. "] = "Ungültige Aktion";
-$a->strings["DB error"] = "DB Error";
 $a->strings["An invitation is required."] = "Du benötigst eine Einladung.";
 $a->strings["Invitation could not be verified."] = "Die Einladung konnte nicht überprüft werden.";
 $a->strings["Invalid OpenID url"] = "Ungültige OpenID URL";
@@ -1798,6 +1795,8 @@ $a->strings["\n\t\tDear %1\$s,\n\t\t\tThank you for registering at %2\$s. Your a
 $a->strings["\n\t\tThe login details are as follows:\n\t\t\tSite Location:\t%3\$s\n\t\t\tLogin Name:\t%1\$s\n\t\t\tPassword:\t%5\$s\n\n\t\tYou may change your password from your account \"Settings\" page after logging\n\t\tin.\n\n\t\tPlease take a few moments to review the other account settings on that page.\n\n\t\tYou may also wish to add some basic information to your default profile\n\t\t(on the \"Profiles\" page) so that other people can easily find you.\n\n\t\tWe recommend setting your full name, adding a profile photo,\n\t\tadding some profile \"keywords\" (very useful in making new friends) - and\n\t\tperhaps what country you live in; if you do not wish to be more specific\n\t\tthan that.\n\n\t\tWe fully respect your right to privacy, and none of these items are necessary.\n\t\tIf you are new and do not know anybody here, they may help\n\t\tyou to make some new and interesting friends.\n\n\n\t\tThank you and welcome to %2\$s."] = "\nDie Anmelde-Details sind die folgenden:\n\tAdresse der Seite:\t%3\$s\n\tBenutzernamename:\t%1\$s\n\tPasswort:\t%5\$s\n\nDu kannst Dein Passwort unter \"Einstellungen\" ändern, sobald Du Dich\nangemeldet hast.\n\nBitte nimm Dir ein paar Minuten um die anderen Einstellungen auf dieser\nSeite zu kontrollieren.\n\nEventuell magst Du ja auch einige Informationen über Dich in Deinem\nProfil veröffentlichen, damit andere Leute Dich einfacher finden können.\nBearbeite hierfür einfach Dein Standard-Profil (über die Profil-Seite).\n\nWir empfehlen Dir, Deinen kompletten Namen anzugeben und ein zu Dir\npassendes Profilbild zu wählen, damit Dich alte Bekannte wieder finden.\nAußerdem ist es nützlich, wenn Du auf Deinem Profil Schlüsselwörter\nangibst. Das erleichtert es, Leute zu finden, die Deine Interessen teilen.\n\nWir respektieren Deine Privatsphäre - keine dieser Angaben ist nötig.\nWenn Du neu im Netzwerk bist und noch niemanden kennst, dann können sie\nallerdings dabei helfen, neue und interessante Kontakte zu knüpfen.\n\nDanke für Deine Aufmerksamkeit und willkommen auf %2\$s.";
 $a->strings["Sharing notification from Diaspora network"] = "Freigabe-Benachrichtigung von Diaspora";
 $a->strings["Attachments:"] = "Anhänge:";
+$a->strings["(no subject)"] = "(kein Betreff)";
+$a->strings["noreply"] = "noreply";
 $a->strings["Do you really want to delete this item?"] = "Möchtest Du wirklich dieses Item löschen?";
 $a->strings["Archives"] = "Archiv";
 $a->strings["Male"] = "Männlich";
@@ -1958,18 +1957,6 @@ $a->strings["Your personal photos"] = "Deine privaten Fotos";
 $a->strings["Local Directory"] = "Lokales Verzeichnis";
 $a->strings["Set zoomfactor for Earth Layers"] = "Zoomfaktor der Earth Layer";
 $a->strings["Show/hide boxes at right-hand column:"] = "Rahmen auf der rechten Seite anzeigen/verbergen";
-$a->strings["Midnight"] = "Mitternacht";
-$a->strings["Zenburn"] = "Zenburn";
-$a->strings["Bootstrap"] = "Bootstrap";
-$a->strings["Shades of Pink"] = "Shades of Pink";
-$a->strings["Lime and Orange"] = "Lime and Orange";
-$a->strings["GeoCities Retro"] = "GeoCities Retro";
-$a->strings["Background Image"] = "Hintergrundbild";
-$a->strings["The URL to a picture (e.g. from your photo album) that should be used as background image."] = "Die URL eines Bildes (z.B. aus deinem Fotoalbum), das als Hintergrundbild verwendet werden soll.";
-$a->strings["Background Color"] = "Hintergrundfarbe";
-$a->strings["HEX value for the background color. Don't include the #"] = "HEX Wert der Hintergrundfarbe. Gib die # nicht mit an.";
-$a->strings["font size"] = "Schriftgröße";
-$a->strings["base font size for your interface"] = "Basis-Schriftgröße für dein Interface.";
 $a->strings["Comma separated list of helper forums"] = "Komma-Separierte Liste der Helfer-Foren";
 $a->strings["Set style"] = "Stil auswählen";
 $a->strings["Quick Start"] = "Schnell-Start";
index 78ca97ac94c0c2a4cb050c09ab5094a3962b6c05..121d5212c6291995f789ab7ad4b0abcd458fbd12 100644 (file)
@@ -8,11 +8,12 @@
 <body>\r
        <?php if(x($page,'nav')) echo $page['nav']; ?>\r
        <aside><?php if(x($page,'aside')) echo $page['aside']; ?></aside>\r
-       <section><?php if(x($page,'content')) echo $page['content']; ?>\r
+       <section>\r
+               <?php if(x($page,'content')) echo $page['content']; ?>\r
+               <div id="pause"></div> <!-- The pause/resume Ajax indicator -->\r
                <div id="page-footer"></div>\r
        </section>\r
        <right_aside><?php if(x($page,'right_aside')) echo $page['right_aside']; ?></right_aside>\r
        <footer><?php if(x($page,'footer')) echo $page['footer']; ?></footer>\r
 </body>\r
 </html>\r
-\r
index b34cac79809d53a2ba8958d604dcbbf50d257c44..5f4494dde7b4fbfe5e1fd7293b75c04b4bdf070d 100644 (file)
     content: "Z";
 }
 .shashape.hash:before{
-    content: "/";
+    content: "#";
 }
 .shashape.tag:before{
     content: "=";
index 05940508ca49c5609a68d0f8071c646b18112daf..8646bf8e448c532ee66165f6f7e77a831c847faf 100644 (file)
@@ -93,7 +93,7 @@ span.connector {
 
 /* Shared Messages */
 .shared_header {
-  height: 32px;
+  min-height: 32px;
   color: #999;
   border-top: 1px solid #D2D2D2;
   padding-top: 5px;
@@ -118,10 +118,12 @@ span.connector {
   -moz-border-radius: 4px;
   border-radius: 4px;
   float: left;
+  margin-right: 9px;
 }
 
 .shared_header span {
-  margin-left: 9px;
+  display: table-cell;
+  float: none;
 }
 
 blockquote.shared_content {
@@ -274,13 +276,37 @@ a {
 #poke-recip-label, #poke-action-label, #prvmail-message-label {
   margin: 10px 0 10px;
 }
+.version-match {
+    font-weight: bold;
+    color: #00a700;
+}
+.federation-graph {
+    width: 400px; 
+    height: 400px; 
+    float: right; 
+    margin: 20px;
+}
+.federation-network-graph {
+    width: 240px; 
+    height: 240px; 
+    float: left; 
+    margin: 20px;
+}
+ul.federation-stats,
 ul.credits {
   list-style: none;
 }
+ul.federation-stats li,
 ul.credits li {
   float: left;
   width: 240px;
 }
+table#federation-stats {
+    width: 100%;
+}
+td.federation-data {
+    border-bottom: 1px solid #000;
+}
 
 .contact-entry-photo img {
   max-width: 80px;
@@ -321,3 +347,7 @@ ul.credits li {
 .p-addr {
        clear: both;    
 }
+
+#live-community {
+       clear: both;
+}
index 5f0129fb0a453820c5d923c67f319a3703b9106e..b2b88bc72f69be6092e2d4aa327a93b0f59f154e 100644 (file)
@@ -9,15 +9,15 @@
 # fabrixxm <fabrix.xm@gmail.com>, 2011-2012
 # Francesco Apruzzese <cescoap@gmail.com>, 2012-2013
 # ufic <marco@carnazzo.it>, 2012
-# tuscanhobbit <pynolo@tarine.net>, 2012
-# Sandro Santilli <strk@keybit.net>, 2015
+# Paolo Wave <pynolo@tarine.net>, 2012
+# Sandro Santilli <strk@keybit.net>, 2015-2016
 msgid ""
 msgstr ""
 "Project-Id-Version: friendica\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-12-14 07:48+0100\n"
-"PO-Revision-Date: 2015-12-14 13:05+0000\n"
-"Last-Translator: fabrixxm <fabrix.xm@gmail.com>\n"
+"POT-Creation-Date: 2016-01-24 06:49+0100\n"
+"PO-Revision-Date: 2016-01-30 08:43+0000\n"
+"Last-Translator: Sandro Santilli <strk@keybit.net>\n"
 "Language-Team: Italian (http://www.transifex.com/Friendica/friendica/language/it/)\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
@@ -25,26 +25,26 @@ msgstr ""
 "Language: it\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#: mod/contacts.php:50 include/identity.php:380
+#: mod/contacts.php:50 include/identity.php:395
 msgid "Network:"
 msgstr "Rete:"
 
-#: mod/contacts.php:51 mod/contacts.php:986 mod/videos.php:37
-#: mod/viewcontacts.php:105 mod/dirfind.php:208 mod/network.php:596
-#: mod/allfriends.php:72 mod/match.php:82 mod/directory.php:172
-#: mod/common.php:124 mod/suggest.php:95 mod/photos.php:41
-#: include/identity.php:295
+#: mod/contacts.php:51 mod/contacts.php:961 mod/videos.php:37
+#: mod/viewcontacts.php:105 mod/dirfind.php:214 mod/network.php:598
+#: mod/allfriends.php:77 mod/match.php:82 mod/directory.php:172
+#: mod/common.php:123 mod/suggest.php:95 mod/photos.php:41
+#: include/identity.php:298
 msgid "Forum"
 msgstr "Forum"
 
 #: mod/contacts.php:128
 #, php-format
 msgid "%d contact edited."
-msgid_plural "%d contacts edited"
-msgstr[0] "%d contatto modificato"
-msgstr[1] "%d contatti modificati"
+msgid_plural "%d contacts edited."
+msgstr[0] ""
+msgstr[1] ""
 
-#: mod/contacts.php:159 mod/contacts.php:382
+#: mod/contacts.php:159 mod/contacts.php:383
 msgid "Could not access contact record."
 msgstr "Non è possibile accedere al contatto."
 
@@ -56,15 +56,15 @@ msgstr "Non riesco a trovare il profilo selezionato."
 msgid "Contact updated."
 msgstr "Contatto aggiornato."
 
-#: mod/contacts.php:208 mod/dfrn_request.php:578
+#: mod/contacts.php:208 mod/dfrn_request.php:575
 msgid "Failed to update contact record."
 msgstr "Errore nell'aggiornamento del contatto."
 
-#: mod/contacts.php:364 mod/manage.php:96 mod/display.php:496
+#: mod/contacts.php:365 mod/manage.php:96 mod/display.php:509
 #: mod/profile_photo.php:19 mod/profile_photo.php:175
 #: mod/profile_photo.php:186 mod/profile_photo.php:199
-#: mod/ostatus_subscribe.php:9 mod/follow.php:10 mod/follow.php:72
-#: mod/follow.php:137 mod/item.php:169 mod/item.php:185 mod/group.php:19
+#: mod/ostatus_subscribe.php:9 mod/follow.php:11 mod/follow.php:73
+#: mod/follow.php:155 mod/item.php:180 mod/item.php:192 mod/group.php:19
 #: mod/dfrn_confirm.php:55 mod/fsuggest.php:78 mod/wall_upload.php:77
 #: mod/wall_upload.php:80 mod/viewcontacts.php:40 mod/notifications.php:69
 #: mod/message.php:45 mod/message.php:181 mod/crepair.php:117
@@ -72,129 +72,129 @@ msgstr "Errore nell'aggiornamento del contatto."
 #: mod/allfriends.php:12 mod/events.php:165 mod/wallmessage.php:9
 #: mod/wallmessage.php:33 mod/wallmessage.php:79 mod/wallmessage.php:103
 #: mod/wall_attach.php:67 mod/wall_attach.php:70 mod/settings.php:20
-#: mod/settings.php:116 mod/settings.php:637 mod/register.php:42
+#: mod/settings.php:126 mod/settings.php:646 mod/register.php:42
 #: mod/delegate.php:12 mod/common.php:18 mod/mood.php:114 mod/suggest.php:58
 #: mod/profiles.php:165 mod/profiles.php:615 mod/editpost.php:10
 #: mod/api.php:26 mod/api.php:31 mod/notes.php:22 mod/poke.php:149
 #: mod/repair_ostatus.php:9 mod/invite.php:15 mod/invite.php:101
 #: mod/photos.php:171 mod/photos.php:1105 mod/regmod.php:110
-#: mod/uimport.php:23 mod/attach.php:33 include/items.php:5067 index.php:382
+#: mod/uimport.php:23 mod/attach.php:33 include/items.php:5096 index.php:383
 msgid "Permission denied."
 msgstr "Permesso negato."
 
-#: mod/contacts.php:403
+#: mod/contacts.php:404
 msgid "Contact has been blocked"
 msgstr "Il contatto è stato bloccato"
 
-#: mod/contacts.php:403
+#: mod/contacts.php:404
 msgid "Contact has been unblocked"
 msgstr "Il contatto è stato sbloccato"
 
-#: mod/contacts.php:414
+#: mod/contacts.php:415
 msgid "Contact has been ignored"
 msgstr "Il contatto è ignorato"
 
-#: mod/contacts.php:414
+#: mod/contacts.php:415
 msgid "Contact has been unignored"
 msgstr "Il contatto non è più ignorato"
 
-#: mod/contacts.php:426
+#: mod/contacts.php:427
 msgid "Contact has been archived"
 msgstr "Il contatto è stato archiviato"
 
-#: mod/contacts.php:426
+#: mod/contacts.php:427
 msgid "Contact has been unarchived"
 msgstr "Il contatto è stato dearchiviato"
 
-#: mod/contacts.php:453 mod/contacts.php:801
+#: mod/contacts.php:454 mod/contacts.php:802
 msgid "Do you really want to delete this contact?"
 msgstr "Vuoi veramente cancellare questo contatto?"
 
-#: mod/contacts.php:455 mod/follow.php:105 mod/message.php:216
-#: mod/settings.php:1094 mod/settings.php:1100 mod/settings.php:1108
-#: mod/settings.php:1112 mod/settings.php:1117 mod/settings.php:1123
-#: mod/settings.php:1129 mod/settings.php:1135 mod/settings.php:1161
-#: mod/settings.php:1162 mod/settings.php:1163 mod/settings.php:1164
-#: mod/settings.php:1165 mod/dfrn_request.php:850 mod/register.php:238
+#: mod/contacts.php:456 mod/follow.php:110 mod/message.php:216
+#: mod/settings.php:1103 mod/settings.php:1109 mod/settings.php:1117
+#: mod/settings.php:1121 mod/settings.php:1126 mod/settings.php:1132
+#: mod/settings.php:1138 mod/settings.php:1144 mod/settings.php:1170
+#: mod/settings.php:1171 mod/settings.php:1172 mod/settings.php:1173
+#: mod/settings.php:1174 mod/dfrn_request.php:857 mod/register.php:238
 #: mod/suggest.php:29 mod/profiles.php:658 mod/profiles.php:661
-#: mod/profiles.php:687 mod/api.php:105 include/items.php:4899
+#: mod/profiles.php:687 mod/api.php:105 include/items.php:4928
 msgid "Yes"
 msgstr "Si"
 
-#: mod/contacts.php:458 mod/tagrm.php:11 mod/tagrm.php:94 mod/follow.php:116
+#: mod/contacts.php:459 mod/tagrm.php:11 mod/tagrm.php:94 mod/follow.php:121
 #: mod/videos.php:131 mod/message.php:219 mod/fbrowser.php:93
-#: mod/fbrowser.php:128 mod/settings.php:651 mod/settings.php:677
-#: mod/dfrn_request.php:864 mod/suggest.php:32 mod/editpost.php:148
-#: mod/photos.php:247 mod/photos.php:336 include/conversation.php:1221
-#: include/items.php:4902
+#: mod/fbrowser.php:128 mod/settings.php:660 mod/settings.php:686
+#: mod/dfrn_request.php:871 mod/suggest.php:32 mod/editpost.php:148
+#: mod/photos.php:247 mod/photos.php:336 include/conversation.php:1220
+#: include/items.php:4931
 msgid "Cancel"
 msgstr "Annulla"
 
-#: mod/contacts.php:470
+#: mod/contacts.php:471
 msgid "Contact has been removed."
 msgstr "Il contatto è stato rimosso."
 
-#: mod/contacts.php:511
+#: mod/contacts.php:512
 #, php-format
 msgid "You are mutual friends with %s"
 msgstr "Sei amico reciproco con %s"
 
-#: mod/contacts.php:515
+#: mod/contacts.php:516
 #, php-format
 msgid "You are sharing with %s"
 msgstr "Stai condividendo con %s"
 
-#: mod/contacts.php:520
+#: mod/contacts.php:521
 #, php-format
 msgid "%s is sharing with you"
 msgstr "%s sta condividendo con te"
 
-#: mod/contacts.php:540
+#: mod/contacts.php:541
 msgid "Private communications are not available for this contact."
 msgstr "Le comunicazioni private non sono disponibili per questo contatto."
 
-#: mod/contacts.php:543 mod/admin.php:645
+#: mod/contacts.php:544 mod/admin.php:822
 msgid "Never"
 msgstr "Mai"
 
-#: mod/contacts.php:547
+#: mod/contacts.php:548
 msgid "(Update was successful)"
 msgstr "(L'aggiornamento è stato completato)"
 
-#: mod/contacts.php:547
+#: mod/contacts.php:548
 msgid "(Update was not successful)"
 msgstr "(L'aggiornamento non è stato completato)"
 
-#: mod/contacts.php:549
+#: mod/contacts.php:550
 msgid "Suggest friends"
 msgstr "Suggerisci amici"
 
-#: mod/contacts.php:553
+#: mod/contacts.php:554
 #, php-format
 msgid "Network type: %s"
 msgstr "Tipo di rete: %s"
 
-#: mod/contacts.php:566
+#: mod/contacts.php:567
 msgid "Communications lost with this contact!"
 msgstr "Comunicazione con questo contatto persa!"
 
-#: mod/contacts.php:569
+#: mod/contacts.php:570
 msgid "Fetch further information for feeds"
 msgstr "Recupera maggiori infomazioni per i feed"
 
-#: mod/contacts.php:570 mod/admin.php:654
+#: mod/contacts.php:571 mod/admin.php:831
 msgid "Disabled"
 msgstr "Disabilitato"
 
-#: mod/contacts.php:570
+#: mod/contacts.php:571
 msgid "Fetch information"
 msgstr "Recupera informazioni"
 
-#: mod/contacts.php:570
+#: mod/contacts.php:571
 msgid "Fetch information and keywords"
 msgstr "Recupera informazioni e parole chiave"
 
-#: mod/contacts.php:586 mod/manage.php:143 mod/fsuggest.php:107
+#: mod/contacts.php:587 mod/manage.php:143 mod/fsuggest.php:107
 #: mod/message.php:342 mod/message.php:525 mod/crepair.php:196
 #: mod/events.php:574 mod/content.php:712 mod/install.php:261
 #: mod/install.php:299 mod/mood.php:137 mod/profiles.php:696
@@ -204,308 +204,308 @@ msgstr "Recupera informazioni e parole chiave"
 #: object/Item.php:710 view/theme/cleanzero/config.php:80
 #: view/theme/dispy/config.php:70 view/theme/quattro/config.php:64
 #: view/theme/diabook/config.php:148 view/theme/diabook/theme.php:633
-#: view/theme/clean/config.php:83 view/theme/vier/config.php:107
-#: view/theme/duepuntozero/config.php:59
+#: view/theme/vier/config.php:107 view/theme/duepuntozero/config.php:59
 msgid "Submit"
 msgstr "Invia"
 
-#: mod/contacts.php:587
+#: mod/contacts.php:588
 msgid "Profile Visibility"
 msgstr "Visibilità del profilo"
 
-#: mod/contacts.php:588
+#: mod/contacts.php:589
 #, php-format
 msgid ""
 "Please choose the profile you would like to display to %s when viewing your "
 "profile securely."
 msgstr "Seleziona il profilo che vuoi mostrare a %s quando visita il tuo profilo in modo sicuro."
 
-#: mod/contacts.php:589
+#: mod/contacts.php:590
 msgid "Contact Information / Notes"
 msgstr "Informazioni / Note sul contatto"
 
-#: mod/contacts.php:590
+#: mod/contacts.php:591
 msgid "Edit contact notes"
 msgstr "Modifica note contatto"
 
-#: mod/contacts.php:595 mod/contacts.php:977 mod/viewcontacts.php:97
+#: mod/contacts.php:596 mod/contacts.php:952 mod/viewcontacts.php:97
 #: mod/nogroup.php:41
 #, php-format
 msgid "Visit %s's profile [%s]"
 msgstr "Visita il profilo di %s [%s]"
 
-#: mod/contacts.php:596
+#: mod/contacts.php:597
 msgid "Block/Unblock contact"
 msgstr "Blocca/Sblocca contatto"
 
-#: mod/contacts.php:597
+#: mod/contacts.php:598
 msgid "Ignore contact"
 msgstr "Ignora il contatto"
 
-#: mod/contacts.php:598
+#: mod/contacts.php:599
 msgid "Repair URL settings"
 msgstr "Impostazioni riparazione URL"
 
-#: mod/contacts.php:599
+#: mod/contacts.php:600
 msgid "View conversations"
 msgstr "Vedi conversazioni"
 
-#: mod/contacts.php:601
+#: mod/contacts.php:602
 msgid "Delete contact"
 msgstr "Rimuovi contatto"
 
-#: mod/contacts.php:605
+#: mod/contacts.php:606
 msgid "Last update:"
 msgstr "Ultimo aggiornamento:"
 
-#: mod/contacts.php:607
+#: mod/contacts.php:608
 msgid "Update public posts"
 msgstr "Aggiorna messaggi pubblici"
 
-#: mod/contacts.php:609 mod/admin.php:1653
+#: mod/contacts.php:610
 msgid "Update now"
 msgstr "Aggiorna adesso"
 
-#: mod/contacts.php:611 mod/dirfind.php:190 mod/allfriends.php:60
-#: mod/match.php:71 mod/suggest.php:82 include/contact_widgets.php:32
-#: include/Contact.php:321 include/conversation.php:924
+#: mod/contacts.php:612 mod/follow.php:103 mod/dirfind.php:196
+#: mod/allfriends.php:65 mod/match.php:71 mod/suggest.php:82
+#: include/contact_widgets.php:32 include/Contact.php:297
+#: include/conversation.php:924
 msgid "Connect/Follow"
 msgstr "Connetti/segui"
 
-#: mod/contacts.php:614 mod/contacts.php:805 mod/contacts.php:864
-#: mod/admin.php:1117
+#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:865
+#: mod/admin.php:1312
 msgid "Unblock"
 msgstr "Sblocca"
 
-#: mod/contacts.php:614 mod/contacts.php:805 mod/contacts.php:864
-#: mod/admin.php:1116
+#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:865
+#: mod/admin.php:1311
 msgid "Block"
 msgstr "Blocca"
 
-#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:871
+#: mod/contacts.php:616 mod/contacts.php:807 mod/contacts.php:872
 msgid "Unignore"
 msgstr "Non ignorare"
 
-#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:871
+#: mod/contacts.php:616 mod/contacts.php:807 mod/contacts.php:872
 #: mod/notifications.php:54 mod/notifications.php:179
 #: mod/notifications.php:259
 msgid "Ignore"
 msgstr "Ignora"
 
-#: mod/contacts.php:618
+#: mod/contacts.php:619
 msgid "Currently blocked"
 msgstr "Bloccato"
 
-#: mod/contacts.php:619
+#: mod/contacts.php:620
 msgid "Currently ignored"
 msgstr "Ignorato"
 
-#: mod/contacts.php:620
+#: mod/contacts.php:621
 msgid "Currently archived"
 msgstr "Al momento archiviato"
 
-#: mod/contacts.php:621 mod/notifications.php:172 mod/notifications.php:251
+#: mod/contacts.php:622 mod/notifications.php:172 mod/notifications.php:251
 msgid "Hide this contact from others"
 msgstr "Nascondi questo contatto agli altri"
 
-#: mod/contacts.php:621
+#: mod/contacts.php:622
 msgid ""
 "Replies/likes to your public posts <strong>may</strong> still be visible"
 msgstr "Risposte ai tuoi post pubblici <strong>possono</strong> essere comunque visibili"
 
-#: mod/contacts.php:622
+#: mod/contacts.php:623
 msgid "Notification for new posts"
 msgstr "Notifica per i nuovi messaggi"
 
-#: mod/contacts.php:622
+#: mod/contacts.php:623
 msgid "Send a notification of every new post of this contact"
 msgstr "Invia una notifica per ogni nuovo messaggio di questo contatto"
 
-#: mod/contacts.php:625
+#: mod/contacts.php:626
 msgid "Blacklisted keywords"
 msgstr "Parole chiave in blacklist"
 
-#: mod/contacts.php:625
+#: mod/contacts.php:626
 msgid ""
 "Comma separated list of keywords that should not be converted to hashtags, "
 "when \"Fetch information and keywords\" is selected"
 msgstr "Lista separata da virgola di parole chiave che non dovranno essere convertite in hastag, quando \"Recupera informazioni e parole chiave\" è selezionato"
 
-#: mod/contacts.php:632 mod/follow.php:121 mod/notifications.php:255
+#: mod/contacts.php:633 mod/follow.php:126 mod/notifications.php:255
 msgid "Profile URL"
 msgstr "URL Profilo"
 
-#: mod/contacts.php:635 mod/follow.php:125 mod/notifications.php:244
-#: mod/events.php:566 mod/directory.php:145 include/identity.php:304
-#: include/bb2diaspora.php:170 include/event.php:36 include/event.php:60
+#: mod/contacts.php:636 mod/notifications.php:244 mod/events.php:566
+#: mod/directory.php:145 include/identity.php:308 include/bb2diaspora.php:170
+#: include/event.php:36 include/event.php:60
 msgid "Location:"
 msgstr "Posizione:"
 
-#: mod/contacts.php:637 mod/follow.php:127 mod/notifications.php:246
-#: mod/directory.php:153 include/identity.php:313 include/identity.php:621
+#: mod/contacts.php:638 mod/notifications.php:246 mod/directory.php:153
+#: include/identity.php:317 include/identity.php:631
 msgid "About:"
 msgstr "Informazioni:"
 
-#: mod/contacts.php:639 mod/follow.php:129 mod/notifications.php:248
-#: include/identity.php:615
+#: mod/contacts.php:640 mod/follow.php:134 mod/notifications.php:248
+#: include/identity.php:625
 msgid "Tags:"
 msgstr "Tag:"
 
-#: mod/contacts.php:684
+#: mod/contacts.php:685
 msgid "Suggestions"
 msgstr "Suggerimenti"
 
-#: mod/contacts.php:687
+#: mod/contacts.php:688
 msgid "Suggest potential friends"
 msgstr "Suggerisci potenziali amici"
 
-#: mod/contacts.php:692 mod/group.php:192
+#: mod/contacts.php:693 mod/group.php:192
 msgid "All Contacts"
 msgstr "Tutti i contatti"
 
-#: mod/contacts.php:695
+#: mod/contacts.php:696
 msgid "Show all contacts"
 msgstr "Mostra tutti i contatti"
 
-#: mod/contacts.php:700
+#: mod/contacts.php:701
 msgid "Unblocked"
 msgstr "Sbloccato"
 
-#: mod/contacts.php:703
+#: mod/contacts.php:704
 msgid "Only show unblocked contacts"
 msgstr "Mostra solo contatti non bloccati"
 
-#: mod/contacts.php:709
+#: mod/contacts.php:710
 msgid "Blocked"
 msgstr "Bloccato"
 
-#: mod/contacts.php:712
+#: mod/contacts.php:713
 msgid "Only show blocked contacts"
 msgstr "Mostra solo contatti bloccati"
 
-#: mod/contacts.php:718
+#: mod/contacts.php:719
 msgid "Ignored"
 msgstr "Ignorato"
 
-#: mod/contacts.php:721
+#: mod/contacts.php:722
 msgid "Only show ignored contacts"
 msgstr "Mostra solo contatti ignorati"
 
-#: mod/contacts.php:727
+#: mod/contacts.php:728
 msgid "Archived"
 msgstr "Achiviato"
 
-#: mod/contacts.php:730
+#: mod/contacts.php:731
 msgid "Only show archived contacts"
 msgstr "Mostra solo contatti archiviati"
 
-#: mod/contacts.php:736
+#: mod/contacts.php:737
 msgid "Hidden"
 msgstr "Nascosto"
 
-#: mod/contacts.php:739
+#: mod/contacts.php:740
 msgid "Only show hidden contacts"
 msgstr "Mostra solo contatti nascosti"
 
-#: mod/contacts.php:792 mod/contacts.php:840 mod/viewcontacts.php:116
-#: include/identity.php:732 include/identity.php:735 include/text.php:1012
+#: mod/contacts.php:793 mod/contacts.php:841 mod/viewcontacts.php:116
+#: include/identity.php:741 include/identity.php:744 include/text.php:1012
 #: include/nav.php:123 include/nav.php:187 view/theme/diabook/theme.php:125
 msgid "Contacts"
 msgstr "Contatti"
 
-#: mod/contacts.php:796
+#: mod/contacts.php:797
 msgid "Search your contacts"
 msgstr "Cerca nei tuoi contatti"
 
-#: mod/contacts.php:797
+#: mod/contacts.php:798
 msgid "Finding: "
 msgstr "Ricerca: "
 
-#: mod/contacts.php:798 mod/directory.php:210 include/contact_widgets.php:34
+#: mod/contacts.php:799 mod/directory.php:210 include/contact_widgets.php:34
 msgid "Find"
 msgstr "Trova"
 
-#: mod/contacts.php:804 mod/settings.php:146 mod/settings.php:676
+#: mod/contacts.php:805 mod/settings.php:156 mod/settings.php:685
 msgid "Update"
 msgstr "Aggiorna"
 
-#: mod/contacts.php:807 mod/contacts.php:878
+#: mod/contacts.php:808 mod/contacts.php:879
 msgid "Archive"
 msgstr "Archivia"
 
-#: mod/contacts.php:807 mod/contacts.php:878
+#: mod/contacts.php:808 mod/contacts.php:879
 msgid "Unarchive"
 msgstr "Dearchivia"
 
-#: mod/contacts.php:808 mod/group.php:171 mod/admin.php:1115
-#: mod/content.php:440 mod/content.php:743 mod/settings.php:713
+#: mod/contacts.php:809 mod/group.php:171 mod/admin.php:1310
+#: mod/content.php:440 mod/content.php:743 mod/settings.php:722
 #: mod/photos.php:1723 object/Item.php:134 include/conversation.php:635
 msgid "Delete"
 msgstr "Rimuovi"
 
-#: mod/contacts.php:821 include/identity.php:677 include/nav.php:75
+#: mod/contacts.php:822 include/identity.php:686 include/nav.php:75
 msgid "Status"
 msgstr "Stato"
 
-#: mod/contacts.php:824 include/identity.php:680
+#: mod/contacts.php:825 mod/follow.php:143 include/identity.php:689
 msgid "Status Messages and Posts"
 msgstr "Messaggi di stato e post"
 
-#: mod/contacts.php:829 mod/profperm.php:104 mod/newmember.php:32
-#: include/identity.php:569 include/identity.php:655 include/identity.php:685
+#: mod/contacts.php:830 mod/profperm.php:104 mod/newmember.php:32
+#: include/identity.php:579 include/identity.php:665 include/identity.php:694
 #: include/nav.php:76 view/theme/diabook/theme.php:124
 msgid "Profile"
 msgstr "Profilo"
 
-#: mod/contacts.php:832 include/identity.php:688
+#: mod/contacts.php:833 include/identity.php:697
 msgid "Profile Details"
 msgstr "Dettagli del profilo"
 
-#: mod/contacts.php:843
+#: mod/contacts.php:844
 msgid "View all contacts"
 msgstr "Vedi tutti i contatti"
 
-#: mod/contacts.php:849 mod/common.php:135
+#: mod/contacts.php:850 mod/common.php:134
 msgid "Common Friends"
 msgstr "Amici in comune"
 
-#: mod/contacts.php:852
+#: mod/contacts.php:853
 msgid "View all common friends"
 msgstr "Vedi tutti gli amici in comune"
 
-#: mod/contacts.php:856
+#: mod/contacts.php:857
 msgid "Repair"
 msgstr "Ripara"
 
-#: mod/contacts.php:859
+#: mod/contacts.php:860
 msgid "Advanced Contact Settings"
 msgstr "Impostazioni avanzate Contatto"
 
-#: mod/contacts.php:867
+#: mod/contacts.php:868
 msgid "Toggle Blocked status"
 msgstr "Inverti stato \"Blocca\""
 
-#: mod/contacts.php:874
+#: mod/contacts.php:875
 msgid "Toggle Ignored status"
 msgstr "Inverti stato \"Ignora\""
 
-#: mod/contacts.php:881
+#: mod/contacts.php:882
 msgid "Toggle Archive status"
 msgstr "Inverti stato \"Archiviato\""
 
-#: mod/contacts.php:949
+#: mod/contacts.php:924
 msgid "Mutual Friendship"
 msgstr "Amicizia reciproca"
 
-#: mod/contacts.php:953
+#: mod/contacts.php:928
 msgid "is a fan of yours"
 msgstr "è un tuo fan"
 
-#: mod/contacts.php:957
+#: mod/contacts.php:932
 msgid "you are a fan of"
 msgstr "sei un fan di"
 
-#: mod/contacts.php:978 mod/nogroup.php:42
+#: mod/contacts.php:953 mod/nogroup.php:42
 msgid "Edit contact"
 msgstr "Modifca contatto"
 
@@ -531,7 +531,7 @@ msgstr "Seleziona un'identità da gestire:"
 msgid "Post successful."
 msgstr "Inviato!"
 
-#: mod/profperm.php:19 mod/group.php:72 index.php:381
+#: mod/profperm.php:19 mod/group.php:72 index.php:382
 msgid "Permission denied"
 msgstr "Permesso negato"
 
@@ -555,23 +555,23 @@ msgstr "Visibile a"
 msgid "All Contacts (with secure profile access)"
 msgstr "Tutti i contatti (con profilo ad accesso sicuro)"
 
-#: mod/display.php:82 mod/display.php:283 mod/display.php:500
-#: mod/viewsrc.php:15 mod/admin.php:196 mod/admin.php:1160 mod/admin.php:1381
-#: mod/notice.php:15 include/items.php:4858
+#: mod/display.php:82 mod/display.php:291 mod/display.php:513
+#: mod/viewsrc.php:15 mod/admin.php:234 mod/admin.php:1365 mod/admin.php:1599
+#: mod/notice.php:15 include/items.php:4887
 msgid "Item not found."
 msgstr "Elemento non trovato."
 
-#: mod/display.php:211 mod/videos.php:197 mod/viewcontacts.php:35
-#: mod/community.php:18 mod/dfrn_request.php:779 mod/search.php:93
+#: mod/display.php:220 mod/videos.php:197 mod/viewcontacts.php:35
+#: mod/community.php:22 mod/dfrn_request.php:786 mod/search.php:93
 #: mod/search.php:99 mod/directory.php:37 mod/photos.php:976
 msgid "Public access denied."
 msgstr "Accesso negato."
 
-#: mod/display.php:331 mod/profile.php:155
+#: mod/display.php:339 mod/profile.php:155
 msgid "Access to this profile has been restricted."
 msgstr "L'accesso a questo profilo è stato limitato."
 
-#: mod/display.php:493
+#: mod/display.php:506
 msgid "Item has been removed."
 msgstr "L'oggetto è stato rimosso."
 
@@ -606,8 +606,8 @@ msgid ""
 " join."
 msgstr "Sulla tua pagina <em>Quick Start</em> - veloce introduzione alla tua pagina profilo e alla pagina Rete, fai qualche nuova amicizia, e trova qualche gruppo a cui unirti."
 
-#: mod/newmember.php:22 mod/admin.php:1212 mod/admin.php:1457
-#: mod/settings.php:99 include/nav.php:182 view/theme/diabook/theme.php:544
+#: mod/newmember.php:22 mod/admin.php:1418 mod/admin.php:1676
+#: mod/settings.php:109 include/nav.php:182 view/theme/diabook/theme.php:544
 #: view/theme/diabook/theme.php:648
 msgid "Settings"
 msgstr "Impostazioni"
@@ -668,60 +668,44 @@ msgstr "Inserisci qualche parola chiave pubblica nel tuo profilo predefinito che
 msgid "Connecting"
 msgstr "Collegarsi"
 
-#: mod/newmember.php:49 mod/newmember.php:51 include/contact_selectors.php:81
-msgid "Facebook"
-msgstr "Facebook"
-
-#: mod/newmember.php:49
-msgid ""
-"Authorise the Facebook Connector if you currently have a Facebook account "
-"and we will (optionally) import all your Facebook friends and conversations."
-msgstr "Autorizza il Facebook Connector se hai un account Facebook, e noi (opzionalmente) importeremo tuti i tuoi amici e le tue conversazioni da Facebook."
-
 #: mod/newmember.php:51
-msgid ""
-"<em>If</em> this is your own personal server, installing the Facebook addon "
-"may ease your transition to the free social web."
-msgstr "<em>Se</em questo è il tuo server personale, installare il plugin per Facebook puo' aiutarti nella transizione verso il web sociale libero."
-
-#: mod/newmember.php:56
 msgid "Importing Emails"
 msgstr "Importare le Email"
 
-#: mod/newmember.php:56
+#: mod/newmember.php:51
 msgid ""
 "Enter your email access information on your Connector Settings page if you "
 "wish to import and interact with friends or mailing lists from your email "
 "INBOX"
 msgstr "Inserisci i tuoi dati di accesso all'email nella tua pagina Impostazioni Connettori se vuoi importare e interagire con amici o mailing list dalla tua casella di posta in arrivo"
 
-#: mod/newmember.php:58
+#: mod/newmember.php:53
 msgid "Go to Your Contacts Page"
 msgstr "Vai alla tua pagina Contatti"
 
-#: mod/newmember.php:58
+#: mod/newmember.php:53
 msgid ""
 "Your Contacts page is your gateway to managing friendships and connecting "
 "with friends on other networks. Typically you enter their address or site "
 "URL in the <em>Add New Contact</em> dialog."
 msgstr "La tua pagina Contatti è il mezzo per gestire le amicizie e collegarsi con amici su altre reti. Di solito, basta inserire l'indirizzo nel campo <em>Aggiungi Nuovo Contatto</em>"
 
-#: mod/newmember.php:60
+#: mod/newmember.php:55
 msgid "Go to Your Site's Directory"
 msgstr "Vai all'Elenco del tuo sito"
 
-#: mod/newmember.php:60
+#: mod/newmember.php:55
 msgid ""
 "The Directory page lets you find other people in this network or other "
 "federated sites. Look for a <em>Connect</em> or <em>Follow</em> link on "
 "their profile page. Provide your own Identity Address if requested."
 msgstr "La pagina Elenco ti permette di trovare altre persone in questa rete o in altri siti. Cerca un link <em>Connetti</em> o <em>Segui</em> nella loro pagina del profilo. Inserisci il tuo Indirizzo Identità, se richiesto."
 
-#: mod/newmember.php:62
+#: mod/newmember.php:57
 msgid "Finding New People"
 msgstr "Trova nuove persone"
 
-#: mod/newmember.php:62
+#: mod/newmember.php:57
 msgid ""
 "On the side panel of the Contacts page are several tools to find new "
 "friends. We can match people by interest, look up people by name or "
@@ -730,41 +714,41 @@ msgid ""
 "hours."
 msgstr "Nel pannello laterale nella pagina \"Contatti\", ci sono diversi strumenti per trovare nuovi amici. Possiamo confrontare le persone per interessi, cercare le persone per nome e fornire suggerimenti basati sui tuoi contatti esistenti. Su un sito nuovo, i suggerimenti sono di solito presenti dopo 24 ore."
 
-#: mod/newmember.php:66 include/group.php:283
+#: mod/newmember.php:61 include/group.php:283
 msgid "Groups"
 msgstr "Gruppi"
 
-#: mod/newmember.php:70
+#: mod/newmember.php:65
 msgid "Group Your Contacts"
 msgstr "Raggruppa i tuoi contatti"
 
-#: mod/newmember.php:70
+#: mod/newmember.php:65
 msgid ""
 "Once you have made some friends, organize them into private conversation "
 "groups from the sidebar of your Contacts page and then you can interact with"
 " each group privately on your Network page."
 msgstr "Quando avrai alcuni amici, organizzali in gruppi di conversazioni private dalla barra laterale della tua pagina Contatti. Potrai interagire privatamente con ogni gruppo nella tua pagina Rete"
 
-#: mod/newmember.php:73
+#: mod/newmember.php:68
 msgid "Why Aren't My Posts Public?"
 msgstr "Perchè i miei post non sono pubblici?"
 
-#: mod/newmember.php:73
+#: mod/newmember.php:68
 msgid ""
 "Friendica respects your privacy. By default, your posts will only show up to"
 " people you've added as friends. For more information, see the help section "
 "from the link above."
 msgstr "Friendica rispetta la tua provacy. Per impostazione predefinita, i tuoi post sono mostrati solo alle persone che hai aggiunto come amici. Per maggiori informazioni guarda la sezione della guida dal link qui sopra."
 
-#: mod/newmember.php:78
+#: mod/newmember.php:73
 msgid "Getting Help"
 msgstr "Ottenere Aiuto"
 
-#: mod/newmember.php:82
+#: mod/newmember.php:77
 msgid "Go to the Help Section"
 msgstr "Vai alla sezione Guida"
 
-#: mod/newmember.php:82
+#: mod/newmember.php:77
 msgid ""
 "Our <strong>help</strong> pages may be consulted for detail on other program"
 " features and resources."
@@ -779,7 +763,7 @@ msgid ""
 "Account not found and OpenID registration is not permitted on this site."
 msgstr "L'account non è stato trovato, e la registrazione via OpenID non è permessa su questo sito."
 
-#: mod/openid.php:93 include/auth.php:112 include/auth.php:175
+#: mod/openid.php:93 include/auth.php:118 include/auth.php:181
 msgid "Login failed."
 msgstr "Accesso fallito."
 
@@ -865,18 +849,18 @@ msgstr "Immagine caricata con successo."
 msgid "Image upload failed."
 msgstr "Caricamento immagine fallito."
 
-#: mod/subthread.php:87 mod/tagger.php:62 mod/like.php:168
+#: mod/subthread.php:87 mod/tagger.php:62 include/like.php:165
 #: include/conversation.php:130 include/conversation.php:266
-#: include/text.php:1993 include/diaspora.php:2146
+#: include/text.php:2000 include/diaspora.php:2169
 #: view/theme/diabook/theme.php:471
 msgid "photo"
 msgstr "foto"
 
-#: mod/subthread.php:87 mod/tagger.php:62 mod/like.php:168 mod/like.php:346
-#: include/conversation.php:125 include/conversation.php:134
-#: include/conversation.php:261 include/conversation.php:270
-#: include/diaspora.php:2146 view/theme/diabook/theme.php:466
-#: view/theme/diabook/theme.php:475
+#: mod/subthread.php:87 mod/tagger.php:62 include/like.php:165
+#: include/like.php:334 include/conversation.php:125
+#: include/conversation.php:134 include/conversation.php:261
+#: include/conversation.php:270 include/diaspora.php:2169
+#: view/theme/diabook/theme.php:466 view/theme/diabook/theme.php:475
 msgid "status"
 msgstr "stato"
 
@@ -902,8 +886,8 @@ msgid "Remove"
 msgstr "Rimuovi"
 
 #: mod/ostatus_subscribe.php:14
-msgid "Subsribing to OStatus contacts"
-msgstr "Iscrizione a contatti OStatus"
+msgid "Subscribing to OStatus contacts"
+msgstr ""
 
 #: mod/ostatus_subscribe.php:25
 msgid "No contact provided."
@@ -937,8 +921,8 @@ msgstr "ignorato"
 msgid "Keep this window open until done."
 msgstr "Tieni questa finestra aperta fino a che ha finito."
 
-#: mod/filer.php:30 include/conversation.php:1133
-#: include/conversation.php:1151
+#: mod/filer.php:30 include/conversation.php:1132
+#: include/conversation.php:1150
 msgid "Save to Folder:"
 msgstr "Salva nella Cartella:"
 
@@ -951,54 +935,54 @@ msgstr "- seleziona -"
 msgid "Save"
 msgstr "Salva"
 
-#: mod/follow.php:18 mod/dfrn_request.php:863
+#: mod/follow.php:19 mod/dfrn_request.php:870
 msgid "Submit Request"
 msgstr "Invia richiesta"
 
-#: mod/follow.php:29
+#: mod/follow.php:30
 msgid "You already added this contact."
 msgstr "Hai già aggiunto questo contatto."
 
-#: mod/follow.php:38
+#: mod/follow.php:39
 msgid "Diaspora support isn't enabled. Contact can't be added."
 msgstr "Il supporto Diaspora non è abilitato. Il contatto non puo' essere aggiunto."
 
-#: mod/follow.php:45
+#: mod/follow.php:46
 msgid "OStatus support is disabled. Contact can't be added."
 msgstr "Il supporto OStatus non è abilitato. Il contatto non puo' essere aggiunto."
 
-#: mod/follow.php:52
+#: mod/follow.php:53
 msgid "The network type couldn't be detected. Contact can't be added."
 msgstr "Non è possibile rilevare il tipo di rete. Il contatto non puo' essere aggiunto."
 
-#: mod/follow.php:104 mod/dfrn_request.php:849
+#: mod/follow.php:109 mod/dfrn_request.php:856
 msgid "Please answer the following:"
 msgstr "Rispondi:"
 
-#: mod/follow.php:105 mod/dfrn_request.php:850
+#: mod/follow.php:110 mod/dfrn_request.php:857
 #, php-format
 msgid "Does %s know you?"
 msgstr "%s ti conosce?"
 
-#: mod/follow.php:105 mod/settings.php:1094 mod/settings.php:1100
-#: mod/settings.php:1108 mod/settings.php:1112 mod/settings.php:1117
-#: mod/settings.php:1123 mod/settings.php:1129 mod/settings.php:1135
-#: mod/settings.php:1161 mod/settings.php:1162 mod/settings.php:1163
-#: mod/settings.php:1164 mod/settings.php:1165 mod/dfrn_request.php:850
+#: mod/follow.php:110 mod/settings.php:1103 mod/settings.php:1109
+#: mod/settings.php:1117 mod/settings.php:1121 mod/settings.php:1126
+#: mod/settings.php:1132 mod/settings.php:1138 mod/settings.php:1144
+#: mod/settings.php:1170 mod/settings.php:1171 mod/settings.php:1172
+#: mod/settings.php:1173 mod/settings.php:1174 mod/dfrn_request.php:857
 #: mod/register.php:239 mod/profiles.php:658 mod/profiles.php:662
 #: mod/profiles.php:687 mod/api.php:106
 msgid "No"
 msgstr "No"
 
-#: mod/follow.php:106 mod/dfrn_request.php:854
+#: mod/follow.php:111 mod/dfrn_request.php:861
 msgid "Add a personal note:"
 msgstr "Aggiungi una nota personale:"
 
-#: mod/follow.php:112 mod/dfrn_request.php:860
+#: mod/follow.php:117 mod/dfrn_request.php:867
 msgid "Your Identity Address:"
 msgstr "L'indirizzo della tua identità:"
 
-#: mod/follow.php:162
+#: mod/follow.php:180
 msgid "Contact added"
 msgstr "Contatto aggiunto"
 
@@ -1006,39 +990,39 @@ msgstr "Contatto aggiunto"
 msgid "Unable to locate original post."
 msgstr "Impossibile trovare il messaggio originale."
 
-#: mod/item.php:322
+#: mod/item.php:329
 msgid "Empty post discarded."
 msgstr "Messaggio vuoto scartato."
 
-#: mod/item.php:460 mod/wall_upload.php:213 mod/wall_upload.php:227
-#: mod/wall_upload.php:234 include/Photo.php:954 include/Photo.php:969
-#: include/Photo.php:976 include/Photo.php:998 include/message.php:145
+#: mod/item.php:467 mod/wall_upload.php:213 mod/wall_upload.php:227
+#: mod/wall_upload.php:234 include/Photo.php:958 include/Photo.php:973
+#: include/Photo.php:980 include/Photo.php:1002 include/message.php:145
 msgid "Wall Photos"
 msgstr "Foto della bacheca"
 
-#: mod/item.php:834
+#: mod/item.php:842
 msgid "System error. Post not saved."
 msgstr "Errore di sistema. Messaggio non salvato."
 
-#: mod/item.php:963
+#: mod/item.php:971
 #, php-format
 msgid ""
 "This message was sent to you by %s, a member of the Friendica social "
 "network."
 msgstr "Questo messaggio ti è stato inviato da %s, un membro del social network Friendica."
 
-#: mod/item.php:965
+#: mod/item.php:973
 #, php-format
 msgid "You may visit them online at %s"
 msgstr "Puoi visitarli online su %s"
 
-#: mod/item.php:966
+#: mod/item.php:974
 msgid ""
 "Please contact the sender by replying to this post if you do not wish to "
 "receive these messages."
 msgstr "Contatta il mittente rispondendo a questo post se non vuoi ricevere questi messaggi."
 
-#: mod/item.php:970
+#: mod/item.php:978
 #, php-format
 msgid "%s posted an update."
 msgstr "%s ha inviato un aggiornamento."
@@ -1087,11 +1071,11 @@ msgstr "Modifica gruppo"
 msgid "Members"
 msgstr "Membri"
 
-#: mod/group.php:193 mod/network.php:563 mod/content.php:130
+#: mod/group.php:193 mod/network.php:576 mod/content.php:130
 msgid "Group is empty"
 msgstr "Il gruppo è vuoto"
 
-#: mod/apps.php:7 index.php:225
+#: mod/apps.php:7 index.php:226
 msgid "You must be logged in to use addons. "
 msgstr "Devi aver effettuato il login per usare gli addons."
 
@@ -1148,7 +1132,7 @@ msgid "Unable to set contact photo."
 msgstr "Impossibile impostare la foto del contatto."
 
 #: mod/dfrn_confirm.php:487 include/conversation.php:185
-#: include/diaspora.php:636
+#: include/diaspora.php:637
 #, php-format
 msgid "%1$s is now friends with %2$s"
 msgstr "%1$s e %2$s adesso sono amici"
@@ -1189,7 +1173,7 @@ msgstr "Impossibile impostare le credenziali del tuo contatto sul nostro sistema
 msgid "Unable to update your contact profile details on our system"
 msgstr "Impossibile aggiornare i dettagli del tuo contatto sul nostro sistema"
 
-#: mod/dfrn_confirm.php:753 mod/dfrn_request.php:734 include/items.php:4270
+#: mod/dfrn_confirm.php:753 mod/dfrn_request.php:741 include/items.php:4299
 msgid "[Name Withheld]"
 msgstr "[Nome Nascosto]"
 
@@ -1198,7 +1182,7 @@ msgstr "[Nome Nascosto]"
 msgid "%1$s has joined %2$s"
 msgstr "%1$s si è unito a %2$s"
 
-#: mod/profile.php:21 include/identity.php:52
+#: mod/profile.php:21 include/identity.php:51
 msgid "Requested profile is not available."
 msgstr "Profilo richiesto non disponibile."
 
@@ -1222,7 +1206,7 @@ msgstr "Nessun video selezionato"
 msgid "Access to this item is restricted."
 msgstr "Questo oggetto non è visibile a tutti."
 
-#: mod/videos.php:383 include/text.php:1465
+#: mod/videos.php:383 include/text.php:1472
 msgid "View Video"
 msgstr "Guarda Video"
 
@@ -1258,7 +1242,7 @@ msgstr "Suggerisci un amico a %s"
 
 #: mod/wall_upload.php:20 mod/wall_upload.php:33 mod/wall_upload.php:86
 #: mod/wall_upload.php:122 mod/wall_upload.php:125 mod/wall_attach.php:17
-#: mod/wall_attach.php:25 mod/wall_attach.php:76 include/api.php:1733
+#: mod/wall_attach.php:25 mod/wall_attach.php:76 include/api.php:1781
 msgid "Invalid request."
 msgstr "Richiesta non valida."
 
@@ -1314,7 +1298,7 @@ msgid ""
 "Password reset failed."
 msgstr "La richiesta non può essere verificata. (Puoi averla già richiesta precendentemente). Reimpostazione password fallita."
 
-#: mod/lostpass.php:109 boot.php:1307
+#: mod/lostpass.php:109 boot.php:1444
 msgid "Password Reset"
 msgstr "Reimpostazione password"
 
@@ -1388,37 +1372,6 @@ msgstr "Nome utente o email: "
 msgid "Reset"
 msgstr "Reimposta"
 
-#: mod/like.php:170 include/conversation.php:122 include/conversation.php:258
-#: include/text.php:1991 view/theme/diabook/theme.php:463
-msgid "event"
-msgstr "l'evento"
-
-#: mod/like.php:187 include/conversation.php:141 include/diaspora.php:2162
-#: view/theme/diabook/theme.php:480
-#, php-format
-msgid "%1$s likes %2$s's %3$s"
-msgstr "A %1$s piace %3$s di %2$s"
-
-#: mod/like.php:189 include/conversation.php:144
-#, php-format
-msgid "%1$s doesn't like %2$s's %3$s"
-msgstr "A %1$s non piace %3$s di %2$s"
-
-#: mod/like.php:191
-#, php-format
-msgid "%1$s is attending %2$s's %3$s"
-msgstr "%1$s parteciperà a %3$s di %2$s"
-
-#: mod/like.php:193
-#, php-format
-msgid "%1$s is not attending %2$s's %3$s"
-msgstr "%1$s non parteciperà a %3$s di %2$s"
-
-#: mod/like.php:195
-#, php-format
-msgid "%1$s may attend %2$s's %3$s"
-msgstr "%1$s forse parteciperà a %3$s di %2$s"
-
 #: mod/ping.php:265
 msgid "{0} wants to be your friend"
 msgstr "{0} vuole essere tuo amico"
@@ -1448,11 +1401,11 @@ msgstr "Scarta"
 msgid "System"
 msgstr "Sistema"
 
-#: mod/notifications.php:87 mod/admin.php:228 include/nav.php:154
+#: mod/notifications.php:87 mod/admin.php:390 include/nav.php:154
 msgid "Network"
 msgstr "Rete"
 
-#: mod/notifications.php:93 mod/network.php:381
+#: mod/notifications.php:93 mod/network.php:384
 msgid "Personal"
 msgstr "Personale"
 
@@ -1494,7 +1447,7 @@ msgstr "Invia una attività \"è ora amico con\""
 msgid "if applicable"
 msgstr "se applicabile"
 
-#: mod/notifications.php:176 mod/notifications.php:257 mod/admin.php:1113
+#: mod/notifications.php:176 mod/notifications.php:257 mod/admin.php:1308
 msgid "Approve"
 msgstr "Approva"
 
@@ -1544,8 +1497,8 @@ msgstr "Richiesta amicizia/connessione"
 msgid "New Follower"
 msgstr "Qualcuno inizia a seguirti"
 
-#: mod/notifications.php:250 mod/directory.php:147 include/identity.php:306
-#: include/identity.php:580
+#: mod/notifications.php:250 mod/directory.php:147 include/identity.php:310
+#: include/identity.php:590
 msgid "Gender:"
 msgstr "Genere:"
 
@@ -1716,7 +1669,7 @@ msgstr "Conversazione rimossa."
 
 #: mod/message.php:290 mod/message.php:298 mod/message.php:427
 #: mod/message.php:435 mod/wallmessage.php:127 mod/wallmessage.php:135
-#: include/conversation.php:1129 include/conversation.php:1147
+#: include/conversation.php:1128 include/conversation.php:1146
 msgid "Please enter a link URL:"
 msgstr "Inserisci l'indirizzo del link:"
 
@@ -1738,19 +1691,19 @@ msgid "Your message:"
 msgstr "Il tuo messaggio:"
 
 #: mod/message.php:339 mod/message.php:523 mod/wallmessage.php:154
-#: mod/editpost.php:110 include/conversation.php:1184
+#: mod/editpost.php:110 include/conversation.php:1183
 msgid "Upload photo"
 msgstr "Carica foto"
 
 #: mod/message.php:340 mod/message.php:524 mod/wallmessage.php:155
-#: mod/editpost.php:114 include/conversation.php:1188
+#: mod/editpost.php:114 include/conversation.php:1187
 msgid "Insert web link"
 msgstr "Inserisci link"
 
 #: mod/message.php:341 mod/message.php:526 mod/content.php:501
 #: mod/content.php:885 mod/wallmessage.php:156 mod/editpost.php:124
 #: mod/photos.php:1610 object/Item.php:396 include/conversation.php:713
-#: include/conversation.php:1202
+#: include/conversation.php:1201
 msgid "Please wait"
 msgstr "Attendi"
 
@@ -1766,7 +1719,7 @@ msgstr "Messaggio non disponibile."
 msgid "Delete message"
 msgstr "Elimina il messaggio"
 
-#: mod/message.php:507 mod/message.php:582
+#: mod/message.php:507 mod/message.php:584
 msgid "Delete conversation"
 msgstr "Elimina la conversazione"
 
@@ -1780,26 +1733,26 @@ msgstr "Nessuna comunicazione sicura disponibile, <strong>Potresti</strong> esse
 msgid "Send Reply"
 msgstr "Invia la risposta"
 
-#: mod/message.php:555
+#: mod/message.php:557
 #, php-format
 msgid "Unknown sender - %s"
 msgstr "Mittente sconosciuto - %s"
 
-#: mod/message.php:558
+#: mod/message.php:560
 #, php-format
 msgid "You and %s"
 msgstr "Tu e %s"
 
-#: mod/message.php:561
+#: mod/message.php:563
 #, php-format
 msgid "%s and You"
 msgstr "%s e Tu"
 
-#: mod/message.php:585
+#: mod/message.php:587
 msgid "D, d M Y - g:i A"
 msgstr "D d M Y - G:i"
 
-#: mod/message.php:588
+#: mod/message.php:590
 #, php-format
 msgid "%d message"
 msgid_plural "%d messages"
@@ -1851,9 +1804,9 @@ msgstr "Ritorna alla modifica contatto"
 msgid "Refetch contact data"
 msgstr "Ricarica dati contatto"
 
-#: mod/crepair.php:170 mod/admin.php:1111 mod/admin.php:1123
-#: mod/admin.php:1124 mod/admin.php:1137 mod/settings.php:652
-#: mod/settings.php:678
+#: mod/crepair.php:170 mod/admin.php:1306 mod/admin.php:1318
+#: mod/admin.php:1319 mod/admin.php:1332 mod/settings.php:661
+#: mod/settings.php:687
 msgid "Name"
 msgstr "Nome"
 
@@ -1903,7 +1856,7 @@ msgid ""
 "entries from this contact."
 msgstr "Imposta questo contatto come 'io remoto', questo farà si che friendica reinvii i nuovi messaggi da questo contatto."
 
-#: mod/bookmarklet.php:12 boot.php:1293 include/nav.php:91
+#: mod/bookmarklet.php:12 boot.php:1430 include/nav.php:91
 msgid "Login"
 msgstr "Accedi"
 
@@ -1915,28 +1868,28 @@ msgstr "Il messaggio è stato creato"
 msgid "Access denied."
 msgstr "Accesso negato."
 
-#: mod/dirfind.php:188 mod/allfriends.php:75 mod/match.php:85
-#: mod/suggest.php:98 include/contact_widgets.php:10 include/identity.php:209
+#: mod/dirfind.php:194 mod/allfriends.php:80 mod/match.php:85
+#: mod/suggest.php:98 include/contact_widgets.php:10 include/identity.php:212
 msgid "Connect"
 msgstr "Connetti"
 
-#: mod/dirfind.php:189 mod/allfriends.php:59 mod/match.php:70
-#: mod/directory.php:162 mod/suggest.php:81 include/Contact.php:307
-#: include/Contact.php:320 include/Contact.php:362
+#: mod/dirfind.php:195 mod/allfriends.php:64 mod/match.php:70
+#: mod/directory.php:162 mod/suggest.php:81 include/Contact.php:283
+#: include/Contact.php:296 include/Contact.php:338
 #: include/conversation.php:912 include/conversation.php:926
 msgid "View Profile"
 msgstr "Visualizza profilo"
 
-#: mod/dirfind.php:218
+#: mod/dirfind.php:224
 #, php-format
 msgid "People Search - %s"
 msgstr "Cerca persone - %s"
 
-#: mod/dirfind.php:225 mod/match.php:105
+#: mod/dirfind.php:231 mod/match.php:105
 msgid "No matches"
 msgstr "Nessun risultato"
 
-#: mod/fbrowser.php:32 include/identity.php:693 include/nav.php:77
+#: mod/fbrowser.php:32 include/identity.php:702 include/nav.php:77
 #: view/theme/diabook/theme.php:126
 msgid "Photos"
 msgstr "Foto"
@@ -1956,548 +1909,579 @@ msgstr "File"
 msgid "Contacts who are not members of a group"
 msgstr "Contatti che non sono membri di un gruppo"
 
-#: mod/admin.php:80
+#: mod/admin.php:92
 msgid "Theme settings updated."
 msgstr "Impostazioni del tema aggiornate."
 
-#: mod/admin.php:127 mod/admin.php:711
+#: mod/admin.php:156 mod/admin.php:888
 msgid "Site"
 msgstr "Sito"
 
-#: mod/admin.php:128 mod/admin.php:655 mod/admin.php:1106 mod/admin.php:1121
+#: mod/admin.php:157 mod/admin.php:832 mod/admin.php:1301 mod/admin.php:1316
 msgid "Users"
 msgstr "Utenti"
 
-#: mod/admin.php:129 mod/admin.php:1210 mod/admin.php:1270 mod/settings.php:66
+#: mod/admin.php:158 mod/admin.php:1416 mod/admin.php:1476 mod/settings.php:72
 msgid "Plugins"
 msgstr "Plugin"
 
-#: mod/admin.php:130 mod/admin.php:1455 mod/admin.php:1506
+#: mod/admin.php:159 mod/admin.php:1674 mod/admin.php:1724
 msgid "Themes"
 msgstr "Temi"
 
-#: mod/admin.php:131
+#: mod/admin.php:160 mod/settings.php:50
+msgid "Additional features"
+msgstr "Funzionalità aggiuntive"
+
+#: mod/admin.php:161
 msgid "DB updates"
 msgstr "Aggiornamenti Database"
 
-#: mod/admin.php:132 mod/admin.php:223
+#: mod/admin.php:162 mod/admin.php:385
 msgid "Inspect Queue"
 msgstr "Ispeziona Coda di invio"
 
-#: mod/admin.php:147 mod/admin.php:156 mod/admin.php:1594
+#: mod/admin.php:163 mod/admin.php:354
+msgid "Federation Statistics"
+msgstr ""
+
+#: mod/admin.php:177 mod/admin.php:188 mod/admin.php:1792
 msgid "Logs"
 msgstr "Log"
 
-#: mod/admin.php:148
+#: mod/admin.php:178 mod/admin.php:1859
+msgid "View Logs"
+msgstr ""
+
+#: mod/admin.php:179
 msgid "probe address"
 msgstr "controlla indirizzo"
 
-#: mod/admin.php:149
+#: mod/admin.php:180
 msgid "check webfinger"
 msgstr "verifica webfinger"
 
-#: mod/admin.php:154 include/nav.php:194
+#: mod/admin.php:186 include/nav.php:194
 msgid "Admin"
 msgstr "Amministrazione"
 
-#: mod/admin.php:155
+#: mod/admin.php:187
 msgid "Plugin Features"
 msgstr "Impostazioni Plugins"
 
-#: mod/admin.php:157
+#: mod/admin.php:189
 msgid "diagnostics"
 msgstr "diagnostiche"
 
-#: mod/admin.php:158
+#: mod/admin.php:190
 msgid "User registrations waiting for confirmation"
 msgstr "Utenti registrati in attesa di conferma"
 
-#: mod/admin.php:222 mod/admin.php:272 mod/admin.php:710 mod/admin.php:1105
-#: mod/admin.php:1209 mod/admin.php:1269 mod/admin.php:1454 mod/admin.php:1505
-#: mod/admin.php:1593
+#: mod/admin.php:347
+msgid ""
+"This page offers you some numbers to the known part of the federated social "
+"network your Friendica node is part of. These numbers are not complete but "
+"only reflect the part of the network your node is aware of."
+msgstr ""
+
+#: mod/admin.php:348
+msgid ""
+"The <em>Auto Discovered Contact Directory</em> feature is not enabled, it "
+"will improve the data displayed here."
+msgstr ""
+
+#: mod/admin.php:353 mod/admin.php:384 mod/admin.php:441 mod/admin.php:887
+#: mod/admin.php:1300 mod/admin.php:1415 mod/admin.php:1475 mod/admin.php:1673
+#: mod/admin.php:1723 mod/admin.php:1791 mod/admin.php:1858
 msgid "Administration"
 msgstr "Amministrazione"
 
-#: mod/admin.php:225
+#: mod/admin.php:360
+#, php-format
+msgid "Currently this node is aware of %d nodes from the following platforms:"
+msgstr ""
+
+#: mod/admin.php:387
 msgid "ID"
 msgstr "ID"
 
-#: mod/admin.php:226
+#: mod/admin.php:388
 msgid "Recipient Name"
 msgstr "Nome Destinatario"
 
-#: mod/admin.php:227
+#: mod/admin.php:389
 msgid "Recipient Profile"
 msgstr "Profilo Destinatario"
 
-#: mod/admin.php:229
+#: mod/admin.php:391
 msgid "Created"
 msgstr "Creato"
 
-#: mod/admin.php:230
+#: mod/admin.php:392
 msgid "Last Tried"
 msgstr "Ultimo Tentativo"
 
-#: mod/admin.php:231
+#: mod/admin.php:393
 msgid ""
 "This page lists the content of the queue for outgoing postings. These are "
 "postings the initial delivery failed for. They will be resend later and "
 "eventually deleted if the delivery fails permanently."
 msgstr "Questa pagina elenca il contenuto della coda di invo dei post. Questi sono post la cui consegna è fallita. Verranno reinviati più tardi ed eventualmente cancellati se la consegna continua a fallire."
 
-#: mod/admin.php:243 mod/admin.php:1059
+#: mod/admin.php:412 mod/admin.php:1254
 msgid "Normal Account"
 msgstr "Account normale"
 
-#: mod/admin.php:244 mod/admin.php:1060
+#: mod/admin.php:413 mod/admin.php:1255
 msgid "Soapbox Account"
 msgstr "Account per comunicati e annunci"
 
-#: mod/admin.php:245 mod/admin.php:1061
+#: mod/admin.php:414 mod/admin.php:1256
 msgid "Community/Celebrity Account"
 msgstr "Account per celebrità o per comunità"
 
-#: mod/admin.php:246 mod/admin.php:1062
+#: mod/admin.php:415 mod/admin.php:1257
 msgid "Automatic Friend Account"
 msgstr "Account per amicizia automatizzato"
 
-#: mod/admin.php:247
+#: mod/admin.php:416
 msgid "Blog Account"
 msgstr "Account Blog"
 
-#: mod/admin.php:248
+#: mod/admin.php:417
 msgid "Private Forum"
 msgstr "Forum Privato"
 
-#: mod/admin.php:267
+#: mod/admin.php:436
 msgid "Message queues"
 msgstr "Code messaggi"
 
-#: mod/admin.php:273
+#: mod/admin.php:442
 msgid "Summary"
 msgstr "Sommario"
 
-#: mod/admin.php:275
+#: mod/admin.php:444
 msgid "Registered users"
 msgstr "Utenti registrati"
 
-#: mod/admin.php:277
+#: mod/admin.php:446
 msgid "Pending registrations"
 msgstr "Registrazioni in attesa"
 
-#: mod/admin.php:278
+#: mod/admin.php:447
 msgid "Version"
 msgstr "Versione"
 
-#: mod/admin.php:283
+#: mod/admin.php:452
 msgid "Active plugins"
 msgstr "Plugin attivi"
 
-#: mod/admin.php:306
+#: mod/admin.php:475
 msgid "Can not parse base url. Must have at least <scheme>://<domain>"
 msgstr "Impossibile analizzare l'url base. Deve avere almeno [schema]://[dominio]"
 
-#: mod/admin.php:587
+#: mod/admin.php:760
 msgid "RINO2 needs mcrypt php extension to work."
 msgstr "RINO2 necessita dell'estensione php mcrypt per funzionare."
 
-#: mod/admin.php:595
+#: mod/admin.php:768
 msgid "Site settings updated."
 msgstr "Impostazioni del sito aggiornate."
 
-#: mod/admin.php:619 mod/settings.php:903
+#: mod/admin.php:796 mod/settings.php:912
 msgid "No special theme for mobile devices"
 msgstr "Nessun tema speciale per i dispositivi mobili"
 
-#: mod/admin.php:638
+#: mod/admin.php:815
 msgid "No community page"
 msgstr "Nessuna pagina Comunità"
 
-#: mod/admin.php:639
+#: mod/admin.php:816
 msgid "Public postings from users of this site"
 msgstr "Messaggi pubblici dagli utenti di questo sito"
 
-#: mod/admin.php:640
+#: mod/admin.php:817
 msgid "Global community page"
 msgstr "Pagina Comunità globale"
 
-#: mod/admin.php:646
+#: mod/admin.php:823
 msgid "At post arrival"
 msgstr "All'arrivo di un messaggio"
 
-#: mod/admin.php:647 include/contact_selectors.php:56
+#: mod/admin.php:824 include/contact_selectors.php:56
 msgid "Frequently"
 msgstr "Frequentemente"
 
-#: mod/admin.php:648 include/contact_selectors.php:57
+#: mod/admin.php:825 include/contact_selectors.php:57
 msgid "Hourly"
 msgstr "Ogni ora"
 
-#: mod/admin.php:649 include/contact_selectors.php:58
+#: mod/admin.php:826 include/contact_selectors.php:58
 msgid "Twice daily"
 msgstr "Due volte al dì"
 
-#: mod/admin.php:650 include/contact_selectors.php:59
+#: mod/admin.php:827 include/contact_selectors.php:59
 msgid "Daily"
 msgstr "Giornalmente"
 
-#: mod/admin.php:656
+#: mod/admin.php:833
 msgid "Users, Global Contacts"
 msgstr "Utenti, Contatti Globali"
 
-#: mod/admin.php:657
+#: mod/admin.php:834
 msgid "Users, Global Contacts/fallback"
 msgstr "Utenti, Contatti Globali/fallback"
 
-#: mod/admin.php:661
+#: mod/admin.php:838
 msgid "One month"
 msgstr "Un mese"
 
-#: mod/admin.php:662
+#: mod/admin.php:839
 msgid "Three months"
 msgstr "Tre mesi"
 
-#: mod/admin.php:663
+#: mod/admin.php:840
 msgid "Half a year"
 msgstr "Sei mesi"
 
-#: mod/admin.php:664
+#: mod/admin.php:841
 msgid "One year"
 msgstr "Un anno"
 
-#: mod/admin.php:669
+#: mod/admin.php:846
 msgid "Multi user instance"
 msgstr "Istanza multi utente"
 
-#: mod/admin.php:692
+#: mod/admin.php:869
 msgid "Closed"
 msgstr "Chiusa"
 
-#: mod/admin.php:693
+#: mod/admin.php:870
 msgid "Requires approval"
 msgstr "Richiede l'approvazione"
 
-#: mod/admin.php:694
+#: mod/admin.php:871
 msgid "Open"
 msgstr "Aperta"
 
-#: mod/admin.php:698
+#: mod/admin.php:875
 msgid "No SSL policy, links will track page SSL state"
 msgstr "Nessuna gestione SSL, i link seguiranno lo stato SSL della pagina"
 
-#: mod/admin.php:699
+#: mod/admin.php:876
 msgid "Force all links to use SSL"
 msgstr "Forza tutti i linki ad usare SSL"
 
-#: mod/admin.php:700
+#: mod/admin.php:877
 msgid "Self-signed certificate, use SSL for local links only (discouraged)"
 msgstr "Certificato auto-firmato, usa SSL solo per i link locali (sconsigliato)"
 
-#: mod/admin.php:712 mod/admin.php:1271 mod/admin.php:1507 mod/admin.php:1595
-#: mod/settings.php:650 mod/settings.php:760 mod/settings.php:804
-#: mod/settings.php:873 mod/settings.php:960 mod/settings.php:1195
+#: mod/admin.php:889 mod/admin.php:1477 mod/admin.php:1725 mod/admin.php:1793
+#: mod/admin.php:1942 mod/settings.php:659 mod/settings.php:769
+#: mod/settings.php:813 mod/settings.php:882 mod/settings.php:969
+#: mod/settings.php:1204
 msgid "Save Settings"
 msgstr "Salva Impostazioni"
 
-#: mod/admin.php:713 mod/register.php:263
+#: mod/admin.php:890 mod/register.php:263
 msgid "Registration"
 msgstr "Registrazione"
 
-#: mod/admin.php:714
+#: mod/admin.php:891
 msgid "File upload"
 msgstr "Caricamento file"
 
-#: mod/admin.php:715
+#: mod/admin.php:892
 msgid "Policies"
 msgstr "Politiche"
 
-#: mod/admin.php:716
+#: mod/admin.php:893
 msgid "Advanced"
 msgstr "Avanzate"
 
-#: mod/admin.php:717
+#: mod/admin.php:894
 msgid "Auto Discovered Contact Directory"
 msgstr "Elenco Contatti Scoperto Automaticamente"
 
-#: mod/admin.php:718
+#: mod/admin.php:895
 msgid "Performance"
 msgstr "Performance"
 
-#: mod/admin.php:719
+#: mod/admin.php:896
 msgid ""
 "Relocate - WARNING: advanced function. Could make this server unreachable."
 msgstr "Trasloca - ATTENZIONE: funzione avanzata! Puo' rendere questo server irraggiungibile."
 
-#: mod/admin.php:722
+#: mod/admin.php:899
 msgid "Site name"
 msgstr "Nome del sito"
 
-#: mod/admin.php:723
+#: mod/admin.php:900
 msgid "Host name"
 msgstr "Nome host"
 
-#: mod/admin.php:724
+#: mod/admin.php:901
 msgid "Sender Email"
 msgstr "Mittente email"
 
-#: mod/admin.php:724
+#: mod/admin.php:901
 msgid ""
 "The email address your server shall use to send notification emails from."
 msgstr "L'indirizzo email che il tuo server dovrà usare per inviare notifiche via email."
 
-#: mod/admin.php:725
+#: mod/admin.php:902
 msgid "Banner/Logo"
 msgstr "Banner/Logo"
 
-#: mod/admin.php:726
+#: mod/admin.php:903
 msgid "Shortcut icon"
 msgstr "Icona shortcut"
 
-#: mod/admin.php:726
+#: mod/admin.php:903
 msgid "Link to an icon that will be used for browsers."
 msgstr "Link verso un'icona che verrà usata dai browsers."
 
-#: mod/admin.php:727
+#: mod/admin.php:904
 msgid "Touch icon"
 msgstr "Icona touch"
 
-#: mod/admin.php:727
+#: mod/admin.php:904
 msgid "Link to an icon that will be used for tablets and mobiles."
 msgstr "Link verso un'icona che verrà usata dai tablet e i telefonini."
 
-#: mod/admin.php:728
+#: mod/admin.php:905
 msgid "Additional Info"
 msgstr "Informazioni aggiuntive"
 
-#: mod/admin.php:728
+#: mod/admin.php:905
 #, php-format
 msgid ""
 "For public servers: you can add additional information here that will be "
 "listed at %s/siteinfo."
 msgstr "Per server pubblici: puoi aggiungere informazioni extra che verrano mostrate su %s/siteinfo."
 
-#: mod/admin.php:729
+#: mod/admin.php:906
 msgid "System language"
 msgstr "Lingua di sistema"
 
-#: mod/admin.php:730
+#: mod/admin.php:907
 msgid "System theme"
 msgstr "Tema di sistema"
 
-#: mod/admin.php:730
+#: mod/admin.php:907
 msgid ""
 "Default system theme - may be over-ridden by user profiles - <a href='#' "
 "id='cnftheme'>change theme settings</a>"
 msgstr "Tema di sistema - puo' essere sovrascritto dalle impostazioni utente - <a href='#' id='cnftheme'>cambia le impostazioni del tema</a>"
 
-#: mod/admin.php:731
+#: mod/admin.php:908
 msgid "Mobile system theme"
 msgstr "Tema mobile di sistema"
 
-#: mod/admin.php:731
+#: mod/admin.php:908
 msgid "Theme for mobile devices"
 msgstr "Tema per dispositivi mobili"
 
-#: mod/admin.php:732
+#: mod/admin.php:909
 msgid "SSL link policy"
 msgstr "Gestione link SSL"
 
-#: mod/admin.php:732
+#: mod/admin.php:909
 msgid "Determines whether generated links should be forced to use SSL"
 msgstr "Determina se i link generati devono essere forzati a usare SSL"
 
-#: mod/admin.php:733
+#: mod/admin.php:910
 msgid "Force SSL"
 msgstr "Forza SSL"
 
-#: mod/admin.php:733
+#: mod/admin.php:910
 msgid ""
 "Force all Non-SSL requests to SSL - Attention: on some systems it could lead"
 " to endless loops."
 msgstr "Forza tutte le richieste non SSL su SSL - Attenzione: su alcuni sistemi puo' portare a loop senza fine"
 
-#: mod/admin.php:734
+#: mod/admin.php:911
 msgid "Old style 'Share'"
 msgstr "Ricondivisione vecchio stile"
 
-#: mod/admin.php:734
+#: mod/admin.php:911
 msgid "Deactivates the bbcode element 'share' for repeating items."
 msgstr "Disattiva l'elemento bbcode 'share' con elementi ripetuti"
 
-#: mod/admin.php:735
+#: mod/admin.php:912
 msgid "Hide help entry from navigation menu"
 msgstr "Nascondi la voce 'Guida' dal menu di navigazione"
 
-#: mod/admin.php:735
+#: mod/admin.php:912
 msgid ""
 "Hides the menu entry for the Help pages from the navigation menu. You can "
 "still access it calling /help directly."
 msgstr "Nasconde la voce per le pagine della guida dal menu di navigazione. E' comunque possibile accedervi richiamando /help direttamente."
 
-#: mod/admin.php:736
+#: mod/admin.php:913
 msgid "Single user instance"
 msgstr "Instanza a singolo utente"
 
-#: mod/admin.php:736
+#: mod/admin.php:913
 msgid "Make this instance multi-user or single-user for the named user"
 msgstr "Rendi questa istanza multi utente o a singolo utente per l'utente selezionato"
 
-#: mod/admin.php:737
+#: mod/admin.php:914
 msgid "Maximum image size"
 msgstr "Massima dimensione immagini"
 
-#: mod/admin.php:737
+#: mod/admin.php:914
 msgid ""
 "Maximum size in bytes of uploaded images. Default is 0, which means no "
 "limits."
 msgstr "Massima dimensione in byte delle immagini caricate. Il default è 0, cioè nessun limite."
 
-#: mod/admin.php:738
+#: mod/admin.php:915
 msgid "Maximum image length"
 msgstr "Massima lunghezza immagine"
 
-#: mod/admin.php:738
+#: mod/admin.php:915
 msgid ""
 "Maximum length in pixels of the longest side of uploaded images. Default is "
 "-1, which means no limits."
 msgstr "Massima lunghezza in pixel del lato più lungo delle immagini caricate. Predefinito a -1, ovvero nessun limite."
 
-#: mod/admin.php:739
+#: mod/admin.php:916
 msgid "JPEG image quality"
 msgstr "Qualità immagini JPEG"
 
-#: mod/admin.php:739
+#: mod/admin.php:916
 msgid ""
 "Uploaded JPEGS will be saved at this quality setting [0-100]. Default is "
 "100, which is full quality."
 msgstr "Le immagini JPEG caricate verranno salvate con questa qualità [0-100]. Predefinito è 100, ovvero qualità piena."
 
-#: mod/admin.php:741
+#: mod/admin.php:918
 msgid "Register policy"
 msgstr "Politica di registrazione"
 
-#: mod/admin.php:742
+#: mod/admin.php:919
 msgid "Maximum Daily Registrations"
 msgstr "Massime registrazioni giornaliere"
 
-#: mod/admin.php:742
+#: mod/admin.php:919
 msgid ""
 "If registration is permitted above, this sets the maximum number of new user"
 " registrations to accept per day.  If register is set to closed, this "
 "setting has no effect."
 msgstr "Se la registrazione è permessa, qui si definisce il massimo numero di nuovi utenti registrati da accettare giornalmente. Se la registrazione è chiusa, questa impostazione non ha effetto."
 
-#: mod/admin.php:743
+#: mod/admin.php:920
 msgid "Register text"
 msgstr "Testo registrazione"
 
-#: mod/admin.php:743
+#: mod/admin.php:920
 msgid "Will be displayed prominently on the registration page."
 msgstr "Sarà mostrato ben visibile nella pagina di registrazione."
 
-#: mod/admin.php:744
+#: mod/admin.php:921
 msgid "Accounts abandoned after x days"
 msgstr "Account abbandonati dopo x giorni"
 
-#: mod/admin.php:744
+#: mod/admin.php:921
 msgid ""
 "Will not waste system resources polling external sites for abandonded "
 "accounts. Enter 0 for no time limit."
 msgstr "Non spreca risorse di sistema controllando siti esterni per gli account abbandonati. Immettere 0 per nessun limite di tempo."
 
-#: mod/admin.php:745
+#: mod/admin.php:922
 msgid "Allowed friend domains"
 msgstr "Domini amici consentiti"
 
-#: mod/admin.php:745
+#: mod/admin.php:922
 msgid ""
 "Comma separated list of domains which are allowed to establish friendships "
 "with this site. Wildcards are accepted. Empty to allow any domains"
 msgstr "Elenco separato da virglola dei domini che possono stabilire amicizie con questo sito. Sono accettati caratteri jolly. Lascalo vuoto per accettare qualsiasi dominio."
 
-#: mod/admin.php:746
+#: mod/admin.php:923
 msgid "Allowed email domains"
 msgstr "Domini email consentiti"
 
-#: mod/admin.php:746
+#: mod/admin.php:923
 msgid ""
 "Comma separated list of domains which are allowed in email addresses for "
 "registrations to this site. Wildcards are accepted. Empty to allow any "
 "domains"
 msgstr "Elenco separato da virgola dei domini permessi come indirizzi email in fase di registrazione a questo sito. Sono accettati caratteri jolly. Lascalo vuoto per accettare qualsiasi dominio."
 
-#: mod/admin.php:747
+#: mod/admin.php:924
 msgid "Block public"
 msgstr "Blocca pagine pubbliche"
 
-#: mod/admin.php:747
+#: mod/admin.php:924
 msgid ""
 "Check to block public access to all otherwise public personal pages on this "
 "site unless you are currently logged in."
 msgstr "Seleziona per bloccare l'accesso pubblico a tutte le pagine personali di questo sito, a meno di essere loggato."
 
-#: mod/admin.php:748
+#: mod/admin.php:925
 msgid "Force publish"
 msgstr "Forza publicazione"
 
-#: mod/admin.php:748
+#: mod/admin.php:925
 msgid ""
 "Check to force all profiles on this site to be listed in the site directory."
 msgstr "Seleziona per forzare tutti i profili di questo sito ad essere compresi  nell'elenco di questo sito."
 
-#: mod/admin.php:749
+#: mod/admin.php:926
 msgid "Global directory URL"
 msgstr "URL della directory globale"
 
-#: mod/admin.php:749
+#: mod/admin.php:926
 msgid ""
 "URL to the global directory. If this is not set, the global directory is "
 "completely unavailable to the application."
 msgstr "URL dell'elenco globale. Se vuoto, l'elenco globale sarà completamente disabilitato."
 
-#: mod/admin.php:750
+#: mod/admin.php:927
 msgid "Allow threaded items"
 msgstr "Permetti commenti nidificati"
 
-#: mod/admin.php:750
+#: mod/admin.php:927
 msgid "Allow infinite level threading for items on this site."
 msgstr "Permette un infinito livello di nidificazione dei commenti su questo sito."
 
-#: mod/admin.php:751
+#: mod/admin.php:928
 msgid "Private posts by default for new users"
 msgstr "Post privati di default per i nuovi utenti"
 
-#: mod/admin.php:751
+#: mod/admin.php:928
 msgid ""
 "Set default post permissions for all new members to the default privacy "
 "group rather than public."
 msgstr "Imposta i permessi predefiniti dei post per tutti i nuovi utenti come privati per il gruppo predefinito, invece che pubblici."
 
-#: mod/admin.php:752
+#: mod/admin.php:929
 msgid "Don't include post content in email notifications"
 msgstr "Non includere il contenuto dei post nelle notifiche via email"
 
-#: mod/admin.php:752
+#: mod/admin.php:929
 msgid ""
 "Don't include the content of a post/comment/private message/etc. in the "
 "email notifications that are sent out from this site, as a privacy measure."
 msgstr "Non include il contenuti del post/commento/messaggio privato/etc. nelle notifiche email che sono inviate da questo sito, per privacy"
 
-#: mod/admin.php:753
+#: mod/admin.php:930
 msgid "Disallow public access to addons listed in the apps menu."
 msgstr "Disabilita l'accesso pubblico ai plugin raccolti nel menu apps."
 
-#: mod/admin.php:753
+#: mod/admin.php:930
 msgid ""
 "Checking this box will restrict addons listed in the apps menu to members "
 "only."
 msgstr "Selezionando questo box si limiterà ai soli membri l'accesso agli addon nel menu applicazioni"
 
-#: mod/admin.php:754
+#: mod/admin.php:931
 msgid "Don't embed private images in posts"
 msgstr "Non inglobare immagini private nei post"
 
-#: mod/admin.php:754
+#: mod/admin.php:931
 msgid ""
 "Don't replace locally-hosted private photos in posts with an embedded copy "
 "of the image. This means that contacts who receive posts containing private "
@@ -2505,218 +2489,228 @@ msgid ""
 "while."
 msgstr "Non sostituire le foto locali nei post con una copia incorporata dell'immagine. Questo significa che i contatti che riceveranno i post contenenti foto private dovranno autenticarsi e caricare ogni immagine, cosa che puo' richiedere un po' di tempo."
 
-#: mod/admin.php:755
+#: mod/admin.php:932
 msgid "Allow Users to set remote_self"
 msgstr "Permetti agli utenti di impostare 'io remoto'"
 
-#: mod/admin.php:755
+#: mod/admin.php:932
 msgid ""
 "With checking this, every user is allowed to mark every contact as a "
 "remote_self in the repair contact dialog. Setting this flag on a contact "
 "causes mirroring every posting of that contact in the users stream."
 msgstr "Selezionando questo, a tutti gli utenti sarà permesso di impostare qualsiasi contatto come 'io remoto' nella pagina di modifica del contatto. Impostare questa opzione fa si che tutti i messaggi di quel contatto vengano ripetuti nello stream del'utente."
 
-#: mod/admin.php:756
+#: mod/admin.php:933
 msgid "Block multiple registrations"
 msgstr "Blocca registrazioni multiple"
 
-#: mod/admin.php:756
+#: mod/admin.php:933
 msgid "Disallow users to register additional accounts for use as pages."
 msgstr "Non permette all'utente di registrare account extra da usare come pagine."
 
-#: mod/admin.php:757
+#: mod/admin.php:934
 msgid "OpenID support"
 msgstr "Supporto OpenID"
 
-#: mod/admin.php:757
+#: mod/admin.php:934
 msgid "OpenID support for registration and logins."
 msgstr "Supporta OpenID per la registrazione e il login"
 
-#: mod/admin.php:758
+#: mod/admin.php:935
 msgid "Fullname check"
 msgstr "Controllo nome completo"
 
-#: mod/admin.php:758
+#: mod/admin.php:935
 msgid ""
 "Force users to register with a space between firstname and lastname in Full "
 "name, as an antispam measure"
 msgstr "Forza gli utenti a registrarsi con uno spazio tra il nome e il cognome in \"Nome completo\", come misura antispam"
 
-#: mod/admin.php:759
+#: mod/admin.php:936
 msgid "UTF-8 Regular expressions"
 msgstr "Espressioni regolari UTF-8"
 
-#: mod/admin.php:759
+#: mod/admin.php:936
 msgid "Use PHP UTF8 regular expressions"
 msgstr "Usa le espressioni regolari PHP in UTF8"
 
-#: mod/admin.php:760
+#: mod/admin.php:937
 msgid "Community Page Style"
 msgstr "Stile pagina Comunità"
 
-#: mod/admin.php:760
+#: mod/admin.php:937
 msgid ""
 "Type of community page to show. 'Global community' shows every public "
 "posting from an open distributed network that arrived on this server."
 msgstr "Tipo di pagina Comunità da mostrare. 'Comunità Globale' mostra tutti i messaggi pubblici arrivati su questo server da network aperti distribuiti."
 
-#: mod/admin.php:761
+#: mod/admin.php:938
 msgid "Posts per user on community page"
 msgstr "Messaggi per utente nella pagina Comunità"
 
-#: mod/admin.php:761
+#: mod/admin.php:938
 msgid ""
 "The maximum number of posts per user on the community page. (Not valid for "
 "'Global Community')"
 msgstr "Il numero massimo di messaggi per utente mostrato nella pagina Comuntà (non valido per 'Comunità globale')"
 
-#: mod/admin.php:762
+#: mod/admin.php:939
 msgid "Enable OStatus support"
 msgstr "Abilita supporto OStatus"
 
-#: mod/admin.php:762
+#: mod/admin.php:939
 msgid ""
 "Provide built-in OStatus (StatusNet, GNU Social etc.) compatibility. All "
 "communications in OStatus are public, so privacy warnings will be "
 "occasionally displayed."
 msgstr "Fornisce la compatibilità integrata a OStatus (StatusNet, Gnu Social, etc.). Tutte le comunicazioni su OStatus sono pubbliche, quindi un avviso di privacy verrà mostrato occasionalmente."
 
-#: mod/admin.php:763
+#: mod/admin.php:940
 msgid "OStatus conversation completion interval"
 msgstr "Intervallo completamento conversazioni OStatus"
 
-#: mod/admin.php:763
+#: mod/admin.php:940
 msgid ""
 "How often shall the poller check for new entries in OStatus conversations? "
 "This can be a very ressource task."
 msgstr "quanto spesso il poller deve controllare se esistono nuovi commenti in una conversazione OStatus? Questo è un lavoro che puo' richiedere molte risorse."
 
-#: mod/admin.php:764
+#: mod/admin.php:941
 msgid "OStatus support can only be enabled if threading is enabled."
 msgstr "Il supporto OStatus puo' essere abilitato solo se è abilitato il threading."
 
-#: mod/admin.php:766
+#: mod/admin.php:943
 msgid ""
 "Diaspora support can't be enabled because Friendica was installed into a sub"
 " directory."
 msgstr "Il supporto a Diaspora non puo' essere abilitato perchè Friendica è stato installato in una sotto directory."
 
-#: mod/admin.php:767
+#: mod/admin.php:944
 msgid "Enable Diaspora support"
 msgstr "Abilita il supporto a Diaspora"
 
-#: mod/admin.php:767
+#: mod/admin.php:944
 msgid "Provide built-in Diaspora network compatibility."
 msgstr "Fornisce compatibilità con il network Diaspora."
 
-#: mod/admin.php:768
+#: mod/admin.php:945
 msgid "Only allow Friendica contacts"
 msgstr "Permetti solo contatti Friendica"
 
-#: mod/admin.php:768
+#: mod/admin.php:945
 msgid ""
 "All contacts must use Friendica protocols. All other built-in communication "
 "protocols disabled."
 msgstr "Tutti i contatti devono usare il protocollo di Friendica. Tutti gli altri protocolli sono disabilitati."
 
-#: mod/admin.php:769
+#: mod/admin.php:946
 msgid "Verify SSL"
 msgstr "Verifica SSL"
 
-#: mod/admin.php:769
+#: mod/admin.php:946
 msgid ""
 "If you wish, you can turn on strict certificate checking. This will mean you"
 " cannot connect (at all) to self-signed SSL sites."
 msgstr "Se vuoi, puoi abilitare il controllo rigoroso dei certificati.Questo significa che non potrai collegarti (del tutto) con siti con certificati SSL auto-firmati."
 
-#: mod/admin.php:770
+#: mod/admin.php:947
 msgid "Proxy user"
 msgstr "Utente Proxy"
 
-#: mod/admin.php:771
+#: mod/admin.php:948
 msgid "Proxy URL"
 msgstr "URL Proxy"
 
-#: mod/admin.php:772
+#: mod/admin.php:949
 msgid "Network timeout"
 msgstr "Timeout rete"
 
-#: mod/admin.php:772
+#: mod/admin.php:949
 msgid "Value is in seconds. Set to 0 for unlimited (not recommended)."
 msgstr "Valore in secondi. Imposta a 0 per illimitato (non raccomandato)."
 
-#: mod/admin.php:773
+#: mod/admin.php:950
 msgid "Delivery interval"
 msgstr "Intervallo di invio"
 
-#: mod/admin.php:773
+#: mod/admin.php:950
 msgid ""
 "Delay background delivery processes by this many seconds to reduce system "
 "load. Recommend: 4-5 for shared hosts, 2-3 for virtual private servers. 0-1 "
 "for large dedicated servers."
 msgstr "Ritarda il processo di invio in background  di n secondi per ridurre il carico di sistema. Raccomandato:  4-5 per host condivisit, 2-3 per VPS. 0-1 per grandi server dedicati."
 
-#: mod/admin.php:774
+#: mod/admin.php:951
 msgid "Poll interval"
 msgstr "Intervallo di poll"
 
-#: mod/admin.php:774
+#: mod/admin.php:951
 msgid ""
 "Delay background polling processes by this many seconds to reduce system "
 "load. If 0, use delivery interval."
 msgstr "Ritarda il processo di poll in background di n secondi per ridurre il carico di sistema. Se 0, usa l'intervallo di invio."
 
-#: mod/admin.php:775
+#: mod/admin.php:952
 msgid "Maximum Load Average"
 msgstr "Massimo carico medio"
 
-#: mod/admin.php:775
+#: mod/admin.php:952
 msgid ""
 "Maximum system load before delivery and poll processes are deferred - "
 "default 50."
 msgstr "Massimo carico di sistema prima che i processi di invio e di poll siano ritardati. Predefinito a 50."
 
-#: mod/admin.php:776
+#: mod/admin.php:953
 msgid "Maximum Load Average (Frontend)"
 msgstr "Media Massimo Carico (Frontend)"
 
-#: mod/admin.php:776
+#: mod/admin.php:953
 msgid "Maximum system load before the frontend quits service - default 50."
 msgstr "Massimo carico di sistema prima che il frontend fermi il servizio - default 50."
 
-#: mod/admin.php:777
+#: mod/admin.php:954
 msgid "Maximum table size for optimization"
 msgstr "Dimensione massima della tabella per l'ottimizzazione"
 
-#: mod/admin.php:777
+#: mod/admin.php:954
 msgid ""
 "Maximum table size (in MB) for the automatic optimization - default 100 MB. "
 "Enter -1 to disable it."
 msgstr "La dimensione massima (in MB) per l'ottimizzazione automatica - default 100 MB. Inserisci -1 per disabilitarlo."
 
-#: mod/admin.php:779
+#: mod/admin.php:955
+msgid "Minimum level of fragmentation"
+msgstr ""
+
+#: mod/admin.php:955
+msgid ""
+"Minimum fragmenation level to start the automatic optimization - default "
+"value is 30%."
+msgstr ""
+
+#: mod/admin.php:957
 msgid "Periodical check of global contacts"
 msgstr "Check periodico dei contatti globali"
 
-#: mod/admin.php:779
+#: mod/admin.php:957
 msgid ""
 "If enabled, the global contacts are checked periodically for missing or "
 "outdated data and the vitality of the contacts and servers."
 msgstr "Se abilitato, i contatti globali sono controllati periodicamente per verificare dati mancanti o sorpassati e la vitaltà dei contatti e dei server."
 
-#: mod/admin.php:780
+#: mod/admin.php:958
 msgid "Days between requery"
 msgstr "Giorni tra le richieste"
 
-#: mod/admin.php:780
+#: mod/admin.php:958
 msgid "Number of days after which a server is requeried for his contacts."
 msgstr "Numero di giorni dopo i quali al server vengono richiesti i suoi contatti."
 
-#: mod/admin.php:781
+#: mod/admin.php:959
 msgid "Discover contacts from other servers"
 msgstr "Trova contatti dagli altri server"
 
-#: mod/admin.php:781
+#: mod/admin.php:959
 msgid ""
 "Periodically query other servers for contacts. You can choose between "
 "'users': the users on the remote system, 'Global Contacts': active contacts "
@@ -2726,32 +2720,32 @@ msgid ""
 "Global Contacts'."
 msgstr "Richiede periodicamente contatti agli altri server. Puoi scegliere tra 'utenti', gli uenti sul sistema remoto, o 'contatti globali', i contatti attivi che sono conosciuti dal sistema. Il fallback è pensato per i server Redmatrix e i vecchi server Friendica, dove i contatti globali non sono disponibili. Il fallback incrementa il carico di sistema, per cui l'impostazione consigliata è \"Utenti, Contatti Globali\"."
 
-#: mod/admin.php:782
+#: mod/admin.php:960
 msgid "Timeframe for fetching global contacts"
 msgstr "Termine per il recupero contatti globali"
 
-#: mod/admin.php:782
+#: mod/admin.php:960
 msgid ""
 "When the discovery is activated, this value defines the timeframe for the "
 "activity of the global contacts that are fetched from other servers."
 msgstr "Quando si attiva la scoperta, questo valore definisce il periodo di tempo per l'attività dei contatti globali che vengono prelevati da altri server."
 
-#: mod/admin.php:783
+#: mod/admin.php:961
 msgid "Search the local directory"
 msgstr "Cerca la directory locale"
 
-#: mod/admin.php:783
+#: mod/admin.php:961
 msgid ""
 "Search the local directory instead of the global directory. When searching "
 "locally, every search will be executed on the global directory in the "
 "background. This improves the search results when the search is repeated."
 msgstr "Cerca nella directory locale invece che nella directory globale. Durante la ricerca a livello locale, ogni ricerca verrà eseguita sulla directory globale in background. Ciò migliora i risultati della ricerca quando la ricerca viene ripetuta."
 
-#: mod/admin.php:785
+#: mod/admin.php:963
 msgid "Publish server information"
 msgstr "Pubblica informazioni server"
 
-#: mod/admin.php:785
+#: mod/admin.php:963
 msgid ""
 "If enabled, general server and usage data will be published. The data "
 "contains the name and version of the server, number of users with public "
@@ -2759,205 +2753,205 @@ msgid ""
 " href='http://the-federation.info/'>the-federation.info</a> for details."
 msgstr "Se abilitata, saranno pubblicati i dati generali del server e i dati di utilizzo. I dati contengono il nome e la versione del server, il numero di utenti con profili pubblici, numero dei posti e dei protocolli e connettori attivati. Per informazioni, vedere <a href='http://the-federation.info/'> the-federation.info </a>."
 
-#: mod/admin.php:787
+#: mod/admin.php:965
 msgid "Use MySQL full text engine"
 msgstr "Usa il motore MySQL full text"
 
-#: mod/admin.php:787
+#: mod/admin.php:965
 msgid ""
 "Activates the full text engine. Speeds up search - but can only search for "
 "four and more characters."
 msgstr "Attiva il motore full text. Velocizza la ricerca, ma puo' cercare solo per quattro o più caratteri."
 
-#: mod/admin.php:788
+#: mod/admin.php:966
 msgid "Suppress Language"
 msgstr "Disattiva lingua"
 
-#: mod/admin.php:788
+#: mod/admin.php:966
 msgid "Suppress language information in meta information about a posting."
 msgstr "Disattiva le informazioni sulla lingua nei meta di un post."
 
-#: mod/admin.php:789
+#: mod/admin.php:967
 msgid "Suppress Tags"
 msgstr "Sopprimi Tags"
 
-#: mod/admin.php:789
+#: mod/admin.php:967
 msgid "Suppress showing a list of hashtags at the end of the posting."
 msgstr "Non mostra la lista di hashtag in coda al messaggio"
 
-#: mod/admin.php:790
+#: mod/admin.php:968
 msgid "Path to item cache"
 msgstr "Percorso cache elementi"
 
-#: mod/admin.php:790
+#: mod/admin.php:968
 msgid "The item caches buffers generated bbcode and external images."
 msgstr "La cache degli elementi memorizza il bbcode generato e le immagini esterne."
 
-#: mod/admin.php:791
+#: mod/admin.php:969
 msgid "Cache duration in seconds"
 msgstr "Durata della cache in secondi"
 
-#: mod/admin.php:791
+#: mod/admin.php:969
 msgid ""
 "How long should the cache files be hold? Default value is 86400 seconds (One"
 " day). To disable the item cache, set the value to -1."
 msgstr "Quanto a lungo devono essere mantenuti i file di cache? Il valore predefinito è 86400 secondi (un giorno). Per disabilitare la cache, imposta il valore a -1."
 
-#: mod/admin.php:792
+#: mod/admin.php:970
 msgid "Maximum numbers of comments per post"
 msgstr "Numero massimo di commenti per post"
 
-#: mod/admin.php:792
+#: mod/admin.php:970
 msgid "How much comments should be shown for each post? Default value is 100."
 msgstr "Quanti commenti devono essere mostrati per ogni post? Default : 100."
 
-#: mod/admin.php:793
+#: mod/admin.php:971
 msgid "Path for lock file"
 msgstr "Percorso al file di lock"
 
-#: mod/admin.php:793
+#: mod/admin.php:971
 msgid ""
 "The lock file is used to avoid multiple pollers at one time. Only define a "
 "folder here."
 msgstr "Il file di lock è usato per evitare l'avvio di poller multipli allo stesso tempo. Inserisci solo la cartella, qui."
 
-#: mod/admin.php:794
+#: mod/admin.php:972
 msgid "Temp path"
 msgstr "Percorso file temporanei"
 
-#: mod/admin.php:794
+#: mod/admin.php:972
 msgid ""
 "If you have a restricted system where the webserver can't access the system "
 "temp path, enter another path here."
 msgstr "Se si dispone di un sistema ristretto in cui il server web non può accedere al percorso temporaneo di sistema, inserire un altro percorso qui."
 
-#: mod/admin.php:795
+#: mod/admin.php:973
 msgid "Base path to installation"
 msgstr "Percorso base all'installazione"
 
-#: mod/admin.php:795
+#: mod/admin.php:973
 msgid ""
 "If the system cannot detect the correct path to your installation, enter the"
 " correct path here. This setting should only be set if you are using a "
 "restricted system and symbolic links to your webroot."
 msgstr "Se il sistema non è in grado di rilevare il percorso corretto per l'installazione, immettere il percorso corretto qui. Questa impostazione deve essere inserita solo se si utilizza un sistema limitato e/o collegamenti simbolici al tuo webroot."
 
-#: mod/admin.php:796
+#: mod/admin.php:974
 msgid "Disable picture proxy"
 msgstr "Disabilita il proxy immagini"
 
-#: mod/admin.php:796
+#: mod/admin.php:974
 msgid ""
 "The picture proxy increases performance and privacy. It shouldn't be used on"
 " systems with very low bandwith."
 msgstr "Il proxy immagini aumenta le performace e la privacy. Non dovrebbe essere usato su server con poca banda disponibile."
 
-#: mod/admin.php:797
+#: mod/admin.php:975
 msgid "Enable old style pager"
 msgstr "Abilita la paginazione vecchio stile"
 
-#: mod/admin.php:797
+#: mod/admin.php:975
 msgid ""
 "The old style pager has page numbers but slows down massively the page "
 "speed."
 msgstr "La paginazione vecchio stile mostra i numeri delle pagine, ma rallenta la velocità di caricamento della pagina."
 
-#: mod/admin.php:798
+#: mod/admin.php:976
 msgid "Only search in tags"
 msgstr "Cerca solo nei tag"
 
-#: mod/admin.php:798
+#: mod/admin.php:976
 msgid "On large systems the text search can slow down the system extremely."
 msgstr "Su server con molti dati, la ricerca nel testo può estremamente rallentare il sistema."
 
-#: mod/admin.php:800
+#: mod/admin.php:978
 msgid "New base url"
 msgstr "Nuovo url base"
 
-#: mod/admin.php:800
+#: mod/admin.php:978
 msgid ""
 "Change base url for this server. Sends relocate message to all DFRN contacts"
 " of all users."
 msgstr "Cambia l'url base di questo server. Invia il messaggio di trasloco a tutti i contatti DFRN di tutti gli utenti."
 
-#: mod/admin.php:802
+#: mod/admin.php:980
 msgid "RINO Encryption"
 msgstr "Crittografia RINO"
 
-#: mod/admin.php:802
+#: mod/admin.php:980
 msgid "Encryption layer between nodes."
 msgstr "Crittografia delle comunicazioni tra nodi."
 
-#: mod/admin.php:803
+#: mod/admin.php:981
 msgid "Embedly API key"
 msgstr "Embedly API key"
 
-#: mod/admin.php:803
+#: mod/admin.php:981
 msgid ""
 "<a href='http://embed.ly'>Embedly</a> is used to fetch additional data for "
 "web pages. This is an optional parameter."
 msgstr "<a href='http://embed.ly'>Embedly</a> è usato per recuperate informazioni addizionali dalle pagine web. Questo parametro è opzionale."
 
-#: mod/admin.php:821
+#: mod/admin.php:1010
 msgid "Update has been marked successful"
 msgstr "L'aggiornamento è stato segnato come  di successo"
 
-#: mod/admin.php:829
+#: mod/admin.php:1018
 #, php-format
 msgid "Database structure update %s was successfully applied."
 msgstr "Aggiornamento struttura database %s applicata con successo."
 
-#: mod/admin.php:832
+#: mod/admin.php:1021
 #, php-format
 msgid "Executing of database structure update %s failed with error: %s"
 msgstr "Aggiornamento struttura database %s fallita con errore: %s"
 
-#: mod/admin.php:844
+#: mod/admin.php:1033
 #, php-format
 msgid "Executing %s failed with error: %s"
 msgstr "Esecuzione di %s fallita con errore: %s"
 
-#: mod/admin.php:847
+#: mod/admin.php:1036
 #, php-format
 msgid "Update %s was successfully applied."
 msgstr "L'aggiornamento %s è stato applicato con successo"
 
-#: mod/admin.php:851
+#: mod/admin.php:1040
 #, php-format
 msgid "Update %s did not return a status. Unknown if it succeeded."
 msgstr "L'aggiornamento %s non ha riportato uno stato. Non so se è andato a buon fine."
 
-#: mod/admin.php:853
+#: mod/admin.php:1042
 #, php-format
 msgid "There was no additional update function %s that needed to be called."
 msgstr "Non ci sono altre funzioni di aggiornamento %s da richiamare."
 
-#: mod/admin.php:872
+#: mod/admin.php:1061
 msgid "No failed updates."
 msgstr "Nessun aggiornamento fallito."
 
-#: mod/admin.php:873
+#: mod/admin.php:1062
 msgid "Check database structure"
 msgstr "Controlla struttura database"
 
-#: mod/admin.php:878
+#: mod/admin.php:1067
 msgid "Failed Updates"
 msgstr "Aggiornamenti falliti"
 
-#: mod/admin.php:879
+#: mod/admin.php:1068
 msgid ""
 "This does not include updates prior to 1139, which did not return a status."
 msgstr "Questo non include gli aggiornamenti prima del 1139, che non ritornano lo stato."
 
-#: mod/admin.php:880
+#: mod/admin.php:1069
 msgid "Mark success (if update was manually applied)"
 msgstr "Segna completato (se l'update è stato applicato manualmente)"
 
-#: mod/admin.php:881
+#: mod/admin.php:1070
 msgid "Attempt to execute this update step automatically"
 msgstr "Cerco di eseguire questo aggiornamento in automatico"
 
-#: mod/admin.php:913
+#: mod/admin.php:1102
 #, php-format
 msgid ""
 "\n"
@@ -2965,7 +2959,7 @@ msgid ""
 "\t\t\t\tthe administrator of %2$s has set up an account for you."
 msgstr "\nGentile %1$s,\n    l'amministratore di %2$s ha impostato un account per te."
 
-#: mod/admin.php:916
+#: mod/admin.php:1105
 #, php-format
 msgid ""
 "\n"
@@ -2995,233 +2989,255 @@ msgid ""
 "\t\t\tThank you and welcome to %4$s."
 msgstr "\nI dettagli del tuo utente sono:\n    Indirizzo del sito: %1$s\n    Nome utente: %2$s\n    Password: %3$s\n\nPuoi cambiare la tua password dalla pagina delle impostazioni del tuo account dopo esserti autenticato.\n\nPer favore, prenditi qualche momento per esaminare tutte le impostazioni presenti.\n\nPotresti voler aggiungere qualche informazione di base al tuo profilo predefinito (nella pagina \"Profili\"), così che le altre persone possano trovarti più facilmente.\n\nTi raccomandiamo di inserire il tuo nome completo, aggiungere una foto, aggiungere qualche parola chiave del profilo (molto utili per trovare nuovi contatti), e magari in quale nazione vivi, se non vuoi essere più specifico di così.\n\nNoi rispettiamo appieno la tua privacy, e nessuna di queste informazioni è necessaria o obbligatoria.\nSe sei nuovo e non conosci nessuno qui, possono aiutarti a trovare qualche nuovo e interessante contatto.\n\nGrazie e benvenuto su %4$s"
 
-#: mod/admin.php:948 include/user.php:423
+#: mod/admin.php:1137 include/user.php:423
 #, php-format
 msgid "Registration details for %s"
 msgstr "Dettagli della registrazione di %s"
 
-#: mod/admin.php:960
+#: mod/admin.php:1149
 #, php-format
 msgid "%s user blocked/unblocked"
 msgid_plural "%s users blocked/unblocked"
 msgstr[0] "%s utente bloccato/sbloccato"
 msgstr[1] "%s utenti bloccati/sbloccati"
 
-#: mod/admin.php:967
+#: mod/admin.php:1156
 #, php-format
 msgid "%s user deleted"
 msgid_plural "%s users deleted"
 msgstr[0] "%s utente cancellato"
 msgstr[1] "%s utenti cancellati"
 
-#: mod/admin.php:1006
+#: mod/admin.php:1203
 #, php-format
 msgid "User '%s' deleted"
 msgstr "Utente '%s' cancellato"
 
-#: mod/admin.php:1014
+#: mod/admin.php:1211
 #, php-format
 msgid "User '%s' unblocked"
 msgstr "Utente '%s' sbloccato"
 
-#: mod/admin.php:1014
+#: mod/admin.php:1211
 #, php-format
 msgid "User '%s' blocked"
 msgstr "Utente '%s' bloccato"
 
-#: mod/admin.php:1107
+#: mod/admin.php:1302
 msgid "Add User"
 msgstr "Aggiungi utente"
 
-#: mod/admin.php:1108
+#: mod/admin.php:1303
 msgid "select all"
 msgstr "seleziona tutti"
 
-#: mod/admin.php:1109
+#: mod/admin.php:1304
 msgid "User registrations waiting for confirm"
 msgstr "Richieste di registrazione in attesa di conferma"
 
-#: mod/admin.php:1110
+#: mod/admin.php:1305
 msgid "User waiting for permanent deletion"
 msgstr "Utente in attesa di cancellazione definitiva"
 
-#: mod/admin.php:1111
+#: mod/admin.php:1306
 msgid "Request date"
 msgstr "Data richiesta"
 
-#: mod/admin.php:1111 mod/admin.php:1123 mod/admin.php:1124 mod/admin.php:1139
+#: mod/admin.php:1306 mod/admin.php:1318 mod/admin.php:1319 mod/admin.php:1334
 #: include/contact_selectors.php:79 include/contact_selectors.php:86
 msgid "Email"
 msgstr "Email"
 
-#: mod/admin.php:1112
+#: mod/admin.php:1307
 msgid "No registrations."
 msgstr "Nessuna registrazione."
 
-#: mod/admin.php:1114
+#: mod/admin.php:1309
 msgid "Deny"
 msgstr "Nega"
 
-#: mod/admin.php:1118
+#: mod/admin.php:1313
 msgid "Site admin"
 msgstr "Amministrazione sito"
 
-#: mod/admin.php:1119
+#: mod/admin.php:1314
 msgid "Account expired"
 msgstr "Account scaduto"
 
-#: mod/admin.php:1122
+#: mod/admin.php:1317
 msgid "New User"
 msgstr "Nuovo Utente"
 
-#: mod/admin.php:1123 mod/admin.php:1124
+#: mod/admin.php:1318 mod/admin.php:1319
 msgid "Register date"
 msgstr "Data registrazione"
 
-#: mod/admin.php:1123 mod/admin.php:1124
+#: mod/admin.php:1318 mod/admin.php:1319
 msgid "Last login"
 msgstr "Ultimo accesso"
 
-#: mod/admin.php:1123 mod/admin.php:1124
+#: mod/admin.php:1318 mod/admin.php:1319
 msgid "Last item"
 msgstr "Ultimo elemento"
 
-#: mod/admin.php:1123
+#: mod/admin.php:1318
 msgid "Deleted since"
 msgstr "Rimosso da"
 
-#: mod/admin.php:1124 mod/settings.php:41
+#: mod/admin.php:1319 mod/settings.php:41
 msgid "Account"
 msgstr "Account"
 
-#: mod/admin.php:1126
+#: mod/admin.php:1321
 msgid ""
 "Selected users will be deleted!\\n\\nEverything these users had posted on "
 "this site will be permanently deleted!\\n\\nAre you sure?"
 msgstr "Gli utenti selezionati saranno cancellati!\\n\\nTutto quello che gli utenti hanno inviato su questo sito sarà permanentemente canellato!\\n\\nSei sicuro?"
 
-#: mod/admin.php:1127
+#: mod/admin.php:1322
 msgid ""
 "The user {0} will be deleted!\\n\\nEverything this user has posted on this "
 "site will be permanently deleted!\\n\\nAre you sure?"
 msgstr "L'utente {0} sarà cancellato!\\n\\nTutto quello che ha inviato su questo sito sarà permanentemente cancellato!\\n\\nSei sicuro?"
 
-#: mod/admin.php:1137
+#: mod/admin.php:1332
 msgid "Name of the new user."
 msgstr "Nome del nuovo utente."
 
-#: mod/admin.php:1138
+#: mod/admin.php:1333
 msgid "Nickname"
 msgstr "Nome utente"
 
-#: mod/admin.php:1138
+#: mod/admin.php:1333
 msgid "Nickname of the new user."
 msgstr "Nome utente del nuovo utente."
 
-#: mod/admin.php:1139
+#: mod/admin.php:1334
 msgid "Email address of the new user."
 msgstr "Indirizzo Email del nuovo utente."
 
-#: mod/admin.php:1172
+#: mod/admin.php:1377
 #, php-format
 msgid "Plugin %s disabled."
 msgstr "Plugin %s disabilitato."
 
-#: mod/admin.php:1176
+#: mod/admin.php:1381
 #, php-format
 msgid "Plugin %s enabled."
 msgstr "Plugin %s abilitato."
 
-#: mod/admin.php:1186 mod/admin.php:1410
+#: mod/admin.php:1392 mod/admin.php:1628
 msgid "Disable"
 msgstr "Disabilita"
 
-#: mod/admin.php:1188 mod/admin.php:1412
+#: mod/admin.php:1394 mod/admin.php:1630
 msgid "Enable"
 msgstr "Abilita"
 
-#: mod/admin.php:1211 mod/admin.php:1456
+#: mod/admin.php:1417 mod/admin.php:1675
 msgid "Toggle"
 msgstr "Inverti"
 
-#: mod/admin.php:1219 mod/admin.php:1466
+#: mod/admin.php:1425 mod/admin.php:1684
 msgid "Author: "
 msgstr "Autore: "
 
-#: mod/admin.php:1220 mod/admin.php:1467
+#: mod/admin.php:1426 mod/admin.php:1685
 msgid "Maintainer: "
 msgstr "Manutentore: "
 
-#: mod/admin.php:1272
-#: view/smarty3/compiled/f835364006028b1061f37be121c9bd9db5fa50a9.file.admin_plugins.tpl.php:42
+#: mod/admin.php:1478
 msgid "Reload active plugins"
 msgstr "Ricarica i plugin attivi"
 
-#: mod/admin.php:1370
+#: mod/admin.php:1483
+#, php-format
+msgid ""
+"There are currently no plugins available on your node. You can find the "
+"official plugin repository at %1$s and might find other interesting plugins "
+"in the open plugin registry at %2$s"
+msgstr ""
+
+#: mod/admin.php:1588
 msgid "No themes found."
 msgstr "Nessun tema trovato."
 
-#: mod/admin.php:1448
+#: mod/admin.php:1666
 msgid "Screenshot"
 msgstr "Anteprima"
 
-#: mod/admin.php:1508
+#: mod/admin.php:1726
 msgid "Reload active themes"
 msgstr "Ricarica i temi attivi"
 
-#: mod/admin.php:1512
+#: mod/admin.php:1731
+#, php-format
+msgid "No themes found on the system. They should be paced in %1$s"
+msgstr ""
+
+#: mod/admin.php:1732
 msgid "[Experimental]"
 msgstr "[Sperimentale]"
 
-#: mod/admin.php:1513
+#: mod/admin.php:1733
 msgid "[Unsupported]"
 msgstr "[Non supportato]"
 
-#: mod/admin.php:1540
+#: mod/admin.php:1757
 msgid "Log settings updated."
 msgstr "Impostazioni Log aggiornate."
 
-#: mod/admin.php:1596
+#: mod/admin.php:1794
 msgid "Clear"
 msgstr "Pulisci"
 
-#: mod/admin.php:1602
+#: mod/admin.php:1799
 msgid "Enable Debugging"
 msgstr "Abilita Debugging"
 
-#: mod/admin.php:1603
+#: mod/admin.php:1800
 msgid "Log file"
 msgstr "File di Log"
 
-#: mod/admin.php:1603
+#: mod/admin.php:1800
 msgid ""
 "Must be writable by web server. Relative to your Friendica top-level "
 "directory."
 msgstr "Deve essere scrivibile dal server web. Relativo alla tua directory Friendica."
 
-#: mod/admin.php:1604
+#: mod/admin.php:1801
 msgid "Log level"
 msgstr "Livello di Log"
 
-#: mod/admin.php:1654 include/acl_selectors.php:348
-msgid "Close"
-msgstr "Chiudi"
+#: mod/admin.php:1804
+msgid "PHP logging"
+msgstr ""
 
-#: mod/admin.php:1660
-msgid "FTP Host"
-msgstr "Indirizzo FTP"
+#: mod/admin.php:1805
+msgid ""
+"To enable logging of PHP errors and warnings you can add the following to "
+"the .htconfig.php file of your installation. The filename set in the "
+"'error_log' line is relative to the friendica top-level directory and must "
+"be writeable by the web server. The option '1' for 'log_errors' and "
+"'display_errors' is to enable these options, set to '0' to disable them."
+msgstr ""
 
-#: mod/admin.php:1661
-msgid "FTP Path"
-msgstr "Percorso FTP"
+#: mod/admin.php:1931 mod/admin.php:1932 mod/settings.php:759
+msgid "Off"
+msgstr "Spento"
 
-#: mod/admin.php:1662
-msgid "FTP User"
-msgstr "Utente FTP"
+#: mod/admin.php:1931 mod/admin.php:1932 mod/settings.php:759
+msgid "On"
+msgstr "Acceso"
+
+#: mod/admin.php:1932
+#, php-format
+msgid "Lock feature %s"
+msgstr ""
 
-#: mod/admin.php:1663
-msgid "FTP Password"
-msgstr "Pasword FTP"
+#: mod/admin.php:1940
+msgid "Manage Additional Features"
+msgstr ""
 
 #: mod/network.php:146
 #, php-format
@@ -3232,7 +3248,7 @@ msgstr "Risultato della ricerca per: %s"
 msgid "Remove term"
 msgstr "Rimuovi termine"
 
-#: mod/network.php:200 mod/search.php:34 include/features.php:79
+#: mod/network.php:200 mod/search.php:34 include/features.php:84
 msgid "Saved Searches"
 msgstr "Ricerche salvate"
 
@@ -3240,51 +3256,51 @@ msgstr "Ricerche salvate"
 msgid "add"
 msgstr "aggiungi"
 
-#: mod/network.php:362
+#: mod/network.php:365
 msgid "Commented Order"
 msgstr "Ordina per commento"
 
-#: mod/network.php:365
+#: mod/network.php:368
 msgid "Sort by Comment Date"
 msgstr "Ordina per data commento"
 
-#: mod/network.php:370
+#: mod/network.php:373
 msgid "Posted Order"
 msgstr "Ordina per invio"
 
-#: mod/network.php:373
+#: mod/network.php:376
 msgid "Sort by Post Date"
 msgstr "Ordina per data messaggio"
 
-#: mod/network.php:384
+#: mod/network.php:387
 msgid "Posts that mention or involve you"
 msgstr "Messaggi che ti citano o coinvolgono"
 
-#: mod/network.php:392
+#: mod/network.php:395
 msgid "New"
 msgstr "Nuovo"
 
-#: mod/network.php:395
+#: mod/network.php:398
 msgid "Activity Stream - by date"
 msgstr "Activity Stream - per data"
 
-#: mod/network.php:403
+#: mod/network.php:406
 msgid "Shared Links"
 msgstr "Links condivisi"
 
-#: mod/network.php:406
+#: mod/network.php:409
 msgid "Interesting Links"
 msgstr "Link Interessanti"
 
-#: mod/network.php:414
+#: mod/network.php:417
 msgid "Starred"
 msgstr "Preferiti"
 
-#: mod/network.php:417
+#: mod/network.php:420
 msgid "Favourite Posts"
 msgstr "Messaggi preferiti"
 
-#: mod/network.php:476
+#: mod/network.php:479
 #, php-format
 msgid "Warning: This group contains %s member from an insecure network."
 msgid_plural ""
@@ -3292,28 +3308,28 @@ msgid_plural ""
 msgstr[0] "Attenzione: questo gruppo contiene %s membro da un network insicuro."
 msgstr[1] "Attenzione: questo gruppo contiene %s membri da un network insicuro."
 
-#: mod/network.php:479
+#: mod/network.php:482
 msgid "Private messages to this group are at risk of public disclosure."
 msgstr "I messaggi privati su questo gruppo potrebbero risultare visibili anche pubblicamente."
 
-#: mod/network.php:546 mod/content.php:119
+#: mod/network.php:549 mod/content.php:119
 msgid "No such group"
 msgstr "Nessun gruppo"
 
-#: mod/network.php:574 mod/content.php:135
+#: mod/network.php:580 mod/content.php:135
 #, php-format
 msgid "Group: %s"
 msgstr "Gruppo: %s"
 
-#: mod/network.php:606
+#: mod/network.php:608
 msgid "Private messages to this person are at risk of public disclosure."
 msgstr "I messaggi privati a questa persona potrebbero risultare visibili anche pubblicamente."
 
-#: mod/network.php:611
+#: mod/network.php:613
 msgid "Invalid contact."
 msgstr "Contatto non valido."
 
-#: mod/allfriends.php:38
+#: mod/allfriends.php:43
 msgid "No friends to display."
 msgstr "Nessun amico da visualizzare."
 
@@ -3353,11 +3369,11 @@ msgstr "Ven"
 msgid "Sat"
 msgstr "Sab"
 
-#: mod/events.php:208 mod/settings.php:939 include/text.php:1274
+#: mod/events.php:208 mod/settings.php:948 include/text.php:1274
 msgid "Sunday"
 msgstr "Domenica"
 
-#: mod/events.php:209 mod/settings.php:939 include/text.php:1274
+#: mod/events.php:209 mod/settings.php:948 include/text.php:1274
 msgid "Monday"
 msgstr "Lunedì"
 
@@ -3497,11 +3513,11 @@ msgstr "l j F"
 msgid "Edit event"
 msgstr "Modifca l'evento"
 
-#: mod/events.php:421 include/text.php:1721 include/text.php:1728
+#: mod/events.php:421 include/text.php:1728 include/text.php:1735
 msgid "link to source"
 msgstr "Collegamento all'originale"
 
-#: mod/events.php:456 include/identity.php:713 include/nav.php:79
+#: mod/events.php:456 include/identity.php:722 include/nav.php:79
 #: include/nav.php:140 view/theme/diabook/theme.php:127
 msgid "Events"
 msgstr "Eventi"
@@ -3560,7 +3576,7 @@ msgstr "Condividi questo evento"
 
 #: mod/events.php:572 mod/content.php:721 mod/editpost.php:145
 #: mod/photos.php:1631 mod/photos.php:1679 mod/photos.php:1767
-#: object/Item.php:719 include/conversation.php:1217
+#: object/Item.php:719 include/conversation.php:1216
 msgid "Preview"
 msgstr "Anteprima"
 
@@ -3604,15 +3620,15 @@ msgstr[0] "%d commento"
 msgstr[1] "%d commenti"
 
 #: mod/content.php:607 object/Item.php:421 object/Item.php:434
-#: include/text.php:1997
+#: include/text.php:2004
 msgid "comment"
 msgid_plural "comments"
 msgstr[0] ""
 msgstr[1] "commento"
 
-#: mod/content.php:608 boot.php:785 object/Item.php:422
+#: mod/content.php:608 boot.php:870 object/Item.php:422
 #: include/contact_widgets.php:242 include/forums.php:110
-#: include/items.php:5178 view/theme/vier/theme.php:264
+#: include/items.php:5207 view/theme/vier/theme.php:264
 msgid "show more"
 msgstr "mostra di più"
 
@@ -3650,7 +3666,7 @@ msgid "This is you"
 msgstr "Questo sei tu"
 
 #: mod/content.php:711 mod/photos.php:1629 mod/photos.php:1677
-#: mod/photos.php:1765 boot.php:784 object/Item.php:393 object/Item.php:709
+#: mod/photos.php:1765 boot.php:869 object/Item.php:393 object/Item.php:709
 msgid "Comment"
 msgstr "Commento"
 
@@ -3686,7 +3702,7 @@ msgstr "Link"
 msgid "Video"
 msgstr "Video"
 
-#: mod/content.php:730 mod/settings.php:712 object/Item.php:122
+#: mod/content.php:730 mod/settings.php:721 object/Item.php:122
 #: object/Item.php:124
 msgid "Edit"
 msgstr "Modifica"
@@ -4082,19 +4098,19 @@ msgid ""
 "your site allow private mail from unknown senders."
 msgstr "Se vuoi che %s ti risponda, controlla che le tue impostazioni di privacy permettano la ricezione di messaggi privati da mittenti sconosciuti."
 
-#: mod/help.php:31
+#: mod/help.php:41
 msgid "Help:"
 msgstr "Guida:"
 
-#: mod/help.php:36 include/nav.php:113 view/theme/vier/theme.php:302
+#: mod/help.php:47 include/nav.php:113 view/theme/vier/theme.php:302
 msgid "Help"
 msgstr "Guida"
 
-#: mod/help.php:42 mod/p.php:16 mod/p.php:25 index.php:269
+#: mod/help.php:53 mod/p.php:16 mod/p.php:25 index.php:270
 msgid "Not Found"
 msgstr "Non trovato"
 
-#: mod/help.php:45 index.php:272
+#: mod/help.php:56 index.php:273
 msgid "Page not found."
 msgstr "Pagina non trovata."
 
@@ -4141,16 +4157,16 @@ msgstr "Profili corrispondenti"
 msgid "link"
 msgstr "collegamento"
 
-#: mod/community.php:23
+#: mod/community.php:27
 msgid "Not available."
 msgstr "Non disponibile."
 
-#: mod/community.php:32 include/nav.php:136 include/nav.php:138
+#: mod/community.php:36 include/nav.php:136 include/nav.php:138
 #: view/theme/diabook/theme.php:129
 msgid "Community"
 msgstr "Comunità"
 
-#: mod/community.php:62 mod/community.php:71 mod/search.php:228
+#: mod/community.php:66 mod/community.php:75 mod/search.php:228
 msgid "No results."
 msgstr "Nessun risultato."
 
@@ -4158,822 +4174,812 @@ msgstr "Nessun risultato."
 msgid "everybody"
 msgstr "tutti"
 
-#: mod/settings.php:47
-msgid "Additional features"
-msgstr "Funzionalità aggiuntive"
-
-#: mod/settings.php:53
+#: mod/settings.php:58
 msgid "Display"
 msgstr "Visualizzazione"
 
-#: mod/settings.php:60 mod/settings.php:855
+#: mod/settings.php:65 mod/settings.php:864
 msgid "Social Networks"
 msgstr "Social Networks"
 
-#: mod/settings.php:72 include/nav.php:180
+#: mod/settings.php:79 include/nav.php:180
 msgid "Delegations"
 msgstr "Delegazioni"
 
-#: mod/settings.php:78
+#: mod/settings.php:86
 msgid "Connected apps"
 msgstr "Applicazioni collegate"
 
-#: mod/settings.php:84 mod/uexport.php:85
+#: mod/settings.php:93 mod/uexport.php:85
 msgid "Export personal data"
 msgstr "Esporta dati personali"
 
-#: mod/settings.php:90
+#: mod/settings.php:100
 msgid "Remove account"
 msgstr "Rimuovi account"
 
-#: mod/settings.php:143
+#: mod/settings.php:153
 msgid "Missing some important data!"
 msgstr "Mancano alcuni dati importanti!"
 
-#: mod/settings.php:256
+#: mod/settings.php:266
 msgid "Failed to connect with email account using the settings provided."
 msgstr "Impossibile collegarsi all'account email con i parametri forniti."
 
-#: mod/settings.php:261
+#: mod/settings.php:271
 msgid "Email settings updated."
 msgstr "Impostazioni e-mail aggiornate."
 
-#: mod/settings.php:276
+#: mod/settings.php:286
 msgid "Features updated"
 msgstr "Funzionalità aggiornate"
 
-#: mod/settings.php:343
+#: mod/settings.php:353
 msgid "Relocate message has been send to your contacts"
 msgstr "Il messaggio di trasloco è stato inviato ai tuoi contatti"
 
-#: mod/settings.php:357 include/user.php:39
+#: mod/settings.php:367 include/user.php:39
 msgid "Passwords do not match. Password unchanged."
 msgstr "Le password non corrispondono. Password non cambiata."
 
-#: mod/settings.php:362
+#: mod/settings.php:372
 msgid "Empty passwords are not allowed. Password unchanged."
 msgstr "Le password non possono essere vuote. Password non cambiata."
 
-#: mod/settings.php:370
+#: mod/settings.php:380
 msgid "Wrong password."
 msgstr "Password sbagliata."
 
-#: mod/settings.php:381
+#: mod/settings.php:391
 msgid "Password changed."
 msgstr "Password cambiata."
 
-#: mod/settings.php:383
+#: mod/settings.php:393
 msgid "Password update failed. Please try again."
 msgstr "Aggiornamento password fallito. Prova ancora."
 
-#: mod/settings.php:452
+#: mod/settings.php:462
 msgid " Please use a shorter name."
 msgstr " Usa un nome più corto."
 
-#: mod/settings.php:454
+#: mod/settings.php:464
 msgid " Name too short."
 msgstr " Nome troppo corto."
 
-#: mod/settings.php:463
+#: mod/settings.php:473
 msgid "Wrong Password"
 msgstr "Password Sbagliata"
 
-#: mod/settings.php:468
+#: mod/settings.php:478
 msgid " Not valid email."
 msgstr " Email non valida."
 
-#: mod/settings.php:474
+#: mod/settings.php:484
 msgid " Cannot change to that email."
 msgstr "Non puoi usare quella email."
 
-#: mod/settings.php:530
+#: mod/settings.php:540
 msgid "Private forum has no privacy permissions. Using default privacy group."
 msgstr "Il forum privato non ha permessi di privacy. Uso il gruppo di privacy predefinito."
 
-#: mod/settings.php:534
+#: mod/settings.php:544
 msgid "Private forum has no privacy permissions and no default privacy group."
 msgstr "Il gruppo privato non ha permessi di privacy e nessun gruppo di privacy predefinito."
 
-#: mod/settings.php:573
+#: mod/settings.php:583
 msgid "Settings updated."
 msgstr "Impostazioni aggiornate."
 
-#: mod/settings.php:649 mod/settings.php:675 mod/settings.php:711
+#: mod/settings.php:658 mod/settings.php:684 mod/settings.php:720
 msgid "Add application"
 msgstr "Aggiungi applicazione"
 
-#: mod/settings.php:653 mod/settings.php:679
+#: mod/settings.php:662 mod/settings.php:688
 msgid "Consumer Key"
 msgstr "Consumer Key"
 
-#: mod/settings.php:654 mod/settings.php:680
+#: mod/settings.php:663 mod/settings.php:689
 msgid "Consumer Secret"
 msgstr "Consumer Secret"
 
-#: mod/settings.php:655 mod/settings.php:681
+#: mod/settings.php:664 mod/settings.php:690
 msgid "Redirect"
 msgstr "Redirect"
 
-#: mod/settings.php:656 mod/settings.php:682
+#: mod/settings.php:665 mod/settings.php:691
 msgid "Icon url"
 msgstr "Url icona"
 
-#: mod/settings.php:667
+#: mod/settings.php:676
 msgid "You can't edit this application."
 msgstr "Non puoi modificare questa applicazione."
 
-#: mod/settings.php:710
+#: mod/settings.php:719
 msgid "Connected Apps"
 msgstr "Applicazioni Collegate"
 
-#: mod/settings.php:714
+#: mod/settings.php:723
 msgid "Client key starts with"
 msgstr "Chiave del client inizia con"
 
-#: mod/settings.php:715
+#: mod/settings.php:724
 msgid "No name"
 msgstr "Nessun nome"
 
-#: mod/settings.php:716
+#: mod/settings.php:725
 msgid "Remove authorization"
 msgstr "Rimuovi l'autorizzazione"
 
-#: mod/settings.php:728
+#: mod/settings.php:737
 msgid "No Plugin settings configured"
 msgstr "Nessun plugin ha impostazioni modificabili"
 
-#: mod/settings.php:736
+#: mod/settings.php:745
 msgid "Plugin Settings"
 msgstr "Impostazioni plugin"
 
-#: mod/settings.php:750
-msgid "Off"
-msgstr "Spento"
-
-#: mod/settings.php:750
-msgid "On"
-msgstr "Acceso"
-
-#: mod/settings.php:758
+#: mod/settings.php:767
 msgid "Additional Features"
 msgstr "Funzionalità aggiuntive"
 
-#: mod/settings.php:768 mod/settings.php:772
+#: mod/settings.php:777 mod/settings.php:781
 msgid "General Social Media Settings"
 msgstr "Impostazioni Media Sociali"
 
-#: mod/settings.php:778
+#: mod/settings.php:787
 msgid "Disable intelligent shortening"
 msgstr "Disabilita accorciamento intelligente"
 
-#: mod/settings.php:780
+#: mod/settings.php:789
 msgid ""
 "Normally the system tries to find the best link to add to shortened posts. "
 "If this option is enabled then every shortened post will always point to the"
 " original friendica post."
 msgstr "Normalmente il sistema tenta di trovare il migliore link da aggiungere a un post accorciato. Se questa opzione è abilitata, ogni post accorciato conterrà sempre un link al post originale su Friendica."
 
-#: mod/settings.php:786
+#: mod/settings.php:795
 msgid "Automatically follow any GNU Social (OStatus) followers/mentioners"
 msgstr "Segui automanticamente chiunque da GNU Social (OStatus)  ti segua o ti menzioni"
 
-#: mod/settings.php:788
+#: mod/settings.php:797
 msgid ""
 "If you receive a message from an unknown OStatus user, this option decides "
 "what to do. If it is checked, a new contact will be created for every "
 "unknown user."
 msgstr "Se ricevi un messaggio da un utente OStatus sconosciuto, questa opzione decide cosa fare. Se selezionato, un nuovo contatto verrà creato per ogni utente sconosciuto."
 
-#: mod/settings.php:797
+#: mod/settings.php:806
 msgid "Your legacy GNU Social account"
 msgstr "Il tuo vecchio account GNU Social"
 
-#: mod/settings.php:799
+#: mod/settings.php:808
 msgid ""
 "If you enter your old GNU Social/Statusnet account name here (in the format "
 "user@domain.tld), your contacts will be added automatically. The field will "
 "be emptied when done."
 msgstr "Se inserisci il nome del tuo vecchio account GNU Social/Statusnet qui (nel formato utente@dominio.tld), i tuoi contatti verranno automaticamente aggiunti. Il campo verrà svuotato una volta terminato."
 
-#: mod/settings.php:802
+#: mod/settings.php:811
 msgid "Repair OStatus subscriptions"
 msgstr "Ripara le iscrizioni OStatus"
 
-#: mod/settings.php:811 mod/settings.php:812
+#: mod/settings.php:820 mod/settings.php:821
 #, php-format
 msgid "Built-in support for %s connectivity is %s"
 msgstr "Il supporto integrato per la connettività con %s è %s"
 
-#: mod/settings.php:811 mod/dfrn_request.php:858
+#: mod/settings.php:820 mod/dfrn_request.php:865
 #: include/contact_selectors.php:80
 msgid "Diaspora"
 msgstr "Diaspora"
 
-#: mod/settings.php:811 mod/settings.php:812
+#: mod/settings.php:820 mod/settings.php:821
 msgid "enabled"
 msgstr "abilitato"
 
-#: mod/settings.php:811 mod/settings.php:812
+#: mod/settings.php:820 mod/settings.php:821
 msgid "disabled"
 msgstr "disabilitato"
 
-#: mod/settings.php:812
+#: mod/settings.php:821
 msgid "GNU Social (OStatus)"
 msgstr "GNU Social (OStatus)"
 
-#: mod/settings.php:848
+#: mod/settings.php:857
 msgid "Email access is disabled on this site."
 msgstr "L'accesso email è disabilitato su questo sito."
 
-#: mod/settings.php:860
+#: mod/settings.php:869
 msgid "Email/Mailbox Setup"
 msgstr "Impostazioni email"
 
-#: mod/settings.php:861
+#: mod/settings.php:870
 msgid ""
 "If you wish to communicate with email contacts using this service "
 "(optional), please specify how to connect to your mailbox."
 msgstr "Se vuoi comunicare con i contatti email usando questo servizio, specifica come collegarti alla tua casella di posta. (opzionale)"
 
-#: mod/settings.php:862
+#: mod/settings.php:871
 msgid "Last successful email check:"
 msgstr "Ultimo controllo email eseguito con successo:"
 
-#: mod/settings.php:864
+#: mod/settings.php:873
 msgid "IMAP server name:"
 msgstr "Nome server IMAP:"
 
-#: mod/settings.php:865
+#: mod/settings.php:874
 msgid "IMAP port:"
 msgstr "Porta IMAP:"
 
-#: mod/settings.php:866
+#: mod/settings.php:875
 msgid "Security:"
 msgstr "Sicurezza:"
 
-#: mod/settings.php:866 mod/settings.php:871
+#: mod/settings.php:875 mod/settings.php:880
 msgid "None"
 msgstr "Nessuna"
 
-#: mod/settings.php:867
+#: mod/settings.php:876
 msgid "Email login name:"
 msgstr "Nome utente email:"
 
-#: mod/settings.php:868
+#: mod/settings.php:877
 msgid "Email password:"
 msgstr "Password email:"
 
-#: mod/settings.php:869
+#: mod/settings.php:878
 msgid "Reply-to address:"
 msgstr "Indirizzo di risposta:"
 
-#: mod/settings.php:870
+#: mod/settings.php:879
 msgid "Send public posts to all email contacts:"
 msgstr "Invia i messaggi pubblici ai contatti email:"
 
-#: mod/settings.php:871
+#: mod/settings.php:880
 msgid "Action after import:"
 msgstr "Azione post importazione:"
 
-#: mod/settings.php:871
+#: mod/settings.php:880
 msgid "Mark as seen"
 msgstr "Segna come letto"
 
-#: mod/settings.php:871
+#: mod/settings.php:880
 msgid "Move to folder"
 msgstr "Sposta nella cartella"
 
-#: mod/settings.php:872
+#: mod/settings.php:881
 msgid "Move to folder:"
 msgstr "Sposta nella cartella:"
 
-#: mod/settings.php:958
+#: mod/settings.php:967
 msgid "Display Settings"
 msgstr "Impostazioni Grafiche"
 
-#: mod/settings.php:964 mod/settings.php:982
+#: mod/settings.php:973 mod/settings.php:991
 msgid "Display Theme:"
 msgstr "Tema:"
 
-#: mod/settings.php:965
+#: mod/settings.php:974
 msgid "Mobile Theme:"
 msgstr "Tema mobile:"
 
-#: mod/settings.php:966
+#: mod/settings.php:975
 msgid "Update browser every xx seconds"
 msgstr "Aggiorna il browser ogni x secondi"
 
-#: mod/settings.php:966
+#: mod/settings.php:975
 msgid "Minimum of 10 seconds. Enter -1 to disable it."
 msgstr "Minimo 10 secondi. Inserisci -1 per disabilitarlo"
 
-#: mod/settings.php:967
+#: mod/settings.php:976
 msgid "Number of items to display per page:"
 msgstr "Numero di elementi da mostrare per pagina:"
 
-#: mod/settings.php:967 mod/settings.php:968
+#: mod/settings.php:976 mod/settings.php:977
 msgid "Maximum of 100 items"
 msgstr "Massimo 100 voci"
 
-#: mod/settings.php:968
+#: mod/settings.php:977
 msgid "Number of items to display per page when viewed from mobile device:"
 msgstr "Numero di voci da visualizzare per pagina quando si utilizza un dispositivo mobile:"
 
-#: mod/settings.php:969
+#: mod/settings.php:978
 msgid "Don't show emoticons"
 msgstr "Non mostrare le emoticons"
 
-#: mod/settings.php:970
+#: mod/settings.php:979
 msgid "Calendar"
 msgstr "Calendario"
 
-#: mod/settings.php:971
+#: mod/settings.php:980
 msgid "Beginning of week:"
 msgstr "Inizio della settimana:"
 
-#: mod/settings.php:972
+#: mod/settings.php:981
 msgid "Don't show notices"
 msgstr "Non mostrare gli avvisi"
 
-#: mod/settings.php:973
+#: mod/settings.php:982
 msgid "Infinite scroll"
 msgstr "Scroll infinito"
 
-#: mod/settings.php:974
+#: mod/settings.php:983
 msgid "Automatic updates only at the top of the network page"
 msgstr "Aggiornamenti automatici solo in cima alla pagina \"rete\""
 
-#: mod/settings.php:976 view/theme/cleanzero/config.php:82
+#: mod/settings.php:985 view/theme/cleanzero/config.php:82
 #: view/theme/dispy/config.php:72 view/theme/quattro/config.php:66
-#: view/theme/diabook/config.php:150 view/theme/clean/config.php:85
-#: view/theme/vier/config.php:109 view/theme/duepuntozero/config.php:61
+#: view/theme/diabook/config.php:150 view/theme/vier/config.php:109
+#: view/theme/duepuntozero/config.php:61
 msgid "Theme settings"
 msgstr "Impostazioni tema"
 
-#: mod/settings.php:1053
+#: mod/settings.php:1062
 msgid "User Types"
 msgstr "Tipi di Utenti"
 
-#: mod/settings.php:1054
+#: mod/settings.php:1063
 msgid "Community Types"
 msgstr "Tipi di Comunità"
 
-#: mod/settings.php:1055
+#: mod/settings.php:1064
 msgid "Normal Account Page"
 msgstr "Pagina Account Normale"
 
-#: mod/settings.php:1056
+#: mod/settings.php:1065
 msgid "This account is a normal personal profile"
 msgstr "Questo account è un normale profilo personale"
 
-#: mod/settings.php:1059
+#: mod/settings.php:1068
 msgid "Soapbox Page"
 msgstr "Pagina Sandbox"
 
-#: mod/settings.php:1060
+#: mod/settings.php:1069
 msgid "Automatically approve all connection/friend requests as read-only fans"
 msgstr "Chi richiede la connessione/amicizia sarà accettato automaticamente come fan che potrà solamente leggere la bacheca"
 
-#: mod/settings.php:1063
+#: mod/settings.php:1072
 msgid "Community Forum/Celebrity Account"
 msgstr "Account Celebrità/Forum comunitario"
 
-#: mod/settings.php:1064
+#: mod/settings.php:1073
 msgid ""
 "Automatically approve all connection/friend requests as read-write fans"
 msgstr "Chi richiede la connessione/amicizia sarà accettato automaticamente come fan che potrà leggere e scrivere sulla bacheca"
 
-#: mod/settings.php:1067
+#: mod/settings.php:1076
 msgid "Automatic Friend Page"
 msgstr "Pagina con amicizia automatica"
 
-#: mod/settings.php:1068
+#: mod/settings.php:1077
 msgid "Automatically approve all connection/friend requests as friends"
 msgstr "Chi richiede la connessione/amicizia sarà accettato automaticamente come amico"
 
-#: mod/settings.php:1071
+#: mod/settings.php:1080
 msgid "Private Forum [Experimental]"
 msgstr "Forum privato [sperimentale]"
 
-#: mod/settings.php:1072
+#: mod/settings.php:1081
 msgid "Private forum - approved members only"
 msgstr "Forum privato - solo membri approvati"
 
-#: mod/settings.php:1084
+#: mod/settings.php:1093
 msgid "OpenID:"
 msgstr "OpenID:"
 
-#: mod/settings.php:1084
+#: mod/settings.php:1093
 msgid "(Optional) Allow this OpenID to login to this account."
 msgstr "(Opzionale) Consente di loggarti in questo account con questo OpenID"
 
-#: mod/settings.php:1094
+#: mod/settings.php:1103
 msgid "Publish your default profile in your local site directory?"
 msgstr "Pubblica il tuo profilo predefinito nell'elenco locale del sito"
 
-#: mod/settings.php:1100
+#: mod/settings.php:1109
 msgid "Publish your default profile in the global social directory?"
 msgstr "Pubblica il tuo profilo predefinito nell'elenco sociale globale"
 
-#: mod/settings.php:1108
+#: mod/settings.php:1117
 msgid "Hide your contact/friend list from viewers of your default profile?"
 msgstr "Nascondi la lista dei tuoi contatti/amici dai visitatori del tuo profilo predefinito"
 
-#: mod/settings.php:1112 include/acl_selectors.php:331
+#: mod/settings.php:1121 include/acl_selectors.php:331
 msgid "Hide your profile details from unknown viewers?"
 msgstr "Nascondi i dettagli del tuo profilo ai visitatori sconosciuti?"
 
-#: mod/settings.php:1112
+#: mod/settings.php:1121
 msgid ""
 "If enabled, posting public messages to Diaspora and other networks isn't "
 "possible."
 msgstr "Se abilitato, l'invio di messaggi pubblici verso Diaspora e altri network non sarà possibile"
 
-#: mod/settings.php:1117
+#: mod/settings.php:1126
 msgid "Allow friends to post to your profile page?"
 msgstr "Permetti agli amici di scrivere sulla tua pagina profilo?"
 
-#: mod/settings.php:1123
+#: mod/settings.php:1132
 msgid "Allow friends to tag your posts?"
 msgstr "Permetti agli amici di taggare i tuoi messaggi?"
 
-#: mod/settings.php:1129
+#: mod/settings.php:1138
 msgid "Allow us to suggest you as a potential friend to new members?"
 msgstr "Ci permetti di suggerirti come potenziale amico ai nuovi membri?"
 
-#: mod/settings.php:1135
+#: mod/settings.php:1144
 msgid "Permit unknown people to send you private mail?"
 msgstr "Permetti a utenti sconosciuti di inviarti messaggi privati?"
 
-#: mod/settings.php:1143
+#: mod/settings.php:1152
 msgid "Profile is <strong>not published</strong>."
 msgstr "Il profilo <strong>non è pubblicato</strong>."
 
-#: mod/settings.php:1151
+#: mod/settings.php:1160
 #, php-format
 msgid "Your Identity Address is <strong>'%s'</strong> or '%s'."
 msgstr "L'indirizzo della tua identità è <strong>'%s'</strong> or '%s'."
 
-#: mod/settings.php:1158
+#: mod/settings.php:1167
 msgid "Automatically expire posts after this many days:"
 msgstr "Fai scadere i post automaticamente dopo x giorni:"
 
-#: mod/settings.php:1158
+#: mod/settings.php:1167
 msgid "If empty, posts will not expire. Expired posts will be deleted"
 msgstr "Se lasciato vuoto, i messaggi non verranno cancellati."
 
-#: mod/settings.php:1159
+#: mod/settings.php:1168
 msgid "Advanced expiration settings"
 msgstr "Impostazioni avanzate di scandenza"
 
-#: mod/settings.php:1160
+#: mod/settings.php:1169
 msgid "Advanced Expiration"
 msgstr "Scadenza avanzata"
 
-#: mod/settings.php:1161
+#: mod/settings.php:1170
 msgid "Expire posts:"
 msgstr "Fai scadere i post:"
 
-#: mod/settings.php:1162
+#: mod/settings.php:1171
 msgid "Expire personal notes:"
 msgstr "Fai scadere le Note personali:"
 
-#: mod/settings.php:1163
+#: mod/settings.php:1172
 msgid "Expire starred posts:"
 msgstr "Fai scadere i post Speciali:"
 
-#: mod/settings.php:1164
+#: mod/settings.php:1173
 msgid "Expire photos:"
 msgstr "Fai scadere le foto:"
 
-#: mod/settings.php:1165
+#: mod/settings.php:1174
 msgid "Only expire posts by others:"
 msgstr "Fai scadere solo i post degli altri:"
 
-#: mod/settings.php:1193
+#: mod/settings.php:1202
 msgid "Account Settings"
 msgstr "Impostazioni account"
 
-#: mod/settings.php:1201
+#: mod/settings.php:1210
 msgid "Password Settings"
 msgstr "Impostazioni password"
 
-#: mod/settings.php:1202 mod/register.php:274
+#: mod/settings.php:1211 mod/register.php:274
 msgid "New Password:"
 msgstr "Nuova password:"
 
-#: mod/settings.php:1203 mod/register.php:275
+#: mod/settings.php:1212 mod/register.php:275
 msgid "Confirm:"
 msgstr "Conferma:"
 
-#: mod/settings.php:1203
+#: mod/settings.php:1212
 msgid "Leave password fields blank unless changing"
 msgstr "Lascia questi campi in bianco per non effettuare variazioni alla password"
 
-#: mod/settings.php:1204
+#: mod/settings.php:1213
 msgid "Current Password:"
 msgstr "Password Attuale:"
 
-#: mod/settings.php:1204 mod/settings.php:1205
+#: mod/settings.php:1213 mod/settings.php:1214
 msgid "Your current password to confirm the changes"
 msgstr "La tua password attuale per confermare le modifiche"
 
-#: mod/settings.php:1205
+#: mod/settings.php:1214
 msgid "Password:"
 msgstr "Password:"
 
-#: mod/settings.php:1209
+#: mod/settings.php:1218
 msgid "Basic Settings"
 msgstr "Impostazioni base"
 
-#: mod/settings.php:1210 include/identity.php:578
+#: mod/settings.php:1219 include/identity.php:588
 msgid "Full Name:"
 msgstr "Nome completo:"
 
-#: mod/settings.php:1211
+#: mod/settings.php:1220
 msgid "Email Address:"
 msgstr "Indirizzo Email:"
 
-#: mod/settings.php:1212
+#: mod/settings.php:1221
 msgid "Your Timezone:"
 msgstr "Il tuo fuso orario:"
 
-#: mod/settings.php:1213
+#: mod/settings.php:1222
 msgid "Your Language:"
 msgstr "La tua lingua:"
 
-#: mod/settings.php:1213
+#: mod/settings.php:1222
 msgid ""
 "Set the language we use to show you friendica interface and to send you "
 "emails"
 msgstr "Imposta la lingua che sarà usata per mostrarti l'interfaccia di Friendica e per inviarti le email"
 
-#: mod/settings.php:1214
+#: mod/settings.php:1223
 msgid "Default Post Location:"
 msgstr "Località predefinita:"
 
-#: mod/settings.php:1215
+#: mod/settings.php:1224
 msgid "Use Browser Location:"
 msgstr "Usa la località rilevata dal browser:"
 
-#: mod/settings.php:1218
+#: mod/settings.php:1227
 msgid "Security and Privacy Settings"
 msgstr "Impostazioni di sicurezza e privacy"
 
-#: mod/settings.php:1220
+#: mod/settings.php:1229
 msgid "Maximum Friend Requests/Day:"
 msgstr "Numero massimo di richieste di amicizia al giorno:"
 
-#: mod/settings.php:1220 mod/settings.php:1250
+#: mod/settings.php:1229 mod/settings.php:1259
 msgid "(to prevent spam abuse)"
 msgstr "(per prevenire lo spam)"
 
-#: mod/settings.php:1221
+#: mod/settings.php:1230
 msgid "Default Post Permissions"
 msgstr "Permessi predefiniti per i messaggi"
 
-#: mod/settings.php:1222
+#: mod/settings.php:1231
 msgid "(click to open/close)"
 msgstr "(clicca per aprire/chiudere)"
 
-#: mod/settings.php:1231 mod/photos.php:1199 mod/photos.php:1584
+#: mod/settings.php:1240 mod/photos.php:1199 mod/photos.php:1584
 msgid "Show to Groups"
 msgstr "Mostra ai gruppi"
 
-#: mod/settings.php:1232 mod/photos.php:1200 mod/photos.php:1585
+#: mod/settings.php:1241 mod/photos.php:1200 mod/photos.php:1585
 msgid "Show to Contacts"
 msgstr "Mostra ai contatti"
 
-#: mod/settings.php:1233
+#: mod/settings.php:1242
 msgid "Default Private Post"
 msgstr "Default Post Privato"
 
-#: mod/settings.php:1234
+#: mod/settings.php:1243
 msgid "Default Public Post"
 msgstr "Default Post Pubblico"
 
-#: mod/settings.php:1238
+#: mod/settings.php:1247
 msgid "Default Permissions for New Posts"
 msgstr "Permessi predefiniti per i nuovi post"
 
-#: mod/settings.php:1250
+#: mod/settings.php:1259
 msgid "Maximum private messages per day from unknown people:"
 msgstr "Numero massimo di messaggi privati da utenti sconosciuti per giorno:"
 
-#: mod/settings.php:1253
+#: mod/settings.php:1262
 msgid "Notification Settings"
 msgstr "Impostazioni notifiche"
 
-#: mod/settings.php:1254
+#: mod/settings.php:1263
 msgid "By default post a status message when:"
 msgstr "Invia un messaggio di stato quando:"
 
-#: mod/settings.php:1255
+#: mod/settings.php:1264
 msgid "accepting a friend request"
 msgstr "accetti una richiesta di amicizia"
 
-#: mod/settings.php:1256
+#: mod/settings.php:1265
 msgid "joining a forum/community"
 msgstr "ti unisci a un forum/comunità"
 
-#: mod/settings.php:1257
+#: mod/settings.php:1266
 msgid "making an <em>interesting</em> profile change"
 msgstr "fai un <em>interessante</em> modifica al profilo"
 
-#: mod/settings.php:1258
+#: mod/settings.php:1267
 msgid "Send a notification email when:"
 msgstr "Invia una mail di notifica quando:"
 
-#: mod/settings.php:1259
+#: mod/settings.php:1268
 msgid "You receive an introduction"
 msgstr "Ricevi una presentazione"
 
-#: mod/settings.php:1260
+#: mod/settings.php:1269
 msgid "Your introductions are confirmed"
 msgstr "Le tue presentazioni sono confermate"
 
-#: mod/settings.php:1261
+#: mod/settings.php:1270
 msgid "Someone writes on your profile wall"
 msgstr "Qualcuno scrive sulla bacheca del tuo profilo"
 
-#: mod/settings.php:1262
+#: mod/settings.php:1271
 msgid "Someone writes a followup comment"
 msgstr "Qualcuno scrive un commento a un tuo messaggio"
 
-#: mod/settings.php:1263
+#: mod/settings.php:1272
 msgid "You receive a private message"
 msgstr "Ricevi un messaggio privato"
 
-#: mod/settings.php:1264
+#: mod/settings.php:1273
 msgid "You receive a friend suggestion"
 msgstr "Hai ricevuto un suggerimento di amicizia"
 
-#: mod/settings.php:1265
+#: mod/settings.php:1274
 msgid "You are tagged in a post"
 msgstr "Sei stato taggato in un post"
 
-#: mod/settings.php:1266
+#: mod/settings.php:1275
 msgid "You are poked/prodded/etc. in a post"
 msgstr "Sei 'toccato'/'spronato'/ecc. in un post"
 
-#: mod/settings.php:1268
+#: mod/settings.php:1277
 msgid "Activate desktop notifications"
 msgstr "Attiva notifiche desktop"
 
-#: mod/settings.php:1268
+#: mod/settings.php:1277
 msgid "Show desktop popup on new notifications"
 msgstr "Mostra un popup di notifica sul desktop all'arrivo di nuove notifiche"
 
-#: mod/settings.php:1270
+#: mod/settings.php:1279
 msgid "Text-only notification emails"
 msgstr "Email di notifica in solo testo"
 
-#: mod/settings.php:1272
+#: mod/settings.php:1281
 msgid "Send text only notification emails, without the html part"
 msgstr "Invia le email di notifica in solo testo, senza la parte in html"
 
-#: mod/settings.php:1274
+#: mod/settings.php:1283
 msgid "Advanced Account/Page Type Settings"
 msgstr "Impostazioni avanzate Account/Tipo di pagina"
 
-#: mod/settings.php:1275
+#: mod/settings.php:1284
 msgid "Change the behaviour of this account for special situations"
 msgstr "Modifica il comportamento di questo account in situazioni speciali"
 
-#: mod/settings.php:1278
+#: mod/settings.php:1287
 msgid "Relocate"
 msgstr "Trasloca"
 
-#: mod/settings.php:1279
+#: mod/settings.php:1288
 msgid ""
 "If you have moved this profile from another server, and some of your "
 "contacts don't receive your updates, try pushing this button."
 msgstr "Se hai spostato questo profilo da un'altro server, e alcuni dei tuoi contatti non ricevono i tuoi aggiornamenti, prova a premere questo bottone."
 
-#: mod/settings.php:1280
+#: mod/settings.php:1289
 msgid "Resend relocate message to contacts"
 msgstr "Reinvia il messaggio di trasloco"
 
-#: mod/dfrn_request.php:95
+#: mod/dfrn_request.php:96
 msgid "This introduction has already been accepted."
 msgstr "Questa presentazione è già stata accettata."
 
-#: mod/dfrn_request.php:120 mod/dfrn_request.php:519
+#: mod/dfrn_request.php:119 mod/dfrn_request.php:516
 msgid "Profile location is not valid or does not contain profile information."
 msgstr "L'indirizzo del profilo non è valido o non contiene un profilo."
 
-#: mod/dfrn_request.php:125 mod/dfrn_request.php:524
+#: mod/dfrn_request.php:124 mod/dfrn_request.php:521
 msgid "Warning: profile location has no identifiable owner name."
 msgstr "Attenzione: l'indirizzo del profilo non riporta il nome del proprietario."
 
-#: mod/dfrn_request.php:127 mod/dfrn_request.php:526
+#: mod/dfrn_request.php:126 mod/dfrn_request.php:523
 msgid "Warning: profile location has no profile photo."
 msgstr "Attenzione: l'indirizzo del profilo non ha una foto."
 
-#: mod/dfrn_request.php:130 mod/dfrn_request.php:529
+#: mod/dfrn_request.php:129 mod/dfrn_request.php:526
 #, php-format
 msgid "%d required parameter was not found at the given location"
 msgid_plural "%d required parameters were not found at the given location"
 msgstr[0] "%d parametro richiesto non è stato trovato all'indirizzo dato"
 msgstr[1] "%d parametri richiesti non sono stati trovati all'indirizzo dato"
 
-#: mod/dfrn_request.php:173
+#: mod/dfrn_request.php:172
 msgid "Introduction complete."
 msgstr "Presentazione completa."
 
-#: mod/dfrn_request.php:215
+#: mod/dfrn_request.php:214
 msgid "Unrecoverable protocol error."
 msgstr "Errore di comunicazione."
 
-#: mod/dfrn_request.php:243
+#: mod/dfrn_request.php:242
 msgid "Profile unavailable."
 msgstr "Profilo non disponibile."
 
-#: mod/dfrn_request.php:268
+#: mod/dfrn_request.php:267
 #, php-format
 msgid "%s has received too many connection requests today."
 msgstr "%s ha ricevuto troppe richieste di connessione per oggi."
 
-#: mod/dfrn_request.php:269
+#: mod/dfrn_request.php:268
 msgid "Spam protection measures have been invoked."
 msgstr "Sono state attivate le misure di protezione contro lo spam."
 
-#: mod/dfrn_request.php:270
+#: mod/dfrn_request.php:269
 msgid "Friends are advised to please try again in 24 hours."
 msgstr "Gli amici sono pregati di riprovare tra 24 ore."
 
-#: mod/dfrn_request.php:332
+#: mod/dfrn_request.php:331
 msgid "Invalid locator"
 msgstr "Invalid locator"
 
-#: mod/dfrn_request.php:341
+#: mod/dfrn_request.php:340
 msgid "Invalid email address."
 msgstr "Indirizzo email non valido."
 
-#: mod/dfrn_request.php:368
+#: mod/dfrn_request.php:367
 msgid "This account has not been configured for email. Request failed."
 msgstr "Questo account non è stato configurato per l'email. Richiesta fallita."
 
-#: mod/dfrn_request.php:464
-msgid "Unable to resolve your name at the provided location."
-msgstr "Impossibile risolvere il tuo nome nella posizione indicata."
-
-#: mod/dfrn_request.php:477
+#: mod/dfrn_request.php:474
 msgid "You have already introduced yourself here."
 msgstr "Ti sei già presentato qui."
 
-#: mod/dfrn_request.php:481
+#: mod/dfrn_request.php:478
 #, php-format
 msgid "Apparently you are already friends with %s."
 msgstr "Pare che tu e %s siate già amici."
 
-#: mod/dfrn_request.php:502
+#: mod/dfrn_request.php:499
 msgid "Invalid profile URL."
 msgstr "Indirizzo profilo non valido."
 
-#: mod/dfrn_request.php:508 include/follow.php:72
+#: mod/dfrn_request.php:505 include/follow.php:72
 msgid "Disallowed profile URL."
 msgstr "Indirizzo profilo non permesso."
 
-#: mod/dfrn_request.php:599
+#: mod/dfrn_request.php:596
 msgid "Your introduction has been sent."
 msgstr "La tua presentazione è stata inviata."
 
-#: mod/dfrn_request.php:652
+#: mod/dfrn_request.php:636
+msgid ""
+"Remote subscription can't be done for your network. Please subscribe "
+"directly on your system."
+msgstr ""
+
+#: mod/dfrn_request.php:659
 msgid "Please login to confirm introduction."
 msgstr "Accedi per confermare la presentazione."
 
-#: mod/dfrn_request.php:662
+#: mod/dfrn_request.php:669
 msgid ""
 "Incorrect identity currently logged in. Please login to "
 "<strong>this</strong> profile."
 msgstr "Non hai fatto accesso con l'identità corretta. Accedi a <strong>questo</strong> profilo."
 
-#: mod/dfrn_request.php:676 mod/dfrn_request.php:693
+#: mod/dfrn_request.php:683 mod/dfrn_request.php:700
 msgid "Confirm"
 msgstr "Conferma"
 
-#: mod/dfrn_request.php:688
+#: mod/dfrn_request.php:695
 msgid "Hide this contact"
 msgstr "Nascondi questo contatto"
 
-#: mod/dfrn_request.php:691
+#: mod/dfrn_request.php:698
 #, php-format
 msgid "Welcome home %s."
 msgstr "Bentornato a casa %s."
 
-#: mod/dfrn_request.php:692
+#: mod/dfrn_request.php:699
 #, php-format
 msgid "Please confirm your introduction/connection request to %s."
 msgstr "Conferma la tua richiesta di connessione con %s."
 
-#: mod/dfrn_request.php:821
+#: mod/dfrn_request.php:828
 msgid ""
 "Please enter your 'Identity Address' from one of the following supported "
 "communications networks:"
 msgstr "Inserisci il tuo 'Indirizzo Identità' da uno dei seguenti network supportati:"
 
-#: mod/dfrn_request.php:842
+#: mod/dfrn_request.php:849
 #, php-format
 msgid ""
 "If you are not yet a member of the free social web, <a "
@@ -4981,25 +4987,25 @@ msgid ""
 "join us today</a>."
 msgstr "Se non sei un membro del web sociale libero,  <a href=\"%s/siteinfo\">segui questo link per trovare un sito Friendica pubblico e unisciti a noi oggi</a>"
 
-#: mod/dfrn_request.php:847
+#: mod/dfrn_request.php:854
 msgid "Friend/Connection Request"
 msgstr "Richieste di amicizia/connessione"
 
-#: mod/dfrn_request.php:848
+#: mod/dfrn_request.php:855
 msgid ""
 "Examples: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, "
 "testuser@identi.ca"
 msgstr "Esempi: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, testuser@identi.ca"
 
-#: mod/dfrn_request.php:856 include/contact_selectors.php:76
+#: mod/dfrn_request.php:863 include/contact_selectors.php:76
 msgid "Friendica"
 msgstr "Friendica"
 
-#: mod/dfrn_request.php:857
+#: mod/dfrn_request.php:864
 msgid "StatusNet/Federated Social Web"
 msgstr "StatusNet/Federated Social Web"
 
-#: mod/dfrn_request.php:859
+#: mod/dfrn_request.php:866
 #, php-format
 msgid ""
 " - please do not use this form.  Instead, enter %s into your Diaspora search"
@@ -5087,7 +5093,7 @@ msgstr "Scegli un nome utente. Deve cominciare con una lettera. L'indirizzo del
 msgid "Choose a nickname: "
 msgstr "Scegli un nome utente: "
 
-#: mod/register.php:280 boot.php:1268 include/nav.php:108
+#: mod/register.php:280 boot.php:1405 include/nav.php:108
 msgid "Register"
 msgstr "Registrati"
 
@@ -5129,11 +5135,11 @@ msgstr "Elementi taggati con: %s"
 msgid "Search results for: %s"
 msgstr "Risultato della ricerca per: %s"
 
-#: mod/directory.php:149 include/identity.php:309 include/identity.php:600
+#: mod/directory.php:149 include/identity.php:313 include/identity.php:610
 msgid "Status:"
 msgstr "Stato:"
 
-#: mod/directory.php:151 include/identity.php:311 include/identity.php:611
+#: mod/directory.php:151 include/identity.php:315 include/identity.php:621
 msgid "Homepage:"
 msgstr "Homepage:"
 
@@ -5193,7 +5199,7 @@ msgstr "Aggiungi"
 msgid "No entries."
 msgstr "Nessuna voce."
 
-#: mod/common.php:87
+#: mod/common.php:86
 msgid "No contacts in common."
 msgstr "Nessun contatto in comune."
 
@@ -5461,7 +5467,7 @@ msgstr "Esempio: cathy123, Cathy Williams, cathy@example.com"
 msgid "Since [date]:"
 msgstr "Dal [data]:"
 
-#: mod/profiles.php:724 include/identity.php:609
+#: mod/profiles.php:724 include/identity.php:619
 msgid "Sexual Preference:"
 msgstr "Preferenze sessuali:"
 
@@ -5469,11 +5475,11 @@ msgstr "Preferenze sessuali:"
 msgid "Homepage URL:"
 msgstr "Homepage:"
 
-#: mod/profiles.php:726 include/identity.php:613
+#: mod/profiles.php:726 include/identity.php:623
 msgid "Hometown:"
 msgstr "Paese natale:"
 
-#: mod/profiles.php:727 include/identity.php:617
+#: mod/profiles.php:727 include/identity.php:627
 msgid "Political Views:"
 msgstr "Orientamento politico:"
 
@@ -5489,11 +5495,11 @@ msgstr "Parole chiave visibili a tutti:"
 msgid "Private Keywords:"
 msgstr "Parole chiave private:"
 
-#: mod/profiles.php:731 include/identity.php:625
+#: mod/profiles.php:731 include/identity.php:635
 msgid "Likes:"
 msgstr "Mi piace:"
 
-#: mod/profiles.php:732 include/identity.php:627
+#: mod/profiles.php:732 include/identity.php:637
 msgid "Dislikes:"
 msgstr "Non mi piace:"
 
@@ -5563,23 +5569,23 @@ msgstr "Età : "
 msgid "Edit/Manage Profiles"
 msgstr "Modifica / Gestisci profili"
 
-#: mod/profiles.php:814 include/identity.php:257 include/identity.php:283
+#: mod/profiles.php:814 include/identity.php:260 include/identity.php:286
 msgid "Change profile photo"
 msgstr "Cambia la foto del profilo"
 
-#: mod/profiles.php:815 include/identity.php:258
+#: mod/profiles.php:815 include/identity.php:261
 msgid "Create New Profile"
 msgstr "Crea un nuovo profilo"
 
-#: mod/profiles.php:826 include/identity.php:268
+#: mod/profiles.php:826 include/identity.php:271
 msgid "Profile Image"
 msgstr "Immagine del Profilo"
 
-#: mod/profiles.php:828 include/identity.php:271
+#: mod/profiles.php:828 include/identity.php:274
 msgid "visible to everybody"
 msgstr "visibile a tutti"
 
-#: mod/profiles.php:829 include/identity.php:272
+#: mod/profiles.php:829 include/identity.php:275
 msgid "Edit visibility"
 msgstr "Modifica visibilità"
 
@@ -5591,55 +5597,55 @@ msgstr "Oggetto non trovato"
 msgid "Edit post"
 msgstr "Modifica messaggio"
 
-#: mod/editpost.php:111 include/conversation.php:1185
+#: mod/editpost.php:111 include/conversation.php:1184
 msgid "upload photo"
 msgstr "carica foto"
 
-#: mod/editpost.php:112 include/conversation.php:1186
+#: mod/editpost.php:112 include/conversation.php:1185
 msgid "Attach file"
 msgstr "Allega file"
 
-#: mod/editpost.php:113 include/conversation.php:1187
+#: mod/editpost.php:113 include/conversation.php:1186
 msgid "attach file"
 msgstr "allega file"
 
-#: mod/editpost.php:115 include/conversation.php:1189
+#: mod/editpost.php:115 include/conversation.php:1188
 msgid "web link"
 msgstr "link web"
 
-#: mod/editpost.php:116 include/conversation.php:1190
+#: mod/editpost.php:116 include/conversation.php:1189
 msgid "Insert video link"
 msgstr "Inserire collegamento video"
 
-#: mod/editpost.php:117 include/conversation.php:1191
+#: mod/editpost.php:117 include/conversation.php:1190
 msgid "video link"
 msgstr "link video"
 
-#: mod/editpost.php:118 include/conversation.php:1192
+#: mod/editpost.php:118 include/conversation.php:1191
 msgid "Insert audio link"
 msgstr "Inserisci collegamento audio"
 
-#: mod/editpost.php:119 include/conversation.php:1193
+#: mod/editpost.php:119 include/conversation.php:1192
 msgid "audio link"
 msgstr "link audio"
 
-#: mod/editpost.php:120 include/conversation.php:1194
+#: mod/editpost.php:120 include/conversation.php:1193
 msgid "Set your location"
 msgstr "La tua posizione"
 
-#: mod/editpost.php:121 include/conversation.php:1195
+#: mod/editpost.php:121 include/conversation.php:1194
 msgid "set location"
 msgstr "posizione"
 
-#: mod/editpost.php:122 include/conversation.php:1196
+#: mod/editpost.php:122 include/conversation.php:1195
 msgid "Clear browser location"
 msgstr "Rimuovi la localizzazione data dal browser"
 
-#: mod/editpost.php:123 include/conversation.php:1197
+#: mod/editpost.php:123 include/conversation.php:1196
 msgid "clear location"
 msgstr "canc. pos."
 
-#: mod/editpost.php:125 include/conversation.php:1203
+#: mod/editpost.php:125 include/conversation.php:1202
 msgid "Permission settings"
 msgstr "Impostazioni permessi"
 
@@ -5647,15 +5653,15 @@ msgstr "Impostazioni permessi"
 msgid "CC: email addresses"
 msgstr "CC: indirizzi email"
 
-#: mod/editpost.php:134 include/conversation.php:1212
+#: mod/editpost.php:134 include/conversation.php:1211
 msgid "Public post"
 msgstr "Messaggio pubblico"
 
-#: mod/editpost.php:137 include/conversation.php:1199
+#: mod/editpost.php:137 include/conversation.php:1198
 msgid "Set title"
 msgstr "Scegli un titolo"
 
-#: mod/editpost.php:139 include/conversation.php:1201
+#: mod/editpost.php:139 include/conversation.php:1200
 msgid "Categories (comma-separated list)"
 msgstr "Categorie (lista separata da virgola)"
 
@@ -5663,39 +5669,39 @@ msgstr "Categorie (lista separata da virgola)"
 msgid "Example: bob@example.com, mary@example.com"
 msgstr "Esempio: bob@example.com, mary@example.com"
 
-#: mod/friendica.php:59
+#: mod/friendica.php:70
 msgid "This is Friendica, version"
 msgstr "Questo è Friendica, versione"
 
-#: mod/friendica.php:60
+#: mod/friendica.php:71
 msgid "running at web location"
 msgstr "in esecuzione all'indirizzo web"
 
-#: mod/friendica.php:62
+#: mod/friendica.php:73
 msgid ""
 "Please visit <a href=\"http://friendica.com\">Friendica.com</a> to learn "
 "more about the Friendica project."
 msgstr "Visita <a href=\"http://friendica.com\">Friendica.com</a> per saperne di più sul progetto Friendica."
 
-#: mod/friendica.php:64
+#: mod/friendica.php:75
 msgid "Bug reports and issues: please visit"
 msgstr "Segnalazioni di bug e problemi: visita"
 
-#: mod/friendica.php:64
+#: mod/friendica.php:75
 msgid "the bugtracker at github"
 msgstr "il bugtracker su github"
 
-#: mod/friendica.php:65
+#: mod/friendica.php:76
 msgid ""
 "Suggestions, praise, donations, etc. - please email \"Info\" at Friendica - "
 "dot com"
 msgstr "Suggerimenti, lodi, donazioni, ecc -  e-mail a  \"Info\" at Friendica punto com"
 
-#: mod/friendica.php:79
+#: mod/friendica.php:90
 msgid "Installed plugins/addons/apps:"
 msgstr "Plugin/addon/applicazioni instalate"
 
-#: mod/friendica.php:92
+#: mod/friendica.php:103
 msgid "No installed plugins/addons/apps"
 msgstr "Nessun plugin/addons/applicazione installata"
 
@@ -5725,7 +5731,7 @@ msgstr "Informazioni remote sulla privacy non disponibili."
 msgid "Visible to:"
 msgstr "Visibile a:"
 
-#: mod/notes.php:46 include/identity.php:721
+#: mod/notes.php:46 include/identity.php:730
 msgid "Personal Notes"
 msgstr "Note personali"
 
@@ -5783,8 +5789,8 @@ msgid "Make this post private"
 msgstr "Rendi questo post privato"
 
 #: mod/repair_ostatus.php:14
-msgid "Resubsribing to OStatus contacts"
-msgstr "Reiscrizione a contatti OStatus"
+msgid "Resubscribing to OStatus contacts"
+msgstr ""
 
 #: mod/repair_ostatus.php:30
 msgid "Error"
@@ -5836,7 +5842,7 @@ msgstr "Visita %s per una lista di siti pubblici a cui puoi iscriverti. I membri
 msgid ""
 "To accept this invitation, please visit and register at %s or any other "
 "public Friendica website."
-msgstr "Per accettare questo invito, visita e resitrati su %s o su un'altro sito web Friendica aperto al pubblico."
+msgstr "Per accettare questo invito, visita e registrati su %s o su un'altro sito web Friendica aperto al pubblico."
 
 #: mod/invite.php:123
 #, php-format
@@ -5865,7 +5871,7 @@ msgstr "Inserisci gli indirizzi email, uno per riga:"
 msgid ""
 "You are cordially invited to join me and other close friends on Friendica - "
 "and help us to create a better social web."
-msgstr "Sei cordialmente invitato a unirti a me ed ad altri amici su Friendica, e ad aiutarci a creare una rete sociale migliore."
+msgstr "Sei cordialmente invitato/a ad unirti a me e ad altri amici su Friendica, e ad aiutarci a creare una rete sociale migliore."
 
 #: mod/invite.php:137
 msgid "You will need to supply this invitation code: $invite_code"
@@ -5882,7 +5888,7 @@ msgid ""
 "important, please visit http://friendica.com"
 msgstr "Per maggiori informazioni sul progetto Friendica e perchè pensiamo sia importante, visita http://friendica.com"
 
-#: mod/photos.php:99 include/identity.php:696
+#: mod/photos.php:99 include/identity.php:705
 msgid "Photo Albums"
 msgstr "Album foto"
 
@@ -6053,12 +6059,12 @@ msgstr "Foto privata"
 msgid "Public photo"
 msgstr "Foto pubblica"
 
-#: mod/photos.php:1609 include/conversation.php:1183
+#: mod/photos.php:1609 include/conversation.php:1182
 msgid "Share"
 msgstr "Condividi"
 
 #: mod/photos.php:1648 include/conversation.php:509
-#: include/conversation.php:1414
+#: include/conversation.php:1413
 msgid "Attending"
 msgid_plural "Attending"
 msgstr[0] "Partecipa"
@@ -6132,60 +6138,60 @@ msgstr "Oggetto non disponibile."
 msgid "Item was not found."
 msgstr "Oggetto non trovato."
 
-#: boot.php:783
+#: boot.php:868
 msgid "Delete this item?"
 msgstr "Cancellare questo elemento?"
 
-#: boot.php:786
+#: boot.php:871
 msgid "show fewer"
 msgstr "mostra di meno"
 
-#: boot.php:1160
+#: boot.php:1292
 #, php-format
 msgid "Update %s failed. See error logs."
 msgstr "aggiornamento %s fallito. Guarda i log di errore."
 
-#: boot.php:1267
+#: boot.php:1404
 msgid "Create a New Account"
 msgstr "Crea un nuovo account"
 
-#: boot.php:1292 include/nav.php:72
+#: boot.php:1429 include/nav.php:72
 msgid "Logout"
 msgstr "Esci"
 
-#: boot.php:1295
+#: boot.php:1432
 msgid "Nickname or Email address: "
 msgstr "Nome utente o indirizzo email: "
 
-#: boot.php:1296
+#: boot.php:1433
 msgid "Password: "
 msgstr "Password: "
 
-#: boot.php:1297
+#: boot.php:1434
 msgid "Remember me"
 msgstr "Ricordati di me"
 
-#: boot.php:1300
+#: boot.php:1437
 msgid "Or login using OpenID: "
 msgstr "O entra con OpenID:"
 
-#: boot.php:1306
+#: boot.php:1443
 msgid "Forgot your password?"
 msgstr "Hai dimenticato la password?"
 
-#: boot.php:1309
+#: boot.php:1446
 msgid "Website Terms of Service"
 msgstr "Condizioni di servizio del sito web "
 
-#: boot.php:1310
+#: boot.php:1447
 msgid "terms of service"
 msgstr "condizioni del servizio"
 
-#: boot.php:1312
+#: boot.php:1449
 msgid "Website Privacy Policy"
 msgstr "Politiche di privacy del sito"
 
-#: boot.php:1313
+#: boot.php:1450
 msgid "privacy policy"
 msgstr "politiche di privacy"
 
@@ -6246,25 +6252,25 @@ msgid ""
 "[pre]%s[/pre]"
 msgstr "Il messaggio di errore è\n[pre]%s[/pre]"
 
-#: include/dbstructure.php:151
+#: include/dbstructure.php:153
 msgid "Errors encountered creating database tables."
 msgstr "La creazione delle tabelle del database ha generato errori."
 
-#: include/dbstructure.php:209
+#: include/dbstructure.php:230
 msgid "Errors encountered performing database changes."
 msgstr "Riscontrati errori applicando le modifiche al database."
 
-#: include/auth.php:38
+#: include/auth.php:44
 msgid "Logged out."
 msgstr "Uscita effettuata."
 
-#: include/auth.php:128 include/user.php:75
+#: include/auth.php:134 include/user.php:75
 msgid ""
 "We encountered a problem while logging in with the OpenID you provided. "
 "Please check the correct spelling of the ID."
 msgstr "Abbiamo incontrato un problema mentre contattavamo il server OpenID che ci hai fornito. Controlla di averlo scritto giusto."
 
-#: include/auth.php:128 include/user.php:75
+#: include/auth.php:134 include/user.php:75
 msgid "The error message was:"
 msgstr "Il messaggio riportato era:"
 
@@ -6321,7 +6327,7 @@ msgstr "Reti"
 msgid "All Networks"
 msgstr "Tutte le Reti"
 
-#: include/contact_widgets.php:141 include/features.php:97
+#: include/contact_widgets.php:141 include/features.php:102
 msgid "Saved Folders"
 msgstr "Cartelle Salvate"
 
@@ -6340,194 +6346,194 @@ msgid_plural "%d contacts in common"
 msgstr[0] "%d contatto in comune"
 msgstr[1] "%d contatti in comune"
 
-#: include/features.php:58
+#: include/features.php:63
 msgid "General Features"
 msgstr "Funzionalità generali"
 
-#: include/features.php:60
+#: include/features.php:65
 msgid "Multiple Profiles"
 msgstr "Profili multipli"
 
-#: include/features.php:60
+#: include/features.php:65
 msgid "Ability to create multiple profiles"
 msgstr "Possibilità di creare profili multipli"
 
-#: include/features.php:61
+#: include/features.php:66
 msgid "Photo Location"
 msgstr "Località Foto"
 
-#: include/features.php:61
+#: include/features.php:66
 msgid ""
 "Photo metadata is normally stripped. This extracts the location (if present)"
 " prior to stripping metadata and links it to a map."
 msgstr "I metadati delle foto vengono rimossi. Questa opzione estrae la località (se presenta) prima di rimuovere i metadati e la collega a una mappa."
 
-#: include/features.php:66
+#: include/features.php:71
 msgid "Post Composition Features"
 msgstr "Funzionalità di composizione dei post"
 
-#: include/features.php:67
+#: include/features.php:72
 msgid "Richtext Editor"
 msgstr "Editor visuale"
 
-#: include/features.php:67
+#: include/features.php:72
 msgid "Enable richtext editor"
 msgstr "Abilita l'editor visuale"
 
-#: include/features.php:68
+#: include/features.php:73
 msgid "Post Preview"
 msgstr "Anteprima dei post"
 
-#: include/features.php:68
+#: include/features.php:73
 msgid "Allow previewing posts and comments before publishing them"
 msgstr "Permetti di avere un'anteprima di messaggi e commenti prima di pubblicarli"
 
-#: include/features.php:69
+#: include/features.php:74
 msgid "Auto-mention Forums"
 msgstr "Auto-cita i Forum"
 
-#: include/features.php:69
+#: include/features.php:74
 msgid ""
 "Add/remove mention when a fourm page is selected/deselected in ACL window."
 msgstr "Aggiunge o rimuove una citazione quando un forum è selezionato o deselezionato nella finestra dei permessi."
 
-#: include/features.php:74
+#: include/features.php:79
 msgid "Network Sidebar Widgets"
 msgstr "Widget della barra laterale nella pagina Rete"
 
-#: include/features.php:75
+#: include/features.php:80
 msgid "Search by Date"
 msgstr "Cerca per data"
 
-#: include/features.php:75
+#: include/features.php:80
 msgid "Ability to select posts by date ranges"
 msgstr "Permette di filtrare i post per data"
 
-#: include/features.php:76 include/features.php:106
+#: include/features.php:81 include/features.php:111
 msgid "List Forums"
 msgstr "Elenco forum"
 
-#: include/features.php:76
+#: include/features.php:81
 msgid "Enable widget to display the forums your are connected with"
 msgstr "Abilita il widget che mostra i forum ai quali sei connesso"
 
-#: include/features.php:77
+#: include/features.php:82
 msgid "Group Filter"
 msgstr "Filtra gruppi"
 
-#: include/features.php:77
+#: include/features.php:82
 msgid "Enable widget to display Network posts only from selected group"
 msgstr "Abilita il widget per filtrare i post solo per il gruppo selezionato"
 
-#: include/features.php:78
+#: include/features.php:83
 msgid "Network Filter"
 msgstr "Filtro reti"
 
-#: include/features.php:78
+#: include/features.php:83
 msgid "Enable widget to display Network posts only from selected network"
 msgstr "Abilita il widget per mostare i post solo per la rete selezionata"
 
-#: include/features.php:79
+#: include/features.php:84
 msgid "Save search terms for re-use"
 msgstr "Salva i termini cercati per riutilizzarli"
 
-#: include/features.php:84
+#: include/features.php:89
 msgid "Network Tabs"
 msgstr "Schede pagina Rete"
 
-#: include/features.php:85
+#: include/features.php:90
 msgid "Network Personal Tab"
 msgstr "Scheda Personali"
 
-#: include/features.php:85
+#: include/features.php:90
 msgid "Enable tab to display only Network posts that you've interacted on"
 msgstr "Abilita la scheda per mostrare solo i post a cui hai partecipato"
 
-#: include/features.php:86
+#: include/features.php:91
 msgid "Network New Tab"
 msgstr "Scheda Nuovi"
 
-#: include/features.php:86
+#: include/features.php:91
 msgid "Enable tab to display only new Network posts (from the last 12 hours)"
 msgstr "Abilita la scheda per mostrare solo i post nuovi (nelle ultime 12 ore)"
 
-#: include/features.php:87
+#: include/features.php:92
 msgid "Network Shared Links Tab"
 msgstr "Scheda Link Condivisi"
 
-#: include/features.php:87
+#: include/features.php:92
 msgid "Enable tab to display only Network posts with links in them"
 msgstr "Abilita la scheda per mostrare solo i post che contengono link"
 
-#: include/features.php:92
+#: include/features.php:97
 msgid "Post/Comment Tools"
 msgstr "Strumenti per messaggi/commenti"
 
-#: include/features.php:93
+#: include/features.php:98
 msgid "Multiple Deletion"
 msgstr "Eliminazione multipla"
 
-#: include/features.php:93
+#: include/features.php:98
 msgid "Select and delete multiple posts/comments at once"
 msgstr "Seleziona ed elimina vari messagi e commenti in una volta sola"
 
-#: include/features.php:94
+#: include/features.php:99
 msgid "Edit Sent Posts"
 msgstr "Modifica i post inviati"
 
-#: include/features.php:94
+#: include/features.php:99
 msgid "Edit and correct posts and comments after sending"
 msgstr "Modifica e correggi messaggi e commenti dopo averli inviati"
 
-#: include/features.php:95
+#: include/features.php:100
 msgid "Tagging"
 msgstr "Aggiunta tag"
 
-#: include/features.php:95
+#: include/features.php:100
 msgid "Ability to tag existing posts"
 msgstr "Permette di aggiungere tag ai post già esistenti"
 
-#: include/features.php:96
+#: include/features.php:101
 msgid "Post Categories"
 msgstr "Cateorie post"
 
-#: include/features.php:96
+#: include/features.php:101
 msgid "Add categories to your posts"
 msgstr "Aggiungi categorie ai tuoi post"
 
-#: include/features.php:97
+#: include/features.php:102
 msgid "Ability to file posts under folders"
 msgstr "Permette di archiviare i post in cartelle"
 
-#: include/features.php:98
+#: include/features.php:103
 msgid "Dislike Posts"
 msgstr "Non mi piace"
 
-#: include/features.php:98
+#: include/features.php:103
 msgid "Ability to dislike posts/comments"
 msgstr "Permetti di inviare \"non mi piace\" ai messaggi"
 
-#: include/features.php:99
+#: include/features.php:104
 msgid "Star Posts"
 msgstr "Post preferiti"
 
-#: include/features.php:99
+#: include/features.php:104
 msgid "Ability to mark special posts with a star indicator"
 msgstr "Permette di segnare i post preferiti con una stella"
 
-#: include/features.php:100
+#: include/features.php:105
 msgid "Mute Post Notifications"
 msgstr "Silenzia le notifiche di nuovi post"
 
-#: include/features.php:100
+#: include/features.php:105
 msgid "Ability to mute notifications for a thread"
 msgstr "Permette di silenziare le notifiche di nuovi post in una discussione"
 
-#: include/features.php:105
+#: include/features.php:110
 msgid "Advanced Profile Settings"
 msgstr "Impostazioni Avanzate Profilo"
 
-#: include/features.php:106
+#: include/features.php:111
 msgid "Show visitors public community forums at the Advanced Profile Page"
 msgstr "Mostra ai visitatori i forum nella pagina Profilo Avanzato"
 
@@ -6686,149 +6692,181 @@ msgstr "secondi"
 msgid "%1$d %2$s ago"
 msgstr "%1$d %2$s fa"
 
-#: include/datetime.php:474 include/items.php:2470
+#: include/datetime.php:474 include/items.php:2500
 #, php-format
 msgid "%s's birthday"
 msgstr "Compleanno di %s"
 
-#: include/datetime.php:475 include/items.php:2471
+#: include/datetime.php:475 include/items.php:2501
 #, php-format
 msgid "Happy Birthday %s"
 msgstr "Buon compleanno %s"
 
-#: include/identity.php:43
+#: include/identity.php:42
 msgid "Requested account is not available."
 msgstr "L'account richiesto non è disponibile."
 
-#: include/identity.php:96 include/identity.php:281 include/identity.php:652
+#: include/identity.php:95 include/identity.php:284 include/identity.php:662
 msgid "Edit profile"
 msgstr "Modifica il profilo"
 
-#: include/identity.php:241
+#: include/identity.php:244
 msgid "Atom feed"
 msgstr "Feed Atom"
 
-#: include/identity.php:246
+#: include/identity.php:249
 msgid "Message"
 msgstr "Messaggio"
 
-#: include/identity.php:252 include/nav.php:185
+#: include/identity.php:255 include/nav.php:185
 msgid "Profiles"
 msgstr "Profili"
 
-#: include/identity.php:252
+#: include/identity.php:255
 msgid "Manage/edit profiles"
 msgstr "Gestisci/modifica i profili"
 
-#: include/identity.php:412 include/identity.php:498
+#: include/identity.php:425 include/identity.php:509
 msgid "g A l F d"
 msgstr "g A l d F"
 
-#: include/identity.php:413 include/identity.php:499
+#: include/identity.php:426 include/identity.php:510
 msgid "F d"
 msgstr "d F"
 
-#: include/identity.php:458 include/identity.php:545
+#: include/identity.php:471 include/identity.php:556
 msgid "[today]"
 msgstr "[oggi]"
 
-#: include/identity.php:470
+#: include/identity.php:483
 msgid "Birthday Reminders"
 msgstr "Promemoria compleanni"
 
-#: include/identity.php:471
+#: include/identity.php:484
 msgid "Birthdays this week:"
 msgstr "Compleanni questa settimana:"
 
-#: include/identity.php:532
+#: include/identity.php:543
 msgid "[No description]"
 msgstr "[Nessuna descrizione]"
 
-#: include/identity.php:556
+#: include/identity.php:567
 msgid "Event Reminders"
 msgstr "Promemoria"
 
-#: include/identity.php:557
+#: include/identity.php:568
 msgid "Events this week:"
 msgstr "Eventi di questa settimana:"
 
-#: include/identity.php:585
+#: include/identity.php:595
 msgid "j F, Y"
 msgstr "j F Y"
 
-#: include/identity.php:586
+#: include/identity.php:596
 msgid "j F"
 msgstr "j F"
 
-#: include/identity.php:593
+#: include/identity.php:603
 msgid "Birthday:"
 msgstr "Compleanno:"
 
-#: include/identity.php:597
+#: include/identity.php:607
 msgid "Age:"
 msgstr "Età:"
 
-#: include/identity.php:606
+#: include/identity.php:616
 #, php-format
 msgid "for %1$d %2$s"
 msgstr "per %1$d %2$s"
 
-#: include/identity.php:619
+#: include/identity.php:629
 msgid "Religion:"
 msgstr "Religione:"
 
-#: include/identity.php:623
+#: include/identity.php:633
 msgid "Hobbies/Interests:"
 msgstr "Hobby/Interessi:"
 
-#: include/identity.php:630
+#: include/identity.php:640
 msgid "Contact information and Social Networks:"
 msgstr "Informazioni su contatti e social network:"
 
-#: include/identity.php:632
+#: include/identity.php:642
 msgid "Musical interests:"
 msgstr "Interessi musicali:"
 
-#: include/identity.php:634
+#: include/identity.php:644
 msgid "Books, literature:"
 msgstr "Libri, letteratura:"
 
-#: include/identity.php:636
+#: include/identity.php:646
 msgid "Television:"
 msgstr "Televisione:"
 
-#: include/identity.php:638
+#: include/identity.php:648
 msgid "Film/dance/culture/entertainment:"
 msgstr "Film/danza/cultura/intrattenimento:"
 
-#: include/identity.php:640
+#: include/identity.php:650
 msgid "Love/Romance:"
 msgstr "Amore:"
 
-#: include/identity.php:642
+#: include/identity.php:652
 msgid "Work/employment:"
 msgstr "Lavoro:"
 
-#: include/identity.php:644
+#: include/identity.php:654
 msgid "School/education:"
 msgstr "Scuola:"
 
-#: include/identity.php:648
+#: include/identity.php:658
 msgid "Forums:"
 msgstr "Forum:"
 
-#: include/identity.php:701 include/identity.php:704 include/nav.php:78
+#: include/identity.php:710 include/identity.php:713 include/nav.php:78
 msgid "Videos"
 msgstr "Video"
 
-#: include/identity.php:716 include/nav.php:140
+#: include/identity.php:725 include/nav.php:140
 msgid "Events and Calendar"
 msgstr "Eventi e calendario"
 
-#: include/identity.php:724
+#: include/identity.php:733
 msgid "Only You Can See This"
 msgstr "Solo tu puoi vedere questo"
 
+#: include/like.php:167 include/conversation.php:122
+#: include/conversation.php:258 include/text.php:1998
+#: view/theme/diabook/theme.php:463
+msgid "event"
+msgstr "l'evento"
+
+#: include/like.php:184 include/conversation.php:141 include/diaspora.php:2185
+#: view/theme/diabook/theme.php:480
+#, php-format
+msgid "%1$s likes %2$s's %3$s"
+msgstr "A %1$s piace %3$s di %2$s"
+
+#: include/like.php:186 include/conversation.php:144
+#, php-format
+msgid "%1$s doesn't like %2$s's %3$s"
+msgstr "A %1$s non piace %3$s di %2$s"
+
+#: include/like.php:188
+#, php-format
+msgid "%1$s is attending %2$s's %3$s"
+msgstr "%1$s parteciperà a %3$s di %2$s"
+
+#: include/like.php:190
+#, php-format
+msgid "%1$s is not attending %2$s's %3$s"
+msgstr "%1$s non parteciperà a %3$s di %2$s"
+
+#: include/like.php:192
+#, php-format
+msgid "%1$s may attend %2$s's %3$s"
+msgstr "%1$s forse parteciperà a %3$s di %2$s"
+
 #: include/acl_selectors.php:325
 msgid "Post to Email"
 msgstr "Invia a email"
@@ -6852,6 +6890,10 @@ msgstr "mostra"
 msgid "don't show"
 msgstr "non mostrare"
 
+#: include/acl_selectors.php:348
+msgid "Close"
+msgstr "Chiudi"
+
 #: include/message.php:15 include/message.php:173
 msgid "[no subject]"
 msgstr "[nessun oggetto]"
@@ -6860,31 +6902,31 @@ msgstr "[nessun oggetto]"
 msgid "stopped following"
 msgstr "tolto dai seguiti"
 
-#: include/Contact.php:361 include/conversation.php:911
+#: include/Contact.php:337 include/conversation.php:911
 msgid "View Status"
 msgstr "Visualizza stato"
 
-#: include/Contact.php:363 include/conversation.php:913
+#: include/Contact.php:339 include/conversation.php:913
 msgid "View Photos"
 msgstr "Visualizza foto"
 
-#: include/Contact.php:364 include/conversation.php:914
+#: include/Contact.php:340 include/conversation.php:914
 msgid "Network Posts"
 msgstr "Post della Rete"
 
-#: include/Contact.php:365 include/conversation.php:915
+#: include/Contact.php:341 include/conversation.php:915
 msgid "Edit Contact"
 msgstr "Modifica contatto"
 
-#: include/Contact.php:366
+#: include/Contact.php:342
 msgid "Drop Contact"
 msgstr "Rimuovi contatto"
 
-#: include/Contact.php:367 include/conversation.php:916
+#: include/Contact.php:343 include/conversation.php:916
 msgid "Send PM"
 msgstr "Invia messaggio privato"
 
-#: include/Contact.php:368 include/conversation.php:920
+#: include/Contact.php:344 include/conversation.php:920
 msgid "Poke"
 msgstr "Stuzzica"
 
@@ -6947,153 +6989,153 @@ msgstr "Cancella elementi selezionati"
 msgid "Follow Thread"
 msgstr "Segui la discussione"
 
-#: include/conversation.php:1035
+#: include/conversation.php:1034
 #, php-format
 msgid "%s likes this."
 msgstr "Piace a %s."
 
-#: include/conversation.php:1038
+#: include/conversation.php:1037
 #, php-format
 msgid "%s doesn't like this."
 msgstr "Non piace a %s."
 
-#: include/conversation.php:1041
+#: include/conversation.php:1040
 #, php-format
 msgid "%s attends."
 msgstr "%s partecipa."
 
-#: include/conversation.php:1044
+#: include/conversation.php:1043
 #, php-format
 msgid "%s doesn't attend."
 msgstr "%s non partecipa."
 
-#: include/conversation.php:1047
+#: include/conversation.php:1046
 #, php-format
 msgid "%s attends maybe."
 msgstr "%s forse partecipa."
 
-#: include/conversation.php:1057
+#: include/conversation.php:1056
 msgid "and"
 msgstr "e"
 
-#: include/conversation.php:1063
+#: include/conversation.php:1062
 #, php-format
 msgid ", and %d other people"
 msgstr "e altre %d persone"
 
-#: include/conversation.php:1072
+#: include/conversation.php:1071
 #, php-format
 msgid "<span  %1$s>%2$d people</span> like this"
 msgstr "Piace a <span %1$s>%2$d persone</span>."
 
-#: include/conversation.php:1073
+#: include/conversation.php:1072
 #, php-format
 msgid "%s like this."
 msgstr "a %s piace."
 
-#: include/conversation.php:1076
+#: include/conversation.php:1075
 #, php-format
 msgid "<span  %1$s>%2$d people</span> don't like this"
 msgstr "Non piace a <span %1$s>%2$d persone</span>."
 
-#: include/conversation.php:1077
+#: include/conversation.php:1076
 #, php-format
 msgid "%s don't like this."
 msgstr "a %s non piace."
 
-#: include/conversation.php:1080
+#: include/conversation.php:1079
 #, php-format
 msgid "<span  %1$s>%2$d people</span> attend"
 msgstr "<span  %1$s>%2$d persone</span> partecipano"
 
-#: include/conversation.php:1081
+#: include/conversation.php:1080
 #, php-format
 msgid "%s attend."
 msgstr "%s partecipa."
 
-#: include/conversation.php:1084
+#: include/conversation.php:1083
 #, php-format
 msgid "<span  %1$s>%2$d people</span> don't attend"
 msgstr "<span  %1$s>%2$d persone</span> non partecipano"
 
-#: include/conversation.php:1085
+#: include/conversation.php:1084
 #, php-format
 msgid "%s don't attend."
 msgstr "%s non partecipa."
 
-#: include/conversation.php:1088
+#: include/conversation.php:1087
 #, php-format
 msgid "<span  %1$s>%2$d people</span> anttend maybe"
 msgstr "<span  %1$s>%2$d persone</span> forse partecipano"
 
-#: include/conversation.php:1089
+#: include/conversation.php:1088
 #, php-format
 msgid "%s anttend maybe."
 msgstr "%s forse partecipano."
 
-#: include/conversation.php:1128 include/conversation.php:1146
+#: include/conversation.php:1127 include/conversation.php:1145
 msgid "Visible to <strong>everybody</strong>"
 msgstr "Visibile a <strong>tutti</strong>"
 
-#: include/conversation.php:1130 include/conversation.php:1148
+#: include/conversation.php:1129 include/conversation.php:1147
 msgid "Please enter a video link/URL:"
 msgstr "Inserisci un collegamento video / URL:"
 
-#: include/conversation.php:1131 include/conversation.php:1149
+#: include/conversation.php:1130 include/conversation.php:1148
 msgid "Please enter an audio link/URL:"
 msgstr "Inserisci un collegamento audio / URL:"
 
-#: include/conversation.php:1132 include/conversation.php:1150
+#: include/conversation.php:1131 include/conversation.php:1149
 msgid "Tag term:"
 msgstr "Tag:"
 
-#: include/conversation.php:1134 include/conversation.php:1152
+#: include/conversation.php:1133 include/conversation.php:1151
 msgid "Where are you right now?"
 msgstr "Dove sei ora?"
 
-#: include/conversation.php:1135
+#: include/conversation.php:1134
 msgid "Delete item(s)?"
 msgstr "Cancellare questo elemento/i?"
 
-#: include/conversation.php:1204
+#: include/conversation.php:1203
 msgid "permissions"
 msgstr "permessi"
 
-#: include/conversation.php:1227
+#: include/conversation.php:1226
 msgid "Post to Groups"
 msgstr "Invia ai Gruppi"
 
-#: include/conversation.php:1228
+#: include/conversation.php:1227
 msgid "Post to Contacts"
 msgstr "Invia ai Contatti"
 
-#: include/conversation.php:1229
+#: include/conversation.php:1228
 msgid "Private post"
 msgstr "Post privato"
 
-#: include/conversation.php:1386
+#: include/conversation.php:1385
 msgid "View all"
 msgstr "Mostra tutto"
 
-#: include/conversation.php:1408
+#: include/conversation.php:1407
 msgid "Like"
 msgid_plural "Likes"
 msgstr[0] "Mi piace"
 msgstr[1] "Mi piace"
 
-#: include/conversation.php:1411
+#: include/conversation.php:1410
 msgid "Dislike"
 msgid_plural "Dislikes"
 msgstr[0] "Non mi piace"
 msgstr[1] "Non mi piace"
 
-#: include/conversation.php:1417
+#: include/conversation.php:1416
 msgid "Not Attending"
 msgid_plural "Not Attending"
 msgstr[0] "Non partecipa"
 msgstr[1] "Non partecipano"
 
-#: include/conversation.php:1420 include/profile_selectors.php:6
+#: include/conversation.php:1419 include/profile_selectors.php:6
 msgid "Undecided"
 msgid_plural "Undecided"
 msgstr[0] "Indeciso"
@@ -7295,67 +7337,59 @@ msgstr "rilassato"
 msgid "surprised"
 msgstr "sorpreso"
 
-#: include/text.php:1497
+#: include/text.php:1504
 msgid "bytes"
 msgstr "bytes"
 
-#: include/text.php:1529 include/text.php:1541
+#: include/text.php:1536 include/text.php:1548
 msgid "Click to open/close"
 msgstr "Clicca per aprire/chiudere"
 
-#: include/text.php:1715
+#: include/text.php:1722
 msgid "View on separate page"
 msgstr "Vedi in una pagina separata"
 
-#: include/text.php:1716
+#: include/text.php:1723
 msgid "view on separate page"
 msgstr "vedi in una pagina separata"
 
-#: include/text.php:1995
+#: include/text.php:2002
 msgid "activity"
 msgstr "attività"
 
-#: include/text.php:1998
+#: include/text.php:2005
 msgid "post"
 msgstr "messaggio"
 
-#: include/text.php:2166
+#: include/text.php:2173
 msgid "Item filed"
 msgstr "Messaggio salvato"
 
-#: include/bbcode.php:483 include/bbcode.php:1143 include/bbcode.php:1144
+#: include/bbcode.php:482 include/bbcode.php:1157 include/bbcode.php:1158
 msgid "Image/photo"
 msgstr "Immagine/foto"
 
-#: include/bbcode.php:581
+#: include/bbcode.php:595
 #, php-format
 msgid "<a href=\"%1$s\" target=\"_blank\">%2$s</a> %3$s"
 msgstr "<a href=\"%1$s\" target=\"_blank\">%2$s</a> %3$s"
 
-#: include/bbcode.php:615
+#: include/bbcode.php:629
 #, php-format
 msgid ""
 "<span><a href=\"%s\" target=\"_blank\">%s</a> wrote the following <a "
 "href=\"%s\" target=\"_blank\">post</a>"
 msgstr "<span><a href=\"%s\" target=\"_blank\">%s</a> ha scritto il seguente <a href=\"%s\" target=\"_blank\">messaggio</a>"
 
-#: include/bbcode.php:1103 include/bbcode.php:1123
+#: include/bbcode.php:1117 include/bbcode.php:1137
 msgid "$1 wrote:"
 msgstr "$1 ha scritto:"
 
-#: include/bbcode.php:1152 include/bbcode.php:1153
+#: include/bbcode.php:1166 include/bbcode.php:1167
 msgid "Encrypted content"
 msgstr "Contenuto criptato"
 
-#: include/notifier.php:843 include/delivery.php:458
-msgid "(no subject)"
-msgstr "(nessun oggetto)"
-
-#: include/notifier.php:853 include/delivery.php:469 include/enotify.php:37
-msgid "noreply"
-msgstr "nessuna risposta"
-
-#: include/dba_pdo.php:72 include/dba.php:56
+#: include/dba_pdo.php:72 include/dba.php:55
 #, php-format
 msgid "Cannot locate DNS info for database server '%s'"
 msgstr "Non trovo le informazioni DNS per il database server '%s'"
@@ -7400,6 +7434,10 @@ msgstr "Ostatus"
 msgid "RSS/Atom"
 msgstr "RSS / Atom"
 
+#: include/contact_selectors.php:81
+msgid "Facebook"
+msgstr "Facebook"
+
 #: include/contact_selectors.php:82
 msgid "Zot!"
 msgstr "Zot!"
@@ -7444,7 +7482,7 @@ msgstr "App.net"
 msgid "Redmatrix"
 msgstr "Redmatrix"
 
-#: include/Scrape.php:610
+#: include/Scrape.php:624
 msgid " on Last.fm"
 msgstr "su Last.fm"
 
@@ -7620,46 +7658,21 @@ msgstr "Navigazione"
 msgid "Site map"
 msgstr "Mappa del sito"
 
-#: include/api.php:343 include/api.php:354 include/api.php:463
-#: include/api.php:1182 include/api.php:1184
-msgid "User not found."
-msgstr "Utente non trovato."
-
-#: include/api.php:830
+#: include/api.php:878
 #, php-format
 msgid "Daily posting limit of %d posts reached. The post was rejected."
 msgstr "Limite giornaliero di %d messaggi raggiunto. Il messaggio è stato rifiutato"
 
-#: include/api.php:849
+#: include/api.php:897
 #, php-format
 msgid "Weekly posting limit of %d posts reached. The post was rejected."
 msgstr "Limite settimanale di %d messaggi raggiunto. Il messaggio è stato rifiutato"
 
-#: include/api.php:868
+#: include/api.php:916
 #, php-format
 msgid "Monthly posting limit of %d posts reached. The post was rejected."
 msgstr "Limite mensile di %d messaggi raggiunto. Il messaggio è stato rifiutato"
 
-#: include/api.php:1391
-msgid "There is no status with this id."
-msgstr "Non c'è nessuno status con questo id."
-
-#: include/api.php:1465
-msgid "There is no conversation with this id."
-msgstr "Non c'è nessuna conversazione con questo id"
-
-#: include/api.php:1744
-msgid "Invalid item."
-msgstr "Elemento non valido."
-
-#: include/api.php:1754
-msgid "Invalid action. "
-msgstr "Azione non valida."
-
-#: include/api.php:1762
-msgid "DB error"
-msgstr "Errore database"
-
 #: include/user.php:48
 msgid "An invitation is required."
 msgstr "E' richiesto un invito."
@@ -7722,8 +7735,7 @@ msgstr "ERRORE GRAVE: La generazione delle chiavi di sicurezza è fallita."
 msgid "An error occurred during registration. Please try again."
 msgstr "C'è stato un errore durante la registrazione. Prova ancora."
 
-#: include/user.php:256 view/theme/clean/config.php:56
-#: view/theme/duepuntozero/config.php:44
+#: include/user.php:256 view/theme/duepuntozero/config.php:44
 msgid "default"
 msgstr "default"
 
@@ -7774,19 +7786,27 @@ msgid ""
 "\t\tThank you and welcome to %2$s."
 msgstr "\nI dettagli del tuo utente sono:\n    Indirizzo del sito: %3$s\n    Nome utente: %1$s\n    Password: %5$s\n\nPuoi cambiare la tua password dalla pagina delle impostazioni del tuo account dopo esserti autenticato.\n\nPer favore, prenditi qualche momento per esaminare tutte le impostazioni presenti.\n\nPotresti voler aggiungere qualche informazione di base al tuo profilo predefinito (nella pagina \"Profili\"), così che le altre persone possano trovarti più facilmente.\n\nTi raccomandiamo di inserire il tuo nome completo, aggiungere una foto, aggiungere qualche parola chiave del profilo (molto utili per trovare nuovi contatti), e magari in quale nazione vivi, se non vuoi essere più specifico di così.\n\nNoi rispettiamo appieno la tua privacy, e nessuna di queste informazioni è necessaria o obbligatoria.\nSe sei nuovo e non conosci nessuno qui, possono aiutarti a trovare qualche nuovo e interessante contatto.\n\nGrazie e benvenuto su %2$s"
 
-#: include/diaspora.php:719
+#: include/diaspora.php:720
 msgid "Sharing notification from Diaspora network"
 msgstr "Notifica di condivisione dal network Diaspora*"
 
-#: include/diaspora.php:2606
+#: include/diaspora.php:2625
 msgid "Attachments:"
 msgstr "Allegati:"
 
-#: include/items.php:4897
+#: include/delivery.php:533
+msgid "(no subject)"
+msgstr "(nessun oggetto)"
+
+#: include/delivery.php:544 include/enotify.php:37
+msgid "noreply"
+msgstr "nessuna risposta"
+
+#: include/items.php:4926
 msgid "Do you really want to delete this item?"
 msgstr "Vuoi veramente cancellare questo elemento?"
 
-#: include/items.php:5172
+#: include/items.php:5201
 msgid "Archives"
 msgstr "Archivi"
 
@@ -8302,11 +8322,11 @@ msgstr "Nome completo: %1$s\nIndirizzo del sito: %2$s\nNome utente: %3$s (%4$s)"
 msgid "Please visit %s to approve or reject the request."
 msgstr "Visita %s per approvare o rifiutare la richiesta."
 
-#: include/oembed.php:220
+#: include/oembed.php:226
 msgid "Embedded content"
 msgstr "Contenuto incorporato"
 
-#: include/oembed.php:229
+#: include/oembed.php:235
 msgid "Embedding disabled"
 msgstr "Embed disabilitato"
 
@@ -8346,7 +8366,7 @@ msgstr[1] "%d contatti non importati"
 msgid "Done. You can now login with your username and password"
 msgstr "Fatto. Ora puoi entrare con il tuo nome utente e la tua password"
 
-#: index.php:441
+#: index.php:442
 msgid "toggle mobile"
 msgstr "commuta tema mobile"
 
@@ -8364,7 +8384,6 @@ msgid "Set theme width"
 msgstr "Imposta la larghezza del tema"
 
 #: view/theme/cleanzero/config.php:86 view/theme/quattro/config.php:68
-#: view/theme/clean/config.php:88
 msgid "Color scheme"
 msgstr "Schema colori"
 
@@ -8486,56 +8505,6 @@ msgstr "Livello di zoom per Earth Layers"
 msgid "Show/hide boxes at right-hand column:"
 msgstr "Mostra/Nascondi riquadri nella colonna destra"
 
-#: view/theme/clean/config.php:57
-msgid "Midnight"
-msgstr "Mezzanotte"
-
-#: view/theme/clean/config.php:58
-msgid "Zenburn"
-msgstr "Zenburn"
-
-#: view/theme/clean/config.php:59
-msgid "Bootstrap"
-msgstr "Bootstrap"
-
-#: view/theme/clean/config.php:60
-msgid "Shades of Pink"
-msgstr "Sfumature di Rosa"
-
-#: view/theme/clean/config.php:61
-msgid "Lime and Orange"
-msgstr "Lime e Arancia"
-
-#: view/theme/clean/config.php:62
-msgid "GeoCities Retro"
-msgstr "GeoCities Retro"
-
-#: view/theme/clean/config.php:86
-msgid "Background Image"
-msgstr "Immagine di sfondo"
-
-#: view/theme/clean/config.php:86
-msgid ""
-"The URL to a picture (e.g. from your photo album) that should be used as "
-"background image."
-msgstr "L'URL a un'immagine (p.e. dal tuo album foto) che viene usata come immagine di sfondo."
-
-#: view/theme/clean/config.php:87
-msgid "Background Color"
-msgstr "Colore di sfondo"
-
-#: view/theme/clean/config.php:87
-msgid "HEX value for the background color. Don't include the #"
-msgstr "Valore esadecimale del colore di sfondo. Non includere il #"
-
-#: view/theme/clean/config.php:89
-msgid "font size"
-msgstr "dimensione font"
-
-#: view/theme/clean/config.php:89
-msgid "base font size for your interface"
-msgstr "dimensione di base dei font per la tua interfaccia"
-
 #: view/theme/vier/config.php:64
 msgid "Comma separated list of helper forums"
 msgstr "Lista separata da virgola di forum di aiuto"
index fb30c735738cae439d91a36aac05e74257fc7d7b..606ae1e16ca2e64f08bab8491ee11cbc73aecb7c 100644 (file)
@@ -8,8 +8,8 @@ function string_plural_select_it($n){
 $a->strings["Network:"] = "Rete:";
 $a->strings["Forum"] = "Forum";
 $a->strings["%d contact edited."] = array(
-       0 => "%d contatto modificato",
-       1 => "%d contatti modificati",
+       0 => "",
+       1 => "",
 );
 $a->strings["Could not access contact record."] = "Non è possibile accedere al contatto.";
 $a->strings["Could not locate selected profile."] = "Non riesco a trovare il profilo selezionato.";
@@ -142,9 +142,6 @@ $a->strings["Edit your <strong>default</strong> profile to your liking. Review t
 $a->strings["Profile Keywords"] = "Parole chiave del profilo";
 $a->strings["Set some public keywords for your default profile which describe your interests. We may be able to find other people with similar interests and suggest friendships."] = "Inserisci qualche parola chiave pubblica nel tuo profilo predefinito che descriva i tuoi interessi. Potremmo essere in grado di trovare altre persone con interessi similari e suggerirti delle amicizie.";
 $a->strings["Connecting"] = "Collegarsi";
-$a->strings["Facebook"] = "Facebook";
-$a->strings["Authorise the Facebook Connector if you currently have a Facebook account and we will (optionally) import all your Facebook friends and conversations."] = "Autorizza il Facebook Connector se hai un account Facebook, e noi (opzionalmente) importeremo tuti i tuoi amici e le tue conversazioni da Facebook.";
-$a->strings["<em>If</em> this is your own personal server, installing the Facebook addon may ease your transition to the free social web."] = "<em>Se</em questo è il tuo server personale, installare il plugin per Facebook puo' aiutarti nella transizione verso il web sociale libero.";
 $a->strings["Importing Emails"] = "Importare le Email";
 $a->strings["Enter your email access information on your Connector Settings page if you wish to import and interact with friends or mailing lists from your email INBOX"] = "Inserisci i tuoi dati di accesso all'email nella tua pagina Impostazioni Connettori se vuoi importare e interagire con amici o mailing list dalla tua casella di posta in arrivo";
 $a->strings["Go to Your Contacts Page"] = "Vai alla tua pagina Contatti";
@@ -189,7 +186,7 @@ $a->strings["Tag removed"] = "Tag rimosso";
 $a->strings["Remove Item Tag"] = "Rimuovi il tag";
 $a->strings["Select a tag to remove: "] = "Seleziona un tag da rimuovere: ";
 $a->strings["Remove"] = "Rimuovi";
-$a->strings["Subsribing to OStatus contacts"] = "Iscrizione a contatti OStatus";
+$a->strings["Subscribing to OStatus contacts"] = "";
 $a->strings["No contact provided."] = "Nessun contatto disponibile.";
 $a->strings["Couldn't fetch information for contact."] = "Non è stato possibile recuperare le informazioni del contatto.";
 $a->strings["Couldn't fetch friends for contact."] = "Non è stato possibile recuperare gli amici del contatto.";
@@ -290,12 +287,6 @@ $a->strings["Forgot your Password?"] = "Hai dimenticato la password?";
 $a->strings["Enter your email address and submit to have your password reset. Then check your email for further instructions."] = "Inserisci il tuo indirizzo email per reimpostare la password.";
 $a->strings["Nickname or Email: "] = "Nome utente o email: ";
 $a->strings["Reset"] = "Reimposta";
-$a->strings["event"] = "l'evento";
-$a->strings["%1\$s likes %2\$s's %3\$s"] = "A %1\$s piace %3\$s di %2\$s";
-$a->strings["%1\$s doesn't like %2\$s's %3\$s"] = "A %1\$s non piace %3\$s di %2\$s";
-$a->strings["%1\$s is attending %2\$s's %3\$s"] = "%1\$s parteciperà a %3\$s di %2\$s";
-$a->strings["%1\$s is not attending %2\$s's %3\$s"] = "%1\$s non parteciperà a %3\$s di %2\$s";
-$a->strings["%1\$s may attend %2\$s's %3\$s"] = "%1\$s forse parteciperà a %3\$s di %2\$s";
 $a->strings["{0} wants to be your friend"] = "{0} vuole essere tuo amico";
 $a->strings["{0} sent you a message"] = "{0} ti ha inviato un messaggio";
 $a->strings["{0} requested registration"] = "{0} chiede la registrazione";
@@ -425,16 +416,22 @@ $a->strings["Site"] = "Sito";
 $a->strings["Users"] = "Utenti";
 $a->strings["Plugins"] = "Plugin";
 $a->strings["Themes"] = "Temi";
+$a->strings["Additional features"] = "Funzionalità aggiuntive";
 $a->strings["DB updates"] = "Aggiornamenti Database";
 $a->strings["Inspect Queue"] = "Ispeziona Coda di invio";
+$a->strings["Federation Statistics"] = "";
 $a->strings["Logs"] = "Log";
+$a->strings["View Logs"] = "";
 $a->strings["probe address"] = "controlla indirizzo";
 $a->strings["check webfinger"] = "verifica webfinger";
 $a->strings["Admin"] = "Amministrazione";
 $a->strings["Plugin Features"] = "Impostazioni Plugins";
 $a->strings["diagnostics"] = "diagnostiche";
 $a->strings["User registrations waiting for confirmation"] = "Utenti registrati in attesa di conferma";
+$a->strings["This page offers you some numbers to the known part of the federated social network your Friendica node is part of. These numbers are not complete but only reflect the part of the network your node is aware of."] = "";
+$a->strings["The <em>Auto Discovered Contact Directory</em> feature is not enabled, it will improve the data displayed here."] = "";
 $a->strings["Administration"] = "Amministrazione";
+$a->strings["Currently this node is aware of %d nodes from the following platforms:"] = "";
 $a->strings["ID"] = "ID";
 $a->strings["Recipient Name"] = "Nome Destinatario";
 $a->strings["Recipient Profile"] = "Profilo Destinatario";
@@ -585,6 +582,8 @@ $a->strings["Maximum Load Average (Frontend)"] = "Media Massimo Carico (Frontend
 $a->strings["Maximum system load before the frontend quits service - default 50."] = "Massimo carico di sistema prima che il frontend fermi il servizio - default 50.";
 $a->strings["Maximum table size for optimization"] = "Dimensione massima della tabella per l'ottimizzazione";
 $a->strings["Maximum table size (in MB) for the automatic optimization - default 100 MB. Enter -1 to disable it."] = "La dimensione massima (in MB) per l'ottimizzazione automatica - default 100 MB. Inserisci -1 per disabilitarlo.";
+$a->strings["Minimum level of fragmentation"] = "";
+$a->strings["Minimum fragmenation level to start the automatic optimization - default value is 30%."] = "";
 $a->strings["Periodical check of global contacts"] = "Check periodico dei contatti globali";
 $a->strings["If enabled, the global contacts are checked periodically for missing or outdated data and the vitality of the contacts and servers."] = "Se abilitato, i contatti globali sono controllati periodicamente per verificare dati mancanti o sorpassati e la vitaltà dei contatti e dei server.";
 $a->strings["Days between requery"] = "Giorni tra le richieste";
@@ -684,9 +683,11 @@ $a->strings["Toggle"] = "Inverti";
 $a->strings["Author: "] = "Autore: ";
 $a->strings["Maintainer: "] = "Manutentore: ";
 $a->strings["Reload active plugins"] = "Ricarica i plugin attivi";
+$a->strings["There are currently no plugins available on your node. You can find the official plugin repository at %1\$s and might find other interesting plugins in the open plugin registry at %2\$s"] = "";
 $a->strings["No themes found."] = "Nessun tema trovato.";
 $a->strings["Screenshot"] = "Anteprima";
 $a->strings["Reload active themes"] = "Ricarica i temi attivi";
+$a->strings["No themes found on the system. They should be paced in %1\$s"] = "";
 $a->strings["[Experimental]"] = "[Sperimentale]";
 $a->strings["[Unsupported]"] = "[Non supportato]";
 $a->strings["Log settings updated."] = "Impostazioni Log aggiornate.";
@@ -695,11 +696,12 @@ $a->strings["Enable Debugging"] = "Abilita Debugging";
 $a->strings["Log file"] = "File di Log";
 $a->strings["Must be writable by web server. Relative to your Friendica top-level directory."] = "Deve essere scrivibile dal server web. Relativo alla tua directory Friendica.";
 $a->strings["Log level"] = "Livello di Log";
-$a->strings["Close"] = "Chiudi";
-$a->strings["FTP Host"] = "Indirizzo FTP";
-$a->strings["FTP Path"] = "Percorso FTP";
-$a->strings["FTP User"] = "Utente FTP";
-$a->strings["FTP Password"] = "Pasword FTP";
+$a->strings["PHP logging"] = "";
+$a->strings["To enable logging of PHP errors and warnings you can add the following to the .htconfig.php file of your installation. The filename set in the 'error_log' line is relative to the friendica top-level directory and must be writeable by the web server. The option '1' for 'log_errors' and 'display_errors' is to enable these options, set to '0' to disable them."] = "";
+$a->strings["Off"] = "Spento";
+$a->strings["On"] = "Acceso";
+$a->strings["Lock feature %s"] = "";
+$a->strings["Manage Additional Features"] = "";
 $a->strings["Search Results For: %s"] = "Risultato della ricerca per: %s";
 $a->strings["Remove term"] = "Rimuovi termine";
 $a->strings["Saved Searches"] = "Ricerche salvate";
@@ -921,7 +923,6 @@ $a->strings["Not available."] = "Non disponibile.";
 $a->strings["Community"] = "Comunità";
 $a->strings["No results."] = "Nessun risultato.";
 $a->strings["everybody"] = "tutti";
-$a->strings["Additional features"] = "Funzionalità aggiuntive";
 $a->strings["Display"] = "Visualizzazione";
 $a->strings["Social Networks"] = "Social Networks";
 $a->strings["Delegations"] = "Delegazioni";
@@ -958,8 +959,6 @@ $a->strings["No name"] = "Nessun nome";
 $a->strings["Remove authorization"] = "Rimuovi l'autorizzazione";
 $a->strings["No Plugin settings configured"] = "Nessun plugin ha impostazioni modificabili";
 $a->strings["Plugin Settings"] = "Impostazioni plugin";
-$a->strings["Off"] = "Spento";
-$a->strings["On"] = "Acceso";
 $a->strings["Additional Features"] = "Funzionalità aggiuntive";
 $a->strings["General Social Media Settings"] = "Impostazioni Media Sociali";
 $a->strings["Disable intelligent shortening"] = "Disabilita accorciamento intelligente";
@@ -1106,12 +1105,12 @@ $a->strings["Friends are advised to please try again in 24 hours."] = "Gli amici
 $a->strings["Invalid locator"] = "Invalid locator";
 $a->strings["Invalid email address."] = "Indirizzo email non valido.";
 $a->strings["This account has not been configured for email. Request failed."] = "Questo account non è stato configurato per l'email. Richiesta fallita.";
-$a->strings["Unable to resolve your name at the provided location."] = "Impossibile risolvere il tuo nome nella posizione indicata.";
 $a->strings["You have already introduced yourself here."] = "Ti sei già presentato qui.";
 $a->strings["Apparently you are already friends with %s."] = "Pare che tu e %s siate già amici.";
 $a->strings["Invalid profile URL."] = "Indirizzo profilo non valido.";
 $a->strings["Disallowed profile URL."] = "Indirizzo profilo non permesso.";
 $a->strings["Your introduction has been sent."] = "La tua presentazione è stata inviata.";
+$a->strings["Remote subscription can't be done for your network. Please subscribe directly on your system."] = "";
 $a->strings["Please login to confirm introduction."] = "Accedi per confermare la presentazione.";
 $a->strings["Incorrect identity currently logged in. Please login to <strong>this</strong> profile."] = "Non hai fatto accesso con l'identità corretta. Accedi a <strong>questo</strong> profilo.";
 $a->strings["Confirm"] = "Conferma";
@@ -1308,7 +1307,7 @@ $a->strings["poke, prod or do other things to somebody"] = "tocca, pungola o fai
 $a->strings["Recipient"] = "Destinatario";
 $a->strings["Choose what you wish to do to recipient"] = "Scegli cosa vuoi fare al destinatario";
 $a->strings["Make this post private"] = "Rendi questo post privato";
-$a->strings["Resubsribing to OStatus contacts"] = "Reiscrizione a contatti OStatus";
+$a->strings["Resubscribing to OStatus contacts"] = "";
 $a->strings["Error"] = "Errore";
 $a->strings["Total invitation limit exceeded."] = "Limite totale degli inviti superato.";
 $a->strings["%s : Not a valid email address."] = "%s: non è un indirizzo email valido.";
@@ -1321,12 +1320,12 @@ $a->strings["%d message sent."] = array(
 );
 $a->strings["You have no more invitations available"] = "Non hai altri inviti disponibili";
 $a->strings["Visit %s for a list of public sites that you can join. Friendica members on other sites can all connect with each other, as well as with members of many other social networks."] = "Visita %s per una lista di siti pubblici a cui puoi iscriverti. I membri Friendica su altri siti possono collegarsi uno con l'altro, come con membri di molti altri social network.";
-$a->strings["To accept this invitation, please visit and register at %s or any other public Friendica website."] = "Per accettare questo invito, visita e resitrati su %s o su un'altro sito web Friendica aperto al pubblico.";
+$a->strings["To accept this invitation, please visit and register at %s or any other public Friendica website."] = "Per accettare questo invito, visita e registrati su %s o su un'altro sito web Friendica aperto al pubblico.";
 $a->strings["Friendica sites all inter-connect to create a huge privacy-enhanced social web that is owned and controlled by its members. They can also connect with many traditional social networks. See %s for a list of alternate Friendica sites you can join."] = "I siti Friendica son tutti collegati tra loro per creare una grossa rete sociale rispettosa della privacy, posseduta e controllata dai suoi membri. I siti Friendica possono anche collegarsi a molti altri social network tradizionali. Vai su %s per una lista di siti Friendica alternativi a cui puoi iscriverti.";
 $a->strings["Our apologies. This system is not currently configured to connect with other public sites or invite members."] = "Ci scusiamo, questo sistema non è configurato per collegarsi con altri siti pubblici o per invitare membri.";
 $a->strings["Send invitations"] = "Invia inviti";
 $a->strings["Enter email addresses, one per line:"] = "Inserisci gli indirizzi email, uno per riga:";
-$a->strings["You are cordially invited to join me and other close friends on Friendica - and help us to create a better social web."] = "Sei cordialmente invitato a unirti a me ed ad altri amici su Friendica, e ad aiutarci a creare una rete sociale migliore.";
+$a->strings["You are cordially invited to join me and other close friends on Friendica - and help us to create a better social web."] = "Sei cordialmente invitato/a ad unirti a me e ad altri amici su Friendica, e ad aiutarci a creare una rete sociale migliore.";
 $a->strings["You will need to supply this invitation code: \$invite_code"] = "Sarà necessario fornire questo codice invito: \$invite_code";
 $a->strings["Once you have registered, please connect with me via my profile page at:"] = "Una volta registrato, connettiti con me dal mio profilo:";
 $a->strings["For more information about the Friendica project and why we feel it is important, please visit http://friendica.com"] = "Per maggiori informazioni sul progetto Friendica e perchè pensiamo sia importante, visita http://friendica.com";
@@ -1563,11 +1562,18 @@ $a->strings["Forums:"] = "Forum:";
 $a->strings["Videos"] = "Video";
 $a->strings["Events and Calendar"] = "Eventi e calendario";
 $a->strings["Only You Can See This"] = "Solo tu puoi vedere questo";
+$a->strings["event"] = "l'evento";
+$a->strings["%1\$s likes %2\$s's %3\$s"] = "A %1\$s piace %3\$s di %2\$s";
+$a->strings["%1\$s doesn't like %2\$s's %3\$s"] = "A %1\$s non piace %3\$s di %2\$s";
+$a->strings["%1\$s is attending %2\$s's %3\$s"] = "%1\$s parteciperà a %3\$s di %2\$s";
+$a->strings["%1\$s is not attending %2\$s's %3\$s"] = "%1\$s non parteciperà a %3\$s di %2\$s";
+$a->strings["%1\$s may attend %2\$s's %3\$s"] = "%1\$s forse parteciperà a %3\$s di %2\$s";
 $a->strings["Post to Email"] = "Invia a email";
 $a->strings["Connectors disabled, since \"%s\" is enabled."] = "Connettore disabilitato, dato che \"%s\" è abilitato.";
 $a->strings["Visible to everybody"] = "Visibile a tutti";
 $a->strings["show"] = "mostra";
 $a->strings["don't show"] = "non mostrare";
+$a->strings["Close"] = "Chiudi";
 $a->strings["[no subject]"] = "[nessun oggetto]";
 $a->strings["stopped following"] = "tolto dai seguiti";
 $a->strings["View Status"] = "Visualizza stato";
@@ -1697,8 +1703,6 @@ $a->strings["<a href=\"%1\$s\" target=\"_blank\">%2\$s</a> %3\$s"] = "<a href=\"
 $a->strings["<span><a href=\"%s\" target=\"_blank\">%s</a> wrote the following <a href=\"%s\" target=\"_blank\">post</a>"] = "<span><a href=\"%s\" target=\"_blank\">%s</a> ha scritto il seguente <a href=\"%s\" target=\"_blank\">messaggio</a>";
 $a->strings["$1 wrote:"] = "$1 ha scritto:";
 $a->strings["Encrypted content"] = "Contenuto criptato";
-$a->strings["(no subject)"] = "(nessun oggetto)";
-$a->strings["noreply"] = "nessuna risposta";
 $a->strings["Cannot locate DNS info for database server '%s'"] = "Non trovo le informazioni DNS per il database server '%s'";
 $a->strings["Unknown | Not categorised"] = "Sconosciuto | non categorizzato";
 $a->strings["Block immediately"] = "Blocca immediatamente";
@@ -1710,6 +1714,7 @@ $a->strings["Weekly"] = "Settimanalmente";
 $a->strings["Monthly"] = "Mensilmente";
 $a->strings["OStatus"] = "Ostatus";
 $a->strings["RSS/Atom"] = "RSS / Atom";
+$a->strings["Facebook"] = "Facebook";
 $a->strings["Zot!"] = "Zot!";
 $a->strings["LinkedIn"] = "LinkedIn";
 $a->strings["XMPP/IM"] = "XMPP/IM";
@@ -1765,15 +1770,9 @@ $a->strings["Manage/edit friends and contacts"] = "Gestisci/modifica amici e con
 $a->strings["Site setup and configuration"] = "Configurazione del sito";
 $a->strings["Navigation"] = "Navigazione";
 $a->strings["Site map"] = "Mappa del sito";
-$a->strings["User not found."] = "Utente non trovato.";
 $a->strings["Daily posting limit of %d posts reached. The post was rejected."] = "Limite giornaliero di %d messaggi raggiunto. Il messaggio è stato rifiutato";
 $a->strings["Weekly posting limit of %d posts reached. The post was rejected."] = "Limite settimanale di %d messaggi raggiunto. Il messaggio è stato rifiutato";
 $a->strings["Monthly posting limit of %d posts reached. The post was rejected."] = "Limite mensile di %d messaggi raggiunto. Il messaggio è stato rifiutato";
-$a->strings["There is no status with this id."] = "Non c'è nessuno status con questo id.";
-$a->strings["There is no conversation with this id."] = "Non c'è nessuna conversazione con questo id";
-$a->strings["Invalid item."] = "Elemento non valido.";
-$a->strings["Invalid action. "] = "Azione non valida.";
-$a->strings["DB error"] = "Errore database";
 $a->strings["An invitation is required."] = "E' richiesto un invito.";
 $a->strings["Invitation could not be verified."] = "L'invito non puo' essere verificato.";
 $a->strings["Invalid OpenID url"] = "Url OpenID non valido";
@@ -1796,6 +1795,8 @@ $a->strings["\n\t\tDear %1\$s,\n\t\t\tThank you for registering at %2\$s. Your a
 $a->strings["\n\t\tThe login details are as follows:\n\t\t\tSite Location:\t%3\$s\n\t\t\tLogin Name:\t%1\$s\n\t\t\tPassword:\t%5\$s\n\n\t\tYou may change your password from your account \"Settings\" page after logging\n\t\tin.\n\n\t\tPlease take a few moments to review the other account settings on that page.\n\n\t\tYou may also wish to add some basic information to your default profile\n\t\t(on the \"Profiles\" page) so that other people can easily find you.\n\n\t\tWe recommend setting your full name, adding a profile photo,\n\t\tadding some profile \"keywords\" (very useful in making new friends) - and\n\t\tperhaps what country you live in; if you do not wish to be more specific\n\t\tthan that.\n\n\t\tWe fully respect your right to privacy, and none of these items are necessary.\n\t\tIf you are new and do not know anybody here, they may help\n\t\tyou to make some new and interesting friends.\n\n\n\t\tThank you and welcome to %2\$s."] = "\nI dettagli del tuo utente sono:\n    Indirizzo del sito: %3\$s\n    Nome utente: %1\$s\n    Password: %5\$s\n\nPuoi cambiare la tua password dalla pagina delle impostazioni del tuo account dopo esserti autenticato.\n\nPer favore, prenditi qualche momento per esaminare tutte le impostazioni presenti.\n\nPotresti voler aggiungere qualche informazione di base al tuo profilo predefinito (nella pagina \"Profili\"), così che le altre persone possano trovarti più facilmente.\n\nTi raccomandiamo di inserire il tuo nome completo, aggiungere una foto, aggiungere qualche parola chiave del profilo (molto utili per trovare nuovi contatti), e magari in quale nazione vivi, se non vuoi essere più specifico di così.\n\nNoi rispettiamo appieno la tua privacy, e nessuna di queste informazioni è necessaria o obbligatoria.\nSe sei nuovo e non conosci nessuno qui, possono aiutarti a trovare qualche nuovo e interessante contatto.\n\nGrazie e benvenuto su %2\$s";
 $a->strings["Sharing notification from Diaspora network"] = "Notifica di condivisione dal network Diaspora*";
 $a->strings["Attachments:"] = "Allegati:";
+$a->strings["(no subject)"] = "(nessun oggetto)";
+$a->strings["noreply"] = "nessuna risposta";
 $a->strings["Do you really want to delete this item?"] = "Vuoi veramente cancellare questo elemento?";
 $a->strings["Archives"] = "Archivi";
 $a->strings["Male"] = "Maschio";
@@ -1956,18 +1957,6 @@ $a->strings["Your personal photos"] = "Le tue foto personali";
 $a->strings["Local Directory"] = "Elenco Locale";
 $a->strings["Set zoomfactor for Earth Layers"] = "Livello di zoom per Earth Layers";
 $a->strings["Show/hide boxes at right-hand column:"] = "Mostra/Nascondi riquadri nella colonna destra";
-$a->strings["Midnight"] = "Mezzanotte";
-$a->strings["Zenburn"] = "Zenburn";
-$a->strings["Bootstrap"] = "Bootstrap";
-$a->strings["Shades of Pink"] = "Sfumature di Rosa";
-$a->strings["Lime and Orange"] = "Lime e Arancia";
-$a->strings["GeoCities Retro"] = "GeoCities Retro";
-$a->strings["Background Image"] = "Immagine di sfondo";
-$a->strings["The URL to a picture (e.g. from your photo album) that should be used as background image."] = "L'URL a un'immagine (p.e. dal tuo album foto) che viene usata come immagine di sfondo.";
-$a->strings["Background Color"] = "Colore di sfondo";
-$a->strings["HEX value for the background color. Don't include the #"] = "Valore esadecimale del colore di sfondo. Non includere il #";
-$a->strings["font size"] = "dimensione font";
-$a->strings["base font size for your interface"] = "dimensione di base dei font per la tua interfaccia";
 $a->strings["Comma separated list of helper forums"] = "Lista separata da virgola di forum di aiuto";
 $a->strings["Set style"] = "Imposta stile";
 $a->strings["Quick Start"] = "Quick Start";
index ba81fdb03bfb1ec8915fbe99aa57351d56deab0c..5ae713daf2fe39080bd983cebba29cdfc4c4859f 100644 (file)
@@ -9,15 +9,15 @@
 # Gert Cauwenberg <gcauwenberg@gmail.com>, 2013
 # jeroenpraat <jeroenpraat@xs4all.nl>, 2012-2014
 # jeroenpraat <jeroenpraat@xs4all.nl>, 2012
-# Karel Vandecandelaere <karel@dasrakel.eu>, 2015
+# Karel Vandecandelaere <karel@dasrakel.eu>, 2015-2016
 # Ralph <pcbee4you@gmail.com>, 2015
 msgid ""
 msgstr ""
 "Project-Id-Version: friendica\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-11-08 21:46+0100\n"
-"PO-Revision-Date: 2015-11-17 23:49+0000\n"
-"Last-Translator: Ralph <pcbee4you@gmail.com>\n"
+"POT-Creation-Date: 2016-01-18 18:49+0100\n"
+"PO-Revision-Date: 2016-01-20 13:13+0000\n"
+"Last-Translator: Karel Vandecandelaere <karel@dasrakel.eu>\n"
 "Language-Team: Dutch (http://www.transifex.com/Friendica/friendica/language/nl/)\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
@@ -25,233 +25,182 @@ msgstr ""
 "Language: nl\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#: mod/contacts.php:118
+#: mod/contacts.php:50 include/identity.php:395
+msgid "Network:"
+msgstr "Netwerk:"
+
+#: mod/contacts.php:51 mod/contacts.php:961 mod/videos.php:37
+#: mod/viewcontacts.php:105 mod/dirfind.php:214 mod/network.php:598
+#: mod/allfriends.php:77 mod/match.php:82 mod/directory.php:172
+#: mod/common.php:123 mod/suggest.php:95 mod/photos.php:41
+#: include/identity.php:298
+msgid "Forum"
+msgstr "Forum"
+
+#: mod/contacts.php:128
 #, php-format
 msgid "%d contact edited."
 msgid_plural "%d contacts edited"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d contact gewijzigd."
+msgstr[1] "%d contacten gewijzigd."
 
-#: mod/contacts.php:149 mod/contacts.php:372
+#: mod/contacts.php:159 mod/contacts.php:383
 msgid "Could not access contact record."
 msgstr "Kon geen toegang krijgen tot de contactgegevens"
 
-#: mod/contacts.php:163
+#: mod/contacts.php:173
 msgid "Could not locate selected profile."
 msgstr "Kon het geselecteerde profiel niet vinden."
 
-#: mod/contacts.php:196
+#: mod/contacts.php:206
 msgid "Contact updated."
 msgstr "Contact bijgewerkt."
 
-#: mod/contacts.php:198 mod/dfrn_request.php:576
+#: mod/contacts.php:208 mod/dfrn_request.php:575
 msgid "Failed to update contact record."
 msgstr "Ik kon de contactgegevens niet aanpassen."
 
-#: mod/contacts.php:354 mod/manage.php:96 mod/display.php:496
-#: mod/profile_photo.php:19 mod/profile_photo.php:169
-#: mod/profile_photo.php:180 mod/profile_photo.php:193
-#: mod/ostatus_subscribe.php:9 mod/follow.php:10 mod/follow.php:72
-#: mod/follow.php:137 mod/item.php:169 mod/item.php:185 mod/group.php:19
+#: mod/contacts.php:365 mod/manage.php:96 mod/display.php:504
+#: mod/profile_photo.php:19 mod/profile_photo.php:175
+#: mod/profile_photo.php:186 mod/profile_photo.php:199
+#: mod/ostatus_subscribe.php:9 mod/follow.php:11 mod/follow.php:73
+#: mod/follow.php:155 mod/item.php:180 mod/item.php:192 mod/group.php:19
 #: mod/dfrn_confirm.php:55 mod/fsuggest.php:78 mod/wall_upload.php:77
-#: mod/wall_upload.php:80 mod/viewcontacts.php:24 mod/notifications.php:69
-#: mod/message.php:45 mod/message.php:181 mod/crepair.php:120
+#: mod/wall_upload.php:80 mod/viewcontacts.php:40 mod/notifications.php:69
+#: mod/message.php:45 mod/message.php:181 mod/crepair.php:117
 #: mod/dirfind.php:11 mod/nogroup.php:25 mod/network.php:4
-#: mod/allfriends.php:11 mod/events.php:165 mod/wallmessage.php:9
+#: mod/allfriends.php:12 mod/events.php:165 mod/wallmessage.php:9
 #: mod/wallmessage.php:33 mod/wallmessage.php:79 mod/wallmessage.php:103
 #: mod/wall_attach.php:67 mod/wall_attach.php:70 mod/settings.php:20
-#: mod/settings.php:116 mod/settings.php:635 mod/register.php:42
-#: mod/delegate.php:12 mod/common.php:17 mod/mood.php:114 mod/suggest.php:58
+#: mod/settings.php:126 mod/settings.php:646 mod/register.php:42
+#: mod/delegate.php:12 mod/common.php:18 mod/mood.php:114 mod/suggest.php:58
 #: mod/profiles.php:165 mod/profiles.php:615 mod/editpost.php:10
 #: mod/api.php:26 mod/api.php:31 mod/notes.php:22 mod/poke.php:149
 #: mod/repair_ostatus.php:9 mod/invite.php:15 mod/invite.php:101
-#: mod/photos.php:163 mod/photos.php:1097 mod/regmod.php:110
-#: mod/uimport.php:23 mod/attach.php:33 include/items.php:5103 index.php:382
+#: mod/photos.php:171 mod/photos.php:1105 mod/regmod.php:110
+#: mod/uimport.php:23 mod/attach.php:33 include/items.php:5073 index.php:383
 msgid "Permission denied."
 msgstr "Toegang geweigerd"
 
-#: mod/contacts.php:393
+#: mod/contacts.php:404
 msgid "Contact has been blocked"
 msgstr "Contact is geblokkeerd"
 
-#: mod/contacts.php:393
+#: mod/contacts.php:404
 msgid "Contact has been unblocked"
 msgstr "Contact is gedeblokkeerd"
 
-#: mod/contacts.php:404
+#: mod/contacts.php:415
 msgid "Contact has been ignored"
 msgstr "Contact wordt genegeerd"
 
-#: mod/contacts.php:404
+#: mod/contacts.php:415
 msgid "Contact has been unignored"
 msgstr "Contact wordt niet meer genegeerd"
 
-#: mod/contacts.php:416
+#: mod/contacts.php:427
 msgid "Contact has been archived"
 msgstr "Contact is gearchiveerd"
 
-#: mod/contacts.php:416
+#: mod/contacts.php:427
 msgid "Contact has been unarchived"
 msgstr "Contact is niet meer gearchiveerd"
 
-#: mod/contacts.php:443 mod/contacts.php:816
+#: mod/contacts.php:454 mod/contacts.php:802
 msgid "Do you really want to delete this contact?"
 msgstr "Wil je echt dit contact verwijderen?"
 
-#: mod/contacts.php:445 mod/follow.php:105 mod/message.php:216
-#: mod/settings.php:1091 mod/settings.php:1097 mod/settings.php:1105
-#: mod/settings.php:1109 mod/settings.php:1114 mod/settings.php:1120
-#: mod/settings.php:1126 mod/settings.php:1132 mod/settings.php:1158
-#: mod/settings.php:1159 mod/settings.php:1160 mod/settings.php:1161
-#: mod/settings.php:1162 mod/dfrn_request.php:848 mod/register.php:235
+#: mod/contacts.php:456 mod/follow.php:110 mod/message.php:216
+#: mod/settings.php:1103 mod/settings.php:1109 mod/settings.php:1117
+#: mod/settings.php:1121 mod/settings.php:1126 mod/settings.php:1132
+#: mod/settings.php:1138 mod/settings.php:1144 mod/settings.php:1170
+#: mod/settings.php:1171 mod/settings.php:1172 mod/settings.php:1173
+#: mod/settings.php:1174 mod/dfrn_request.php:857 mod/register.php:238
 #: mod/suggest.php:29 mod/profiles.php:658 mod/profiles.php:661
-#: mod/profiles.php:687 mod/api.php:105 include/items.php:4935
+#: mod/profiles.php:687 mod/api.php:105 include/items.php:4905
 msgid "Yes"
 msgstr "Ja"
 
-#: mod/contacts.php:448 mod/tagrm.php:11 mod/tagrm.php:94 mod/follow.php:116
-#: mod/videos.php:123 mod/message.php:219 mod/fbrowser.php:93
-#: mod/fbrowser.php:128 mod/settings.php:649 mod/settings.php:675
-#: mod/dfrn_request.php:862 mod/suggest.php:32 mod/editpost.php:147
-#: mod/photos.php:239 mod/photos.php:328 include/conversation.php:1221
-#: include/items.php:4938
+#: mod/contacts.php:459 mod/tagrm.php:11 mod/tagrm.php:94 mod/follow.php:121
+#: mod/videos.php:131 mod/message.php:219 mod/fbrowser.php:93
+#: mod/fbrowser.php:128 mod/settings.php:660 mod/settings.php:686
+#: mod/dfrn_request.php:871 mod/suggest.php:32 mod/editpost.php:148
+#: mod/photos.php:247 mod/photos.php:336 include/conversation.php:1220
+#: include/items.php:4908
 msgid "Cancel"
 msgstr "Annuleren"
 
-#: mod/contacts.php:460
+#: mod/contacts.php:471
 msgid "Contact has been removed."
 msgstr "Contact is verwijderd."
 
-#: mod/contacts.php:498
+#: mod/contacts.php:512
 #, php-format
 msgid "You are mutual friends with %s"
 msgstr "Je bent wederzijds bevriend met %s"
 
-#: mod/contacts.php:502
+#: mod/contacts.php:516
 #, php-format
 msgid "You are sharing with %s"
 msgstr "Je deelt met %s"
 
-#: mod/contacts.php:507
+#: mod/contacts.php:521
 #, php-format
 msgid "%s is sharing with you"
 msgstr "%s deelt met jou"
 
-#: mod/contacts.php:527
+#: mod/contacts.php:541
 msgid "Private communications are not available for this contact."
 msgstr "Privécommunicatie met dit contact is niet beschikbaar."
 
-#: mod/contacts.php:530 mod/admin.php:643
+#: mod/contacts.php:544 mod/admin.php:811
 msgid "Never"
 msgstr "Nooit"
 
-#: mod/contacts.php:534
+#: mod/contacts.php:548
 msgid "(Update was successful)"
 msgstr "(Wijziging is geslaagd)"
 
-#: mod/contacts.php:534
+#: mod/contacts.php:548
 msgid "(Update was not successful)"
 msgstr "(Wijziging is niet geslaagd)"
 
-#: mod/contacts.php:536
+#: mod/contacts.php:550
 msgid "Suggest friends"
 msgstr "Stel vrienden voor"
 
-#: mod/contacts.php:540
+#: mod/contacts.php:554
 #, php-format
 msgid "Network type: %s"
 msgstr "Netwerk type: %s"
 
-#: mod/contacts.php:543 include/contact_widgets.php:200
-#, php-format
-msgid "%d contact in common"
-msgid_plural "%d contacts in common"
-msgstr[0] "%d gedeeld contact"
-msgstr[1] "%d gedeelde contacten"
-
-#: mod/contacts.php:548
-msgid "View all contacts"
-msgstr "Alle contacten zien"
-
-#: mod/contacts.php:553 mod/contacts.php:636 mod/contacts.php:820
-#: mod/admin.php:1114
-msgid "Unblock"
-msgstr "Blokkering opheffen"
-
-#: mod/contacts.php:553 mod/contacts.php:636 mod/contacts.php:820
-#: mod/admin.php:1113
-msgid "Block"
-msgstr "Blokkeren"
-
-#: mod/contacts.php:556
-msgid "Toggle Blocked status"
-msgstr "Schakel geblokkeerde status"
-
-#: mod/contacts.php:561 mod/contacts.php:637 mod/contacts.php:821
-msgid "Unignore"
-msgstr "Negeer niet meer"
-
-#: mod/contacts.php:561 mod/contacts.php:637 mod/contacts.php:821
-#: mod/notifications.php:54 mod/notifications.php:179
-#: mod/notifications.php:259
-msgid "Ignore"
-msgstr "Negeren"
-
-#: mod/contacts.php:564
-msgid "Toggle Ignored status"
-msgstr "Schakel negeerstatus"
-
-#: mod/contacts.php:570 mod/contacts.php:822
-msgid "Unarchive"
-msgstr "Archiveer niet meer"
-
-#: mod/contacts.php:570 mod/contacts.php:822
-msgid "Archive"
-msgstr "Archiveer"
-
-#: mod/contacts.php:573
-msgid "Toggle Archive status"
-msgstr "Schakel archiveringsstatus"
-
-#: mod/contacts.php:578
-msgid "Repair"
-msgstr "Herstellen"
-
-#: mod/contacts.php:581
-msgid "Advanced Contact Settings"
-msgstr "Geavanceerde instellingen voor contacten"
-
-#: mod/contacts.php:589
+#: mod/contacts.php:567
 msgid "Communications lost with this contact!"
 msgstr "Communicatie met dit contact is verbroken!"
 
-#: mod/contacts.php:592
+#: mod/contacts.php:570
 msgid "Fetch further information for feeds"
 msgstr ""
 
-#: mod/contacts.php:593 mod/admin.php:652
+#: mod/contacts.php:571 mod/admin.php:820
 msgid "Disabled"
 msgstr "Uitgeschakeld"
 
-#: mod/contacts.php:593
+#: mod/contacts.php:571
 msgid "Fetch information"
 msgstr ""
 
-#: mod/contacts.php:593
+#: mod/contacts.php:571
 msgid "Fetch information and keywords"
 msgstr ""
 
-#: mod/contacts.php:606
-msgid "Contact Editor"
-msgstr "Contactbewerker"
-
-#: mod/contacts.php:608 mod/manage.php:124 mod/fsuggest.php:107
-#: mod/message.php:342 mod/message.php:525 mod/crepair.php:195
-#: mod/events.php:574 mod/content.php:712 mod/install.php:253
-#: mod/install.php:291 mod/mood.php:137 mod/profiles.php:696
+#: mod/contacts.php:587 mod/manage.php:143 mod/fsuggest.php:107
+#: mod/message.php:342 mod/message.php:525 mod/crepair.php:196
+#: mod/events.php:574 mod/content.php:712 mod/install.php:261
+#: mod/install.php:299 mod/mood.php:137 mod/profiles.php:696
 #: mod/localtime.php:45 mod/poke.php:198 mod/invite.php:140
-#: mod/photos.php:1129 mod/photos.php:1253 mod/photos.php:1571
-#: mod/photos.php:1622 mod/photos.php:1670 mod/photos.php:1758
+#: mod/photos.php:1137 mod/photos.php:1261 mod/photos.php:1579
+#: mod/photos.php:1630 mod/photos.php:1678 mod/photos.php:1766
 #: object/Item.php:710 view/theme/cleanzero/config.php:80
 #: view/theme/dispy/config.php:70 view/theme/quattro/config.php:64
 #: view/theme/diabook/config.php:148 view/theme/diabook/theme.php:633
@@ -259,208 +208,304 @@ msgstr "Contactbewerker"
 msgid "Submit"
 msgstr "Opslaan"
 
-#: mod/contacts.php:609
+#: mod/contacts.php:588
 msgid "Profile Visibility"
 msgstr "Zichtbaarheid profiel"
 
-#: mod/contacts.php:610
+#: mod/contacts.php:589
 #, php-format
 msgid ""
 "Please choose the profile you would like to display to %s when viewing your "
 "profile securely."
 msgstr "Kies het profiel dat getoond moet worden wanneer %s uw profiel bezoekt. "
 
-#: mod/contacts.php:611
+#: mod/contacts.php:590
 msgid "Contact Information / Notes"
 msgstr "Contactinformatie / aantekeningen"
 
-#: mod/contacts.php:612
+#: mod/contacts.php:591
 msgid "Edit contact notes"
 msgstr "Wijzig aantekeningen over dit contact"
 
-#: mod/contacts.php:617 mod/contacts.php:860 mod/viewcontacts.php:66
+#: mod/contacts.php:596 mod/contacts.php:952 mod/viewcontacts.php:97
 #: mod/nogroup.php:41
 #, php-format
 msgid "Visit %s's profile [%s]"
 msgstr "Bekijk het profiel van %s [%s]"
 
-#: mod/contacts.php:618
+#: mod/contacts.php:597
 msgid "Block/Unblock contact"
 msgstr "Blokkeer/deblokkeer contact"
 
-#: mod/contacts.php:619
+#: mod/contacts.php:598
 msgid "Ignore contact"
 msgstr "Negeer contact"
 
-#: mod/contacts.php:620
+#: mod/contacts.php:599
 msgid "Repair URL settings"
 msgstr "Repareer URL-instellingen"
 
-#: mod/contacts.php:621
+#: mod/contacts.php:600
 msgid "View conversations"
 msgstr "Toon conversaties"
 
-#: mod/contacts.php:623
+#: mod/contacts.php:602
 msgid "Delete contact"
 msgstr "Verwijder contact"
 
-#: mod/contacts.php:627
+#: mod/contacts.php:606
 msgid "Last update:"
 msgstr "Laatste wijziging:"
 
-#: mod/contacts.php:629
+#: mod/contacts.php:608
 msgid "Update public posts"
 msgstr "Openbare posts aanpassen"
 
-#: mod/contacts.php:631 mod/admin.php:1650
+#: mod/contacts.php:610
 msgid "Update now"
 msgstr "Wijzig nu"
 
-#: mod/contacts.php:633 mod/dirfind.php:190 mod/allfriends.php:67
-#: mod/match.php:71 mod/suggest.php:82 include/contact_widgets.php:32
+#: mod/contacts.php:612 mod/follow.php:103 mod/dirfind.php:196
+#: mod/allfriends.php:65 mod/match.php:71 mod/suggest.php:82
+#: include/contact_widgets.php:32 include/Contact.php:297
 #: include/conversation.php:924
 msgid "Connect/Follow"
 msgstr "Verbind/Volg"
 
-#: mod/contacts.php:640
+#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:865
+#: mod/admin.php:1301
+msgid "Unblock"
+msgstr "Blokkering opheffen"
+
+#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:865
+#: mod/admin.php:1300
+msgid "Block"
+msgstr "Blokkeren"
+
+#: mod/contacts.php:616 mod/contacts.php:807 mod/contacts.php:872
+msgid "Unignore"
+msgstr "Negeer niet meer"
+
+#: mod/contacts.php:616 mod/contacts.php:807 mod/contacts.php:872
+#: mod/notifications.php:54 mod/notifications.php:179
+#: mod/notifications.php:259
+msgid "Ignore"
+msgstr "Negeren"
+
+#: mod/contacts.php:619
 msgid "Currently blocked"
 msgstr "Op dit moment geblokkeerd"
 
-#: mod/contacts.php:641
+#: mod/contacts.php:620
 msgid "Currently ignored"
 msgstr "Op dit moment genegeerd"
 
-#: mod/contacts.php:642
+#: mod/contacts.php:621
 msgid "Currently archived"
 msgstr "Op dit moment gearchiveerd"
 
-#: mod/contacts.php:643 mod/notifications.php:172 mod/notifications.php:251
+#: mod/contacts.php:622 mod/notifications.php:172 mod/notifications.php:251
 msgid "Hide this contact from others"
 msgstr "Verberg dit contact voor anderen"
 
-#: mod/contacts.php:643
+#: mod/contacts.php:622
 msgid ""
 "Replies/likes to your public posts <strong>may</strong> still be visible"
 msgstr "Antwoorden of 'vind ik leuk's op je openbare posts <strong>kunnen</strong> nog zichtbaar zijn"
 
-#: mod/contacts.php:644
+#: mod/contacts.php:623
 msgid "Notification for new posts"
 msgstr "Meldingen voor nieuwe berichten"
 
-#: mod/contacts.php:644
+#: mod/contacts.php:623
 msgid "Send a notification of every new post of this contact"
 msgstr ""
 
-#: mod/contacts.php:647
+#: mod/contacts.php:626
 msgid "Blacklisted keywords"
 msgstr ""
 
-#: mod/contacts.php:647
+#: mod/contacts.php:626
 msgid ""
 "Comma separated list of keywords that should not be converted to hashtags, "
 "when \"Fetch information and keywords\" is selected"
 msgstr ""
 
-#: mod/contacts.php:654 mod/follow.php:121 mod/notifications.php:255
+#: mod/contacts.php:633 mod/follow.php:126 mod/notifications.php:255
 msgid "Profile URL"
 msgstr "Profiel url"
 
-#: mod/contacts.php:700
+#: mod/contacts.php:636 mod/notifications.php:244 mod/events.php:566
+#: mod/directory.php:145 include/identity.php:308 include/bb2diaspora.php:170
+#: include/event.php:36 include/event.php:60
+msgid "Location:"
+msgstr "Plaats:"
+
+#: mod/contacts.php:638 mod/notifications.php:246 mod/directory.php:153
+#: include/identity.php:317 include/identity.php:631
+msgid "About:"
+msgstr "Over:"
+
+#: mod/contacts.php:640 mod/follow.php:134 mod/notifications.php:248
+#: include/identity.php:625
+msgid "Tags:"
+msgstr "Labels:"
+
+#: mod/contacts.php:685
 msgid "Suggestions"
 msgstr "Voorstellen"
 
-#: mod/contacts.php:703
+#: mod/contacts.php:688
 msgid "Suggest potential friends"
 msgstr "Stel vrienden voor"
 
-#: mod/contacts.php:708 mod/group.php:192
+#: mod/contacts.php:693 mod/group.php:192
 msgid "All Contacts"
 msgstr "Alle Contacten"
 
-#: mod/contacts.php:711
+#: mod/contacts.php:696
 msgid "Show all contacts"
 msgstr "Toon alle contacten"
 
-#: mod/contacts.php:716
+#: mod/contacts.php:701
 msgid "Unblocked"
 msgstr "Niet geblokkeerd"
 
-#: mod/contacts.php:719
+#: mod/contacts.php:704
 msgid "Only show unblocked contacts"
 msgstr "Toon alleen niet-geblokkeerde contacten"
 
-#: mod/contacts.php:725
+#: mod/contacts.php:710
 msgid "Blocked"
 msgstr "Geblokkeerd"
 
-#: mod/contacts.php:728
+#: mod/contacts.php:713
 msgid "Only show blocked contacts"
 msgstr "Toon alleen geblokkeerde contacten"
 
-#: mod/contacts.php:734
+#: mod/contacts.php:719
 msgid "Ignored"
 msgstr "Genegeerd"
 
-#: mod/contacts.php:737
+#: mod/contacts.php:722
 msgid "Only show ignored contacts"
 msgstr "Toon alleen genegeerde contacten"
 
-#: mod/contacts.php:743
+#: mod/contacts.php:728
 msgid "Archived"
 msgstr "Gearchiveerd"
 
-#: mod/contacts.php:746
+#: mod/contacts.php:731
 msgid "Only show archived contacts"
 msgstr "Toon alleen gearchiveerde contacten"
 
-#: mod/contacts.php:752
+#: mod/contacts.php:737
 msgid "Hidden"
 msgstr "Verborgen"
 
-#: mod/contacts.php:755
+#: mod/contacts.php:740
 msgid "Only show hidden contacts"
 msgstr "Toon alleen verborgen contacten"
 
-#: mod/contacts.php:807 include/text.php:1005 include/nav.php:123
-#: include/nav.php:187 view/theme/diabook/theme.php:125
+#: mod/contacts.php:793 mod/contacts.php:841 mod/viewcontacts.php:116
+#: include/identity.php:741 include/identity.php:744 include/text.php:1012
+#: include/nav.php:123 include/nav.php:187 view/theme/diabook/theme.php:125
 msgid "Contacts"
 msgstr "Contacten"
 
-#: mod/contacts.php:811
+#: mod/contacts.php:797
 msgid "Search your contacts"
 msgstr "Doorzoek je contacten"
 
-#: mod/contacts.php:812
+#: mod/contacts.php:798
 msgid "Finding: "
 msgstr "Gevonden:"
 
-#: mod/contacts.php:813 mod/directory.php:202 include/contact_widgets.php:34
+#: mod/contacts.php:799 mod/directory.php:210 include/contact_widgets.php:34
 msgid "Find"
 msgstr "Zoek"
 
-#: mod/contacts.php:819 mod/settings.php:146 mod/settings.php:674
+#: mod/contacts.php:805 mod/settings.php:156 mod/settings.php:685
 msgid "Update"
 msgstr "Wijzigen"
 
-#: mod/contacts.php:823 mod/group.php:171 mod/admin.php:1112
-#: mod/content.php:440 mod/content.php:743 mod/settings.php:711
-#: mod/photos.php:1715 object/Item.php:134 include/conversation.php:635
+#: mod/contacts.php:808 mod/contacts.php:879
+msgid "Archive"
+msgstr "Archiveer"
+
+#: mod/contacts.php:808 mod/contacts.php:879
+msgid "Unarchive"
+msgstr "Archiveer niet meer"
+
+#: mod/contacts.php:809 mod/group.php:171 mod/admin.php:1299
+#: mod/content.php:440 mod/content.php:743 mod/settings.php:722
+#: mod/photos.php:1723 object/Item.php:134 include/conversation.php:635
 msgid "Delete"
 msgstr "Verwijder"
 
-#: mod/contacts.php:836
+#: mod/contacts.php:822 include/identity.php:686 include/nav.php:75
+msgid "Status"
+msgstr "Tijdlijn"
+
+#: mod/contacts.php:825 mod/follow.php:143 include/identity.php:689
+msgid "Status Messages and Posts"
+msgstr "Berichten op jouw tijdlijn"
+
+#: mod/contacts.php:830 mod/profperm.php:104 mod/newmember.php:32
+#: include/identity.php:579 include/identity.php:665 include/identity.php:694
+#: include/nav.php:76 view/theme/diabook/theme.php:124
+msgid "Profile"
+msgstr "Profiel"
+
+#: mod/contacts.php:833 include/identity.php:697
+msgid "Profile Details"
+msgstr "Profieldetails"
+
+#: mod/contacts.php:844
+msgid "View all contacts"
+msgstr "Alle contacten zien"
+
+#: mod/contacts.php:850 mod/common.php:134
+msgid "Common Friends"
+msgstr "Gedeelde Vrienden"
+
+#: mod/contacts.php:853
+msgid "View all common friends"
+msgstr ""
+
+#: mod/contacts.php:857
+msgid "Repair"
+msgstr "Herstellen"
+
+#: mod/contacts.php:860
+msgid "Advanced Contact Settings"
+msgstr "Geavanceerde instellingen voor contacten"
+
+#: mod/contacts.php:868
+msgid "Toggle Blocked status"
+msgstr "Schakel geblokkeerde status"
+
+#: mod/contacts.php:875
+msgid "Toggle Ignored status"
+msgstr "Schakel negeerstatus"
+
+#: mod/contacts.php:882
+msgid "Toggle Archive status"
+msgstr "Schakel archiveringsstatus"
+
+#: mod/contacts.php:924
 msgid "Mutual Friendship"
 msgstr "Wederzijdse vriendschap"
 
-#: mod/contacts.php:840
+#: mod/contacts.php:928
 msgid "is a fan of yours"
 msgstr "Is een fan van jou"
 
-#: mod/contacts.php:844
+#: mod/contacts.php:932
 msgid "you are a fan of"
 msgstr "Jij bent een fan van"
 
-#: mod/contacts.php:861 mod/nogroup.php:42
+#: mod/contacts.php:953 mod/nogroup.php:42
 msgid "Edit contact"
 msgstr "Contact bewerken"
 
@@ -468,17 +513,17 @@ msgstr "Contact bewerken"
 msgid "No profile"
 msgstr "Geen profiel"
 
-#: mod/manage.php:120
+#: mod/manage.php:139
 msgid "Manage Identities and/or Pages"
 msgstr "Beheer Identiteiten en/of Pagina's"
 
-#: mod/manage.php:121
+#: mod/manage.php:140
 msgid ""
 "Toggle between different identities or community/group pages which share "
 "your account details or which you have been granted \"manage\" permissions"
 msgstr "Wissel tussen verschillende identiteiten of forum/groeppagina's die jouw accountdetails delen of waar je \"beheerdersrechten\" hebt gekregen."
 
-#: mod/manage.php:122
+#: mod/manage.php:141
 msgid "Select an identity to manage: "
 msgstr "Selecteer een identiteit om te beheren:"
 
@@ -486,7 +531,7 @@ msgstr "Selecteer een identiteit om te beheren:"
 msgid "Post successful."
 msgstr "Bericht succesvol geplaatst."
 
-#: mod/profperm.php:19 mod/group.php:72 index.php:381
+#: mod/profperm.php:19 mod/group.php:72 index.php:382
 msgid "Permission denied"
 msgstr "Toegang geweigerd"
 
@@ -498,13 +543,7 @@ msgstr "Ongeldige profiel-identificatie."
 msgid "Profile Visibility Editor"
 msgstr ""
 
-#: mod/profperm.php:104 mod/newmember.php:32 include/identity.php:530
-#: include/identity.php:611 include/identity.php:641 include/nav.php:76
-#: view/theme/diabook/theme.php:124
-msgid "Profile"
-msgstr "Profiel"
-
-#: mod/profperm.php:106 mod/group.php:222
+#: mod/profperm.php:106 mod/group.php:223
 msgid "Click on a contact to add or remove."
 msgstr "Klik op een contact om het toe te voegen of te verwijderen."
 
@@ -516,23 +555,23 @@ msgstr "Zichtbaar voor"
 msgid "All Contacts (with secure profile access)"
 msgstr "Alle contacten (met veilige profieltoegang)"
 
-#: mod/display.php:82 mod/display.php:283 mod/display.php:500
-#: mod/viewsrc.php:15 mod/admin.php:196 mod/admin.php:1157 mod/admin.php:1378
-#: mod/notice.php:15 include/items.php:4894
+#: mod/display.php:82 mod/display.php:291 mod/display.php:508
+#: mod/viewsrc.php:15 mod/admin.php:225 mod/admin.php:1354 mod/admin.php:1588
+#: mod/notice.php:15 include/items.php:4864
 msgid "Item not found."
 msgstr "Item niet gevonden."
 
-#: mod/display.php:211 mod/videos.php:189 mod/viewcontacts.php:19
-#: mod/community.php:18 mod/dfrn_request.php:777 mod/search.php:93
-#: mod/search.php:99 mod/directory.php:37 mod/photos.php:968
+#: mod/display.php:220 mod/videos.php:197 mod/viewcontacts.php:35
+#: mod/community.php:18 mod/dfrn_request.php:786 mod/search.php:93
+#: mod/search.php:99 mod/directory.php:37 mod/photos.php:976
 msgid "Public access denied."
 msgstr "Niet vrij toegankelijk"
 
-#: mod/display.php:331 mod/profile.php:155
+#: mod/display.php:339 mod/profile.php:155
 msgid "Access to this profile has been restricted."
 msgstr "Toegang tot dit profiel is beperkt."
 
-#: mod/display.php:493
+#: mod/display.php:501
 msgid "Item has been removed."
 msgstr "Item is verwijderd."
 
@@ -567,8 +606,8 @@ msgid ""
 " join."
 msgstr "Op je <em>Snelstart</em> pagina kun je een korte inleiding vinden over je profiel en netwerk tabs, om enkele nieuwe connecties te leggen en groepen te vinden om lid van te worden."
 
-#: mod/newmember.php:22 mod/admin.php:1209 mod/admin.php:1454
-#: mod/settings.php:99 include/nav.php:182 view/theme/diabook/theme.php:544
+#: mod/newmember.php:22 mod/admin.php:1407 mod/admin.php:1665
+#: mod/settings.php:109 include/nav.php:182 view/theme/diabook/theme.php:544
 #: view/theme/diabook/theme.php:648
 msgid "Settings"
 msgstr "Instellingen"
@@ -592,7 +631,7 @@ msgid ""
 "potential friends know exactly how to find you."
 msgstr "Controleer ook de andere instellingen, in het bijzonder de privacy-instellingen. Een niet-gepubliceerd adres is zoals een privé-telefoonnummer. In het algemeen wil je waarschijnlijk je adres publiceren - tenzij al je vrienden en mogelijke vrienden precies weten hoe je te vinden."
 
-#: mod/newmember.php:36 mod/profile_photo.php:244 mod/profiles.php:709
+#: mod/newmember.php:36 mod/profile_photo.php:250 mod/profiles.php:709
 msgid "Upload Profile Photo"
 msgstr "Profielfoto uploaden"
 
@@ -629,60 +668,44 @@ msgstr "Stel enkele openbare sleutelwoorden in voor je standaard profiel die je
 msgid "Connecting"
 msgstr "Verbinding aan het maken"
 
-#: mod/newmember.php:49 mod/newmember.php:51 include/contact_selectors.php:81
-msgid "Facebook"
-msgstr "Facebook"
-
-#: mod/newmember.php:49
-msgid ""
-"Authorise the Facebook Connector if you currently have a Facebook account "
-"and we will (optionally) import all your Facebook friends and conversations."
-msgstr "Machtig de Facebook Connector als je een Facebook account hebt. We zullen (optioneel) al je Facebook vrienden en conversaties importeren."
-
 #: mod/newmember.php:51
-msgid ""
-"<em>If</em> this is your own personal server, installing the Facebook addon "
-"may ease your transition to the free social web."
-msgstr "<em>Als</em> dit jouw eigen persoonlijke server is kan het installeren van de Facebook toevoeging je overgang naar het vrije sociale web vergemakkelijken."
-
-#: mod/newmember.php:56
 msgid "Importing Emails"
 msgstr "E-mails importeren"
 
-#: mod/newmember.php:56
+#: mod/newmember.php:51
 msgid ""
 "Enter your email access information on your Connector Settings page if you "
 "wish to import and interact with friends or mailing lists from your email "
 "INBOX"
 msgstr "Vul je e-mailtoegangsinformatie in op je pagina met verbindingsinstellingen als je vrienden of mailinglijsten uit je e-mail-inbox wilt importeren, en met hen wilt communiceren"
 
-#: mod/newmember.php:58
+#: mod/newmember.php:53
 msgid "Go to Your Contacts Page"
 msgstr "Ga naar je contactenpagina"
 
-#: mod/newmember.php:58
+#: mod/newmember.php:53
 msgid ""
 "Your Contacts page is your gateway to managing friendships and connecting "
 "with friends on other networks. Typically you enter their address or site "
 "URL in the <em>Add New Contact</em> dialog."
 msgstr "Je contactenpagina is jouw poort om vriendschappen te beheren en verbinding te leggen met vrienden op andere netwerken. Je kunt hun adres of URL toevoegen in de <em>Voeg nieuw contact toe</em> dialoog."
 
-#: mod/newmember.php:60
+#: mod/newmember.php:55
 msgid "Go to Your Site's Directory"
 msgstr "Ga naar de gids van je website"
 
-#: mod/newmember.php:60
+#: mod/newmember.php:55
 msgid ""
 "The Directory page lets you find other people in this network or other "
 "federated sites. Look for a <em>Connect</em> or <em>Follow</em> link on "
 "their profile page. Provide your own Identity Address if requested."
 msgstr "In de gids vind je andere mensen in dit netwerk of op andere federatieve sites. Zoek naar het woord <em>Connect</em> of <em>Follow</em> op hun profielpagina (meestal aan de linkerkant). Vul je eigen identiteitsadres in wanneer daar om wordt gevraagd."
 
-#: mod/newmember.php:62
+#: mod/newmember.php:57
 msgid "Finding New People"
 msgstr "Nieuwe mensen vinden"
 
-#: mod/newmember.php:62
+#: mod/newmember.php:57
 msgid ""
 "On the side panel of the Contacts page are several tools to find new "
 "friends. We can match people by interest, look up people by name or "
@@ -691,41 +714,41 @@ msgid ""
 "hours."
 msgstr "Op het zijpaneel van de Contacten pagina vind je verschillende tools om nieuwe vrienden te zoeken. We kunnen mensen op interesses matchen, mensen opzoeken op naam of hobby, en suggesties doen gebaseerd op netwerk-relaties. Op een nieuwe webstek beginnen vriendschapssuggesties meestal binnen de 24 uur beschikbaar te worden."
 
-#: mod/newmember.php:66 include/group.php:272
+#: mod/newmember.php:61 include/group.php:283
 msgid "Groups"
 msgstr "Groepen"
 
-#: mod/newmember.php:70
+#: mod/newmember.php:65
 msgid "Group Your Contacts"
 msgstr "Groepeer je contacten"
 
-#: mod/newmember.php:70
+#: mod/newmember.php:65
 msgid ""
 "Once you have made some friends, organize them into private conversation "
 "groups from the sidebar of your Contacts page and then you can interact with"
 " each group privately on your Network page."
 msgstr "Als je een aantal vrienden gemaakt hebt kun je ze in je eigen conversatiegroepen indelen vanuit de zijbalk van je 'Conacten' pagina, en dan kun je met elke groep apart contact houden op je Netwerk pagina. "
 
-#: mod/newmember.php:73
+#: mod/newmember.php:68
 msgid "Why Aren't My Posts Public?"
 msgstr "Waarom zijn mijn berichten niet openbaar?"
 
-#: mod/newmember.php:73
+#: mod/newmember.php:68
 msgid ""
 "Friendica respects your privacy. By default, your posts will only show up to"
 " people you've added as friends. For more information, see the help section "
 "from the link above."
 msgstr "Friendica respecteert je privacy. Standaard zullen je berichten alleen zichtbaar zijn voor personen die jij als vriend hebt toegevoegd. Lees de help (zie de verwijzing hierboven) voor meer informatie."
 
-#: mod/newmember.php:78
+#: mod/newmember.php:73
 msgid "Getting Help"
 msgstr "Hulp krijgen"
 
-#: mod/newmember.php:82
+#: mod/newmember.php:77
 msgid "Go to the Help Section"
 msgstr "Ga naar de help"
 
-#: mod/newmember.php:82
+#: mod/newmember.php:77
 msgid ""
 "Our <strong>help</strong> pages may be consulted for detail on other program"
 " features and resources."
@@ -740,7 +763,7 @@ msgid ""
 "Account not found and OpenID registration is not permitted on this site."
 msgstr "Account niet gevonden, en OpenID-registratie is niet toegelaten op deze website."
 
-#: mod/openid.php:93 include/auth.php:112 include/auth.php:175
+#: mod/openid.php:93 include/auth.php:118 include/auth.php:181
 msgid "Login failed."
 msgstr "Login mislukt."
 
@@ -749,95 +772,95 @@ msgid "Image uploaded but image cropping failed."
 msgstr "Afbeelding opgeladen, maar bijsnijden mislukt."
 
 #: mod/profile_photo.php:74 mod/profile_photo.php:81 mod/profile_photo.php:88
-#: mod/profile_photo.php:204 mod/profile_photo.php:296
-#: mod/profile_photo.php:305 mod/photos.php:70 mod/photos.php:184
-#: mod/photos.php:767 mod/photos.php:1237 mod/photos.php:1260
-#: mod/photos.php:1854 include/user.php:343 include/user.php:350
-#: include/user.php:357 view/theme/diabook/theme.php:500
+#: mod/profile_photo.php:210 mod/profile_photo.php:302
+#: mod/profile_photo.php:311 mod/photos.php:78 mod/photos.php:192
+#: mod/photos.php:775 mod/photos.php:1245 mod/photos.php:1268
+#: mod/photos.php:1862 include/user.php:345 include/user.php:352
+#: include/user.php:359 view/theme/diabook/theme.php:500
 msgid "Profile Photos"
 msgstr "Profielfoto's"
 
 #: mod/profile_photo.php:77 mod/profile_photo.php:84 mod/profile_photo.php:91
-#: mod/profile_photo.php:308
+#: mod/profile_photo.php:314
 #, php-format
 msgid "Image size reduction [%s] failed."
 msgstr "Verkleining van de afbeelding [%s] mislukt."
 
-#: mod/profile_photo.php:118
+#: mod/profile_photo.php:124
 msgid ""
 "Shift-reload the page or clear browser cache if the new photo does not "
 "display immediately."
 msgstr "Shift-herlaad de pagina, of maak de browser cache leeg als nieuwe foto's niet onmiddellijk verschijnen."
 
-#: mod/profile_photo.php:128
+#: mod/profile_photo.php:134
 msgid "Unable to process image"
 msgstr "Ik kan de afbeelding niet verwerken"
 
-#: mod/profile_photo.php:144 mod/wall_upload.php:151 mod/photos.php:803
+#: mod/profile_photo.php:150 mod/wall_upload.php:151 mod/photos.php:811
 #, php-format
 msgid "Image exceeds size limit of %s"
 msgstr ""
 
-#: mod/profile_photo.php:153 mod/wall_upload.php:183 mod/photos.php:843
+#: mod/profile_photo.php:159 mod/wall_upload.php:183 mod/photos.php:851
 msgid "Unable to process image."
 msgstr "Niet in staat om de afbeelding te verwerken"
 
-#: mod/profile_photo.php:242
+#: mod/profile_photo.php:248
 msgid "Upload File:"
 msgstr "Upload bestand:"
 
-#: mod/profile_photo.php:243
+#: mod/profile_photo.php:249
 msgid "Select a profile:"
 msgstr "Kies een profiel:"
 
-#: mod/profile_photo.php:245
+#: mod/profile_photo.php:251
 msgid "Upload"
 msgstr "Uploaden"
 
-#: mod/profile_photo.php:248
+#: mod/profile_photo.php:254
 msgid "or"
 msgstr "of"
 
-#: mod/profile_photo.php:248
+#: mod/profile_photo.php:254
 msgid "skip this step"
 msgstr "Deze stap overslaan"
 
-#: mod/profile_photo.php:248
+#: mod/profile_photo.php:254
 msgid "select a photo from your photo albums"
 msgstr "Kies een foto uit je fotoalbums"
 
-#: mod/profile_photo.php:262
+#: mod/profile_photo.php:268
 msgid "Crop Image"
 msgstr "Afbeelding bijsnijden"
 
-#: mod/profile_photo.php:263
+#: mod/profile_photo.php:269
 msgid "Please adjust the image cropping for optimum viewing."
 msgstr "Pas het afsnijden van de afbeelding aan voor het beste resultaat."
 
-#: mod/profile_photo.php:265
+#: mod/profile_photo.php:271
 msgid "Done Editing"
 msgstr "Wijzigingen compleet"
 
-#: mod/profile_photo.php:299
+#: mod/profile_photo.php:305
 msgid "Image uploaded successfully."
 msgstr "Uploaden van afbeelding gelukt."
 
-#: mod/profile_photo.php:301 mod/wall_upload.php:216 mod/photos.php:870
+#: mod/profile_photo.php:307 mod/wall_upload.php:216 mod/photos.php:878
 msgid "Image upload failed."
 msgstr "Uploaden van afbeelding mislukt."
 
-#: mod/subthread.php:87 mod/tagger.php:62 mod/like.php:168
+#: mod/subthread.php:87 mod/tagger.php:62 include/like.php:165
 #: include/conversation.php:130 include/conversation.php:266
-#: include/text.php:1988 include/diaspora.php:2140
+#: include/text.php:1993 include/diaspora.php:2147
 #: view/theme/diabook/theme.php:471
 msgid "photo"
 msgstr "foto"
 
-#: mod/subthread.php:87 mod/tagger.php:62 mod/like.php:168 mod/like.php:346
-#: include/conversation.php:125 include/conversation.php:134
-#: include/conversation.php:261 include/conversation.php:270
-#: include/diaspora.php:2140 view/theme/diabook/theme.php:466
-#: view/theme/diabook/theme.php:475
+#: mod/subthread.php:87 mod/tagger.php:62 include/like.php:165
+#: include/like.php:325 include/conversation.php:125
+#: include/conversation.php:134 include/conversation.php:261
+#: include/conversation.php:270 include/diaspora.php:2147
+#: view/theme/diabook/theme.php:466 view/theme/diabook/theme.php:475
 msgid "status"
 msgstr "status"
 
@@ -863,7 +886,7 @@ msgid "Remove"
 msgstr "Verwijderen"
 
 #: mod/ostatus_subscribe.php:14
-msgid "Subsribing to OStatus contacts"
+msgid "Subscribing to OStatus contacts"
 msgstr ""
 
 #: mod/ostatus_subscribe.php:25
@@ -898,8 +921,8 @@ msgstr "Verboden"
 msgid "Keep this window open until done."
 msgstr "Houd dit scherm open tot het klaar is"
 
-#: mod/filer.php:30 include/conversation.php:1133
-#: include/conversation.php:1151
+#: mod/filer.php:30 include/conversation.php:1132
+#: include/conversation.php:1150
 msgid "Save to Folder:"
 msgstr "Bewaren in map:"
 
@@ -907,73 +930,59 @@ msgstr "Bewaren in map:"
 msgid "- select -"
 msgstr "- Kies -"
 
-#: mod/filer.php:31 mod/editpost.php:108 mod/notes.php:61 include/text.php:997
+#: mod/filer.php:31 mod/editpost.php:109 mod/notes.php:61
+#: include/text.php:1004
 msgid "Save"
 msgstr "Bewaren"
 
-#: mod/follow.php:18 mod/dfrn_request.php:861
+#: mod/follow.php:19 mod/dfrn_request.php:870
 msgid "Submit Request"
 msgstr "Aanvraag indienen"
 
-#: mod/follow.php:29
+#: mod/follow.php:30
 msgid "You already added this contact."
 msgstr "Je hebt deze kontakt al toegevoegd"
 
-#: mod/follow.php:38
+#: mod/follow.php:39
 msgid "Diaspora support isn't enabled. Contact can't be added."
 msgstr ""
 
-#: mod/follow.php:45
+#: mod/follow.php:46
 msgid "OStatus support is disabled. Contact can't be added."
 msgstr ""
 
-#: mod/follow.php:52
+#: mod/follow.php:53
 msgid "The network type couldn't be detected. Contact can't be added."
 msgstr ""
 
-#: mod/follow.php:104 mod/dfrn_request.php:847
+#: mod/follow.php:109 mod/dfrn_request.php:856
 msgid "Please answer the following:"
 msgstr "Beantwoord het volgende:"
 
-#: mod/follow.php:105 mod/dfrn_request.php:848
+#: mod/follow.php:110 mod/dfrn_request.php:857
 #, php-format
 msgid "Does %s know you?"
 msgstr "Kent %s jou?"
 
-#: mod/follow.php:105 mod/settings.php:1091 mod/settings.php:1097
-#: mod/settings.php:1105 mod/settings.php:1109 mod/settings.php:1114
-#: mod/settings.php:1120 mod/settings.php:1126 mod/settings.php:1132
-#: mod/settings.php:1158 mod/settings.php:1159 mod/settings.php:1160
-#: mod/settings.php:1161 mod/settings.php:1162 mod/dfrn_request.php:848
-#: mod/register.php:236 mod/profiles.php:658 mod/profiles.php:662
+#: mod/follow.php:110 mod/settings.php:1103 mod/settings.php:1109
+#: mod/settings.php:1117 mod/settings.php:1121 mod/settings.php:1126
+#: mod/settings.php:1132 mod/settings.php:1138 mod/settings.php:1144
+#: mod/settings.php:1170 mod/settings.php:1171 mod/settings.php:1172
+#: mod/settings.php:1173 mod/settings.php:1174 mod/dfrn_request.php:857
+#: mod/register.php:239 mod/profiles.php:658 mod/profiles.php:662
 #: mod/profiles.php:687 mod/api.php:106
 msgid "No"
 msgstr "Nee"
 
-#: mod/follow.php:106 mod/dfrn_request.php:852
+#: mod/follow.php:111 mod/dfrn_request.php:861
 msgid "Add a personal note:"
 msgstr "Voeg een persoonlijke opmerking toe:"
 
-#: mod/follow.php:112 mod/dfrn_request.php:858
+#: mod/follow.php:117 mod/dfrn_request.php:867
 msgid "Your Identity Address:"
 msgstr "Adres van uw identiteit:"
 
-#: mod/follow.php:125 mod/notifications.php:244 mod/events.php:566
-#: mod/directory.php:139 include/identity.php:268 include/bb2diaspora.php:170
-#: include/event.php:36 include/event.php:60
-msgid "Location:"
-msgstr "Plaats:"
-
-#: mod/follow.php:127 mod/notifications.php:246 mod/directory.php:147
-#: include/identity.php:277 include/identity.php:582
-msgid "About:"
-msgstr "Over:"
-
-#: mod/follow.php:129 mod/notifications.php:248 include/identity.php:576
-msgid "Tags:"
-msgstr "Labels:"
-
-#: mod/follow.php:162
+#: mod/follow.php:180
 msgid "Contact added"
 msgstr "Contact toegevoegd"
 
@@ -981,39 +990,39 @@ msgstr "Contact toegevoegd"
 msgid "Unable to locate original post."
 msgstr "Ik kan de originele post niet meer vinden."
 
-#: mod/item.php:322
+#: mod/item.php:329
 msgid "Empty post discarded."
 msgstr "Lege post weggegooid."
 
-#: mod/item.php:461 mod/wall_upload.php:213 mod/wall_upload.php:227
-#: mod/wall_upload.php:234 include/Photo.php:954 include/Photo.php:969
-#: include/Photo.php:976 include/Photo.php:998 include/message.php:145
+#: mod/item.php:467 mod/wall_upload.php:213 mod/wall_upload.php:227
+#: mod/wall_upload.php:234 include/Photo.php:958 include/Photo.php:973
+#: include/Photo.php:980 include/Photo.php:1002 include/message.php:145
 msgid "Wall Photos"
 msgstr ""
 
-#: mod/item.php:835
+#: mod/item.php:842
 msgid "System error. Post not saved."
 msgstr "Systeemfout. Post niet bewaard."
 
-#: mod/item.php:964
+#: mod/item.php:971
 #, php-format
 msgid ""
 "This message was sent to you by %s, a member of the Friendica social "
 "network."
 msgstr "Dit bericht werd naar jou gestuurd door %s, een lid van het Friendica sociale netwerk."
 
-#: mod/item.php:966
+#: mod/item.php:973
 #, php-format
 msgid "You may visit them online at %s"
 msgstr "Je kunt ze online bezoeken op %s"
 
-#: mod/item.php:967
+#: mod/item.php:974
 msgid ""
 "Please contact the sender by replying to this post if you do not wish to "
 "receive these messages."
 msgstr "Contacteer de afzender door op dit bericht te antwoorden als je deze berichten niet wilt ontvangen."
 
-#: mod/item.php:971
+#: mod/item.php:978
 #, php-format
 msgid "%s posted an update."
 msgstr "%s heeft een wijziging geplaatst."
@@ -1042,7 +1051,7 @@ msgstr "Bewaar groep"
 msgid "Create a group of contacts/friends."
 msgstr "Maak een groep contacten/vrienden aan."
 
-#: mod/group.php:94 mod/group.php:178 include/group.php:275
+#: mod/group.php:94 mod/group.php:178 include/group.php:289
 msgid "Group Name: "
 msgstr "Groepsnaam:"
 
@@ -1062,7 +1071,11 @@ msgstr "Groepsbewerker"
 msgid "Members"
 msgstr "Leden"
 
-#: mod/apps.php:7 index.php:225
+#: mod/group.php:193 mod/network.php:576 mod/content.php:130
+msgid "Group is empty"
+msgstr "De groep is leeg"
+
+#: mod/apps.php:7 index.php:226
 msgid "You must be logged in to use addons. "
 msgstr "Je moet ingelogd zijn om deze addons te kunnen gebruiken. "
 
@@ -1080,7 +1093,7 @@ msgid "Profile not found."
 msgstr "Profiel niet gevonden"
 
 #: mod/dfrn_confirm.php:120 mod/fsuggest.php:20 mod/fsuggest.php:92
-#: mod/crepair.php:134
+#: mod/crepair.php:131
 msgid "Contact not found."
 msgstr "Contact niet gevonden"
 
@@ -1119,7 +1132,7 @@ msgid "Unable to set contact photo."
 msgstr "Ik kan geen contact foto instellen."
 
 #: mod/dfrn_confirm.php:487 include/conversation.php:185
-#: include/diaspora.php:636
+#: include/diaspora.php:637
 #, php-format
 msgid "%1$s is now friends with %2$s"
 msgstr "%1$s is nu bevriend met %2$s"
@@ -1160,7 +1173,7 @@ msgstr "Niet in staat om op dit systeem je contactreferenties in te stellen."
 msgid "Unable to update your contact profile details on our system"
 msgstr ""
 
-#: mod/dfrn_confirm.php:753 mod/dfrn_request.php:732 include/items.php:4313
+#: mod/dfrn_confirm.php:753 mod/dfrn_request.php:741 include/items.php:4276
 msgid "[Name Withheld]"
 msgstr "[Naam achtergehouden]"
 
@@ -1169,7 +1182,7 @@ msgstr "[Naam achtergehouden]"
 msgid "%1$s has joined %2$s"
 msgstr "%1$s is toegetreden tot %2$s"
 
-#: mod/profile.php:21 include/identity.php:77
+#: mod/profile.php:21 include/identity.php:51
 msgid "Requested profile is not available."
 msgstr "Gevraagde profiel is niet beschikbaar."
 
@@ -1177,35 +1190,35 @@ msgstr "Gevraagde profiel is niet beschikbaar."
 msgid "Tips for New Members"
 msgstr "Tips voor nieuwe leden"
 
-#: mod/videos.php:115
+#: mod/videos.php:123
 msgid "Do you really want to delete this video?"
 msgstr "Wil je deze video echt verwijderen?"
 
-#: mod/videos.php:120
+#: mod/videos.php:128
 msgid "Delete Video"
 msgstr "Verwijder video"
 
-#: mod/videos.php:199
+#: mod/videos.php:207
 msgid "No videos selected"
 msgstr "Geen video's geselecteerd"
 
-#: mod/videos.php:300 mod/photos.php:1079
+#: mod/videos.php:308 mod/photos.php:1087
 msgid "Access to this item is restricted."
 msgstr "Toegang tot dit item is beperkt."
 
-#: mod/videos.php:375 include/text.php:1458
+#: mod/videos.php:383 include/text.php:1465
 msgid "View Video"
 msgstr "Bekijk Video"
 
-#: mod/videos.php:382 mod/photos.php:1882
+#: mod/videos.php:390 mod/photos.php:1890
 msgid "View Album"
 msgstr "Album bekijken"
 
-#: mod/videos.php:391
+#: mod/videos.php:399
 msgid "Recent Videos"
 msgstr "Recente video's"
 
-#: mod/videos.php:393
+#: mod/videos.php:401
 msgid "Upload New Videos"
 msgstr "Nieuwe video's uploaden"
 
@@ -1229,7 +1242,7 @@ msgstr "Stel een vriend voor aan %s"
 
 #: mod/wall_upload.php:20 mod/wall_upload.php:33 mod/wall_upload.php:86
 #: mod/wall_upload.php:122 mod/wall_upload.php:125 mod/wall_attach.php:17
-#: mod/wall_attach.php:25 mod/wall_attach.php:76 include/api.php:1702
+#: mod/wall_attach.php:25 mod/wall_attach.php:76 include/api.php:1781
 msgid "Invalid request."
 msgstr ""
 
@@ -1285,7 +1298,7 @@ msgid ""
 "Password reset failed."
 msgstr "Verzoek kon niet geverifieerd worden. (Misschien heb je het voordien al ingediend.) Wachtwoord niet opnieuw ingesteld."
 
-#: mod/lostpass.php:109 boot.php:1295
+#: mod/lostpass.php:109 boot.php:1418
 msgid "Password Reset"
 msgstr "Wachtwoord opnieuw instellen"
 
@@ -1359,57 +1372,22 @@ msgstr "Bijnaam of e-mail:"
 msgid "Reset"
 msgstr "Opnieuw"
 
-#: mod/like.php:170 include/conversation.php:122 include/conversation.php:258
-#: include/text.php:1986 view/theme/diabook/theme.php:463
-msgid "event"
-msgstr "gebeurtenis"
-
-#: mod/like.php:187 include/conversation.php:141 include/diaspora.php:2156
-#: view/theme/diabook/theme.php:480
-#, php-format
-msgid "%1$s likes %2$s's %3$s"
-msgstr "%1$s vindt het %3$s van %2$s leuk"
-
-#: mod/like.php:189 include/conversation.php:144
-#, php-format
-msgid "%1$s doesn't like %2$s's %3$s"
-msgstr "%1$s vindt het %3$s van %2$s niet leuk"
-
-#: mod/like.php:191
-#, php-format
-msgid "%1$s is attending %2$s's %3$s"
-msgstr ""
-
-#: mod/like.php:193
-#, php-format
-msgid "%1$s is not attending %2$s's %3$s"
-msgstr ""
-
-#: mod/like.php:195
-#, php-format
-msgid "%1$s may attend %2$s's %3$s"
-msgstr ""
-
-#: mod/ping.php:257
+#: mod/ping.php:265
 msgid "{0} wants to be your friend"
 msgstr "{0} wilt je vriend worden"
 
-#: mod/ping.php:272
+#: mod/ping.php:280
 msgid "{0} sent you a message"
 msgstr "{0} stuurde jou een bericht"
 
-#: mod/ping.php:287
+#: mod/ping.php:295
 msgid "{0} requested registration"
 msgstr "{0} vroeg om zich te registreren"
 
-#: mod/viewcontacts.php:41
+#: mod/viewcontacts.php:72
 msgid "No contacts."
 msgstr "Geen contacten."
 
-#: mod/viewcontacts.php:83 include/text.php:917
-msgid "View Contacts"
-msgstr "Bekijk contacten"
-
 #: mod/notifications.php:29
 msgid "Invalid request identifier."
 msgstr "Ongeldige <em>request identifier</em>."
@@ -1423,11 +1401,11 @@ msgstr "Verwerpen"
 msgid "System"
 msgstr "Systeem"
 
-#: mod/notifications.php:87 mod/admin.php:228 include/nav.php:154
+#: mod/notifications.php:87 mod/admin.php:379 include/nav.php:154
 msgid "Network"
 msgstr "Netwerk"
 
-#: mod/notifications.php:93 mod/network.php:385
+#: mod/notifications.php:93 mod/network.php:384
 msgid "Personal"
 msgstr "Persoonlijk"
 
@@ -1469,7 +1447,7 @@ msgstr "Bericht over een nieuwe vriend"
 msgid "if applicable"
 msgstr "Indien toepasbaar"
 
-#: mod/notifications.php:176 mod/notifications.php:257 mod/admin.php:1110
+#: mod/notifications.php:176 mod/notifications.php:257 mod/admin.php:1297
 msgid "Approve"
 msgstr "Goedkeuren"
 
@@ -1519,8 +1497,8 @@ msgstr "Vriendschapsverzoek"
 msgid "New Follower"
 msgstr "Nieuwe Volger"
 
-#: mod/notifications.php:250 mod/directory.php:141 include/identity.php:270
-#: include/identity.php:541
+#: mod/notifications.php:250 mod/directory.php:147 include/identity.php:310
+#: include/identity.php:590
 msgid "Gender:"
 msgstr "Geslacht:"
 
@@ -1691,7 +1669,7 @@ msgstr "Gesprek verwijderd."
 
 #: mod/message.php:290 mod/message.php:298 mod/message.php:427
 #: mod/message.php:435 mod/wallmessage.php:127 mod/wallmessage.php:135
-#: include/conversation.php:1129 include/conversation.php:1147
+#: include/conversation.php:1128 include/conversation.php:1146
 msgid "Please enter a link URL:"
 msgstr "Vul een internetadres/URL in:"
 
@@ -1713,19 +1691,19 @@ msgid "Your message:"
 msgstr "Jouw bericht:"
 
 #: mod/message.php:339 mod/message.php:523 mod/wallmessage.php:154
-#: mod/editpost.php:109 include/conversation.php:1184
+#: mod/editpost.php:110 include/conversation.php:1183
 msgid "Upload photo"
 msgstr "Foto uploaden"
 
 #: mod/message.php:340 mod/message.php:524 mod/wallmessage.php:155
-#: mod/editpost.php:113 include/conversation.php:1188
+#: mod/editpost.php:114 include/conversation.php:1187
 msgid "Insert web link"
 msgstr "Voeg een webadres in"
 
 #: mod/message.php:341 mod/message.php:526 mod/content.php:501
-#: mod/content.php:885 mod/wallmessage.php:156 mod/editpost.php:123
-#: mod/photos.php:1602 object/Item.php:396 include/conversation.php:713
-#: include/conversation.php:1202
+#: mod/content.php:885 mod/wallmessage.php:156 mod/editpost.php:124
+#: mod/photos.php:1610 object/Item.php:396 include/conversation.php:713
+#: include/conversation.php:1201
 msgid "Please wait"
 msgstr "Even geduld"
 
@@ -1741,7 +1719,7 @@ msgstr "Bericht niet beschikbaar."
 msgid "Delete message"
 msgstr "Verwijder bericht"
 
-#: mod/message.php:507 mod/message.php:582
+#: mod/message.php:507 mod/message.php:584
 msgid "Delete conversation"
 msgstr "Verwijder gesprek"
 
@@ -1755,26 +1733,26 @@ msgstr "Geen beveiligde communicatie beschikbaar. Je kunt <strong>misschien</str
 msgid "Send Reply"
 msgstr "Verstuur Antwoord"
 
-#: mod/message.php:555
+#: mod/message.php:557
 #, php-format
 msgid "Unknown sender - %s"
 msgstr "Onbekende afzender - %s"
 
-#: mod/message.php:558
+#: mod/message.php:560
 #, php-format
 msgid "You and %s"
 msgstr "Jij en %s"
 
-#: mod/message.php:561
+#: mod/message.php:563
 #, php-format
 msgid "%s and You"
 msgstr "%s en jij"
 
-#: mod/message.php:585
+#: mod/message.php:587
 msgid "D, d M Y - g:i A"
 msgstr "D, d M Y - g:i A"
 
-#: mod/message.php:588
+#: mod/message.php:590
 #, php-format
 msgid "%d message"
 msgid_plural "%d messages"
@@ -1786,103 +1764,99 @@ msgstr[1] "%d berichten"
 msgid "[Embedded content - reload page to view]"
 msgstr "[Ingebedde inhoud - herlaad pagina om het te bekijken]"
 
-#: mod/crepair.php:107
+#: mod/crepair.php:104
 msgid "Contact settings applied."
 msgstr "Contactinstellingen toegepast."
 
-#: mod/crepair.php:109
+#: mod/crepair.php:106
 msgid "Contact update failed."
 msgstr "Aanpassen van contact mislukt."
 
-#: mod/crepair.php:140
+#: mod/crepair.php:137
 msgid ""
 "<strong>WARNING: This is highly advanced</strong> and if you enter incorrect"
 " information your communications with this contact may stop working."
 msgstr ""
 
-#: mod/crepair.php:141
+#: mod/crepair.php:138
 msgid ""
 "Please use your browser 'Back' button <strong>now</strong> if you are "
 "uncertain what to do on this page."
 msgstr "Gebruik <strong>nu</strong> de \"terug\"-knop in je webbrowser wanneer je niet weet wat je op deze pagina moet doen."
 
-#: mod/crepair.php:154 mod/crepair.php:156
+#: mod/crepair.php:151 mod/crepair.php:153
 msgid "No mirroring"
 msgstr ""
 
-#: mod/crepair.php:154
+#: mod/crepair.php:151
 msgid "Mirror as forwarded posting"
 msgstr ""
 
-#: mod/crepair.php:154 mod/crepair.php:156
+#: mod/crepair.php:151 mod/crepair.php:153
 msgid "Mirror as my own posting"
 msgstr ""
 
-#: mod/crepair.php:162
-msgid "Repair Contact Settings"
-msgstr "Contactinstellingen herstellen"
-
-#: mod/crepair.php:166
+#: mod/crepair.php:167
 msgid "Return to contact editor"
 msgstr "Ga terug naar contactbewerker"
 
-#: mod/crepair.php:168
+#: mod/crepair.php:169
 msgid "Refetch contact data"
 msgstr ""
 
-#: mod/crepair.php:169 mod/admin.php:1108 mod/admin.php:1120
-#: mod/admin.php:1121 mod/admin.php:1134 mod/settings.php:650
-#: mod/settings.php:676
+#: mod/crepair.php:170 mod/admin.php:1295 mod/admin.php:1307
+#: mod/admin.php:1308 mod/admin.php:1321 mod/settings.php:661
+#: mod/settings.php:687
 msgid "Name"
 msgstr "Naam"
 
-#: mod/crepair.php:170
+#: mod/crepair.php:171
 msgid "Account Nickname"
 msgstr "Bijnaam account"
 
-#: mod/crepair.php:171
+#: mod/crepair.php:172
 msgid "@Tagname - overrides Name/Nickname"
 msgstr "@Labelnaam - krijgt voorrang op naam/bijnaam"
 
-#: mod/crepair.php:172
+#: mod/crepair.php:173
 msgid "Account URL"
 msgstr "URL account"
 
-#: mod/crepair.php:173
+#: mod/crepair.php:174
 msgid "Friend Request URL"
 msgstr "URL vriendschapsverzoek"
 
-#: mod/crepair.php:174
+#: mod/crepair.php:175
 msgid "Friend Confirm URL"
 msgstr "URL vriendschapsbevestiging"
 
-#: mod/crepair.php:175
+#: mod/crepair.php:176
 msgid "Notification Endpoint URL"
 msgstr ""
 
-#: mod/crepair.php:176
+#: mod/crepair.php:177
 msgid "Poll/Feed URL"
 msgstr "URL poll/feed"
 
-#: mod/crepair.php:177
+#: mod/crepair.php:178
 msgid "New photo from this URL"
 msgstr "Nieuwe foto van deze URL"
 
-#: mod/crepair.php:178
+#: mod/crepair.php:179
 msgid "Remote Self"
 msgstr ""
 
-#: mod/crepair.php:181
+#: mod/crepair.php:182
 msgid "Mirror postings from this contact"
 msgstr ""
 
-#: mod/crepair.php:183
+#: mod/crepair.php:184
 msgid ""
 "Mark this contact as remote_self, this will cause friendica to repost new "
 "entries from this contact."
 msgstr ""
 
-#: mod/bookmarklet.php:12 boot.php:1281 include/nav.php:91
+#: mod/bookmarklet.php:12 boot.php:1404 include/nav.php:91
 msgid "Login"
 msgstr "Login"
 
@@ -1894,34 +1868,35 @@ msgstr ""
 msgid "Access denied."
 msgstr "Toegang geweigerd"
 
-#: mod/dirfind.php:188 mod/allfriends.php:82 mod/match.php:84
-#: mod/suggest.php:97 include/contact_widgets.php:10 include/identity.php:188
+#: mod/dirfind.php:194 mod/allfriends.php:80 mod/match.php:85
+#: mod/suggest.php:98 include/contact_widgets.php:10 include/identity.php:212
 msgid "Connect"
 msgstr "Verbinden"
 
-#: mod/dirfind.php:189 mod/allfriends.php:66 mod/match.php:70
-#: mod/directory.php:156 mod/suggest.php:81 include/Contact.php:335
+#: mod/dirfind.php:195 mod/allfriends.php:64 mod/match.php:70
+#: mod/directory.php:162 mod/suggest.php:81 include/Contact.php:283
+#: include/Contact.php:296 include/Contact.php:338
 #: include/conversation.php:912 include/conversation.php:926
 msgid "View Profile"
 msgstr "Bekijk profiel"
 
-#: mod/dirfind.php:217
+#: mod/dirfind.php:224
 #, php-format
 msgid "People Search - %s"
 msgstr ""
 
-#: mod/dirfind.php:224 mod/match.php:104
+#: mod/dirfind.php:231 mod/match.php:105
 msgid "No matches"
 msgstr "Geen resultaten"
 
-#: mod/fbrowser.php:32 include/identity.php:649 include/nav.php:77
+#: mod/fbrowser.php:32 include/identity.php:702 include/nav.php:77
 #: view/theme/diabook/theme.php:126
 msgid "Photos"
 msgstr "Foto's"
 
-#: mod/fbrowser.php:41 mod/fbrowser.php:62 mod/photos.php:54
-#: mod/photos.php:184 mod/photos.php:1111 mod/photos.php:1237
-#: mod/photos.php:1260 mod/photos.php:1830 mod/photos.php:1842
+#: mod/fbrowser.php:41 mod/fbrowser.php:62 mod/photos.php:62
+#: mod/photos.php:192 mod/photos.php:1119 mod/photos.php:1245
+#: mod/photos.php:1268 mod/photos.php:1838 mod/photos.php:1850
 #: view/theme/diabook/theme.php:499
 msgid "Contact Photos"
 msgstr "Contactfoto's"
@@ -1934,548 +1909,578 @@ msgstr "Bestanden"
 msgid "Contacts who are not members of a group"
 msgstr "Contacten die geen leden zijn van een groep"
 
-#: mod/admin.php:80
+#: mod/admin.php:92
 msgid "Theme settings updated."
 msgstr "Thema-instellingen aangepast."
 
-#: mod/admin.php:127 mod/admin.php:709
+#: mod/admin.php:147 mod/admin.php:877
 msgid "Site"
 msgstr "Website"
 
-#: mod/admin.php:128 mod/admin.php:653 mod/admin.php:1103 mod/admin.php:1118
+#: mod/admin.php:148 mod/admin.php:821 mod/admin.php:1290 mod/admin.php:1305
 msgid "Users"
 msgstr "Gebruiker"
 
-#: mod/admin.php:129 mod/admin.php:1207 mod/admin.php:1267 mod/settings.php:66
+#: mod/admin.php:149 mod/admin.php:1405 mod/admin.php:1465 mod/settings.php:72
 msgid "Plugins"
 msgstr "Plugins"
 
-#: mod/admin.php:130 mod/admin.php:1452 mod/admin.php:1503
+#: mod/admin.php:150 mod/admin.php:1663 mod/admin.php:1713
 msgid "Themes"
 msgstr "Thema's"
 
-#: mod/admin.php:131
+#: mod/admin.php:151 mod/settings.php:50
+msgid "Additional features"
+msgstr "Extra functies"
+
+#: mod/admin.php:152
 msgid "DB updates"
 msgstr "DB aanpassingen"
 
-#: mod/admin.php:132 mod/admin.php:223
+#: mod/admin.php:153 mod/admin.php:374
 msgid "Inspect Queue"
 msgstr ""
 
-#: mod/admin.php:147 mod/admin.php:156 mod/admin.php:1591
+#: mod/admin.php:154 mod/admin.php:343
+msgid "Federation Statistics"
+msgstr ""
+
+#: mod/admin.php:168 mod/admin.php:179 mod/admin.php:1781
 msgid "Logs"
 msgstr "Logs"
 
-#: mod/admin.php:148
+#: mod/admin.php:169 mod/admin.php:1848
+msgid "View Logs"
+msgstr ""
+
+#: mod/admin.php:170
 msgid "probe address"
 msgstr ""
 
-#: mod/admin.php:149
+#: mod/admin.php:171
 msgid "check webfinger"
 msgstr ""
 
-#: mod/admin.php:154 include/nav.php:194
+#: mod/admin.php:177 include/nav.php:194
 msgid "Admin"
 msgstr "Beheer"
 
-#: mod/admin.php:155
+#: mod/admin.php:178
 msgid "Plugin Features"
 msgstr "Plugin Functies"
 
-#: mod/admin.php:157
+#: mod/admin.php:180
 msgid "diagnostics"
 msgstr ""
 
-#: mod/admin.php:158
+#: mod/admin.php:181
 msgid "User registrations waiting for confirmation"
 msgstr "Gebruikersregistraties wachten op bevestiging"
 
-#: mod/admin.php:222 mod/admin.php:272 mod/admin.php:708 mod/admin.php:1102
-#: mod/admin.php:1206 mod/admin.php:1266 mod/admin.php:1451 mod/admin.php:1502
-#: mod/admin.php:1590
+#: mod/admin.php:336
+msgid ""
+"This page offers you some numbers to the known part of the federated social "
+"network your Friendica node is part of. These numbers are not complete but "
+"only reflect the part of the network your node is aware of."
+msgstr ""
+
+#: mod/admin.php:337
+msgid ""
+"The <em>Auto Discovered Contact Directory</em> feature is not enabled, it "
+"will improve the data displayed here."
+msgstr ""
+
+#: mod/admin.php:342 mod/admin.php:373 mod/admin.php:430 mod/admin.php:876
+#: mod/admin.php:1289 mod/admin.php:1404 mod/admin.php:1464 mod/admin.php:1662
+#: mod/admin.php:1712 mod/admin.php:1780 mod/admin.php:1847
 msgid "Administration"
 msgstr "Beheer"
 
-#: mod/admin.php:225
+#: mod/admin.php:349
+msgid "Currently this node is aware of nodes from the following platforms:"
+msgstr ""
+
+#: mod/admin.php:376
 msgid "ID"
 msgstr "ID"
 
-#: mod/admin.php:226
+#: mod/admin.php:377
 msgid "Recipient Name"
 msgstr ""
 
-#: mod/admin.php:227
+#: mod/admin.php:378
 msgid "Recipient Profile"
 msgstr ""
 
-#: mod/admin.php:229
+#: mod/admin.php:380
 msgid "Created"
 msgstr ""
 
-#: mod/admin.php:230
+#: mod/admin.php:381
 msgid "Last Tried"
 msgstr ""
 
-#: mod/admin.php:231
+#: mod/admin.php:382
 msgid ""
 "This page lists the content of the queue for outgoing postings. These are "
 "postings the initial delivery failed for. They will be resend later and "
 "eventually deleted if the delivery fails permanently."
 msgstr ""
 
-#: mod/admin.php:243 mod/admin.php:1056
+#: mod/admin.php:401 mod/admin.php:1243
 msgid "Normal Account"
 msgstr "Normaal account"
 
-#: mod/admin.php:244 mod/admin.php:1057
+#: mod/admin.php:402 mod/admin.php:1244
 msgid "Soapbox Account"
 msgstr "Zeepkist-account"
 
-#: mod/admin.php:245 mod/admin.php:1058
+#: mod/admin.php:403 mod/admin.php:1245
 msgid "Community/Celebrity Account"
 msgstr "Account voor een groep/forum of beroemdheid"
 
-#: mod/admin.php:246 mod/admin.php:1059
+#: mod/admin.php:404 mod/admin.php:1246
 msgid "Automatic Friend Account"
 msgstr "Automatisch Vriendschapsaccount"
 
-#: mod/admin.php:247
+#: mod/admin.php:405
 msgid "Blog Account"
 msgstr "Blog Account"
 
-#: mod/admin.php:248
+#: mod/admin.php:406
 msgid "Private Forum"
 msgstr "Privéforum/-groep"
 
-#: mod/admin.php:267
+#: mod/admin.php:425
 msgid "Message queues"
 msgstr "Bericht-wachtrijen"
 
-#: mod/admin.php:273
+#: mod/admin.php:431
 msgid "Summary"
 msgstr "Samenvatting"
 
-#: mod/admin.php:275
+#: mod/admin.php:433
 msgid "Registered users"
 msgstr "Geregistreerde gebruikers"
 
-#: mod/admin.php:277
+#: mod/admin.php:435
 msgid "Pending registrations"
 msgstr "Registraties die in de wacht staan"
 
-#: mod/admin.php:278
+#: mod/admin.php:436
 msgid "Version"
 msgstr "Versie"
 
-#: mod/admin.php:283
+#: mod/admin.php:441
 msgid "Active plugins"
 msgstr "Actieve plug-ins"
 
-#: mod/admin.php:306
+#: mod/admin.php:464
 msgid "Can not parse base url. Must have at least <scheme>://<domain>"
 msgstr ""
 
-#: mod/admin.php:585
+#: mod/admin.php:749
 msgid "RINO2 needs mcrypt php extension to work."
 msgstr ""
 
-#: mod/admin.php:593
+#: mod/admin.php:757
 msgid "Site settings updated."
 msgstr "Site instellingen gewijzigd."
 
-#: mod/admin.php:617 mod/settings.php:901
+#: mod/admin.php:785 mod/settings.php:912
 msgid "No special theme for mobile devices"
 msgstr "Geen speciaal thema voor mobiele apparaten"
 
-#: mod/admin.php:636
+#: mod/admin.php:804
 msgid "No community page"
 msgstr ""
 
-#: mod/admin.php:637
+#: mod/admin.php:805
 msgid "Public postings from users of this site"
 msgstr ""
 
-#: mod/admin.php:638
+#: mod/admin.php:806
 msgid "Global community page"
 msgstr ""
 
-#: mod/admin.php:644
+#: mod/admin.php:812
 msgid "At post arrival"
 msgstr ""
 
-#: mod/admin.php:645 include/contact_selectors.php:56
+#: mod/admin.php:813 include/contact_selectors.php:56
 msgid "Frequently"
 msgstr "Frequent"
 
-#: mod/admin.php:646 include/contact_selectors.php:57
+#: mod/admin.php:814 include/contact_selectors.php:57
 msgid "Hourly"
 msgstr "elk uur"
 
-#: mod/admin.php:647 include/contact_selectors.php:58
+#: mod/admin.php:815 include/contact_selectors.php:58
 msgid "Twice daily"
 msgstr "Twee keer per dag"
 
-#: mod/admin.php:648 include/contact_selectors.php:59
+#: mod/admin.php:816 include/contact_selectors.php:59
 msgid "Daily"
 msgstr "dagelijks"
 
-#: mod/admin.php:654
+#: mod/admin.php:822
 msgid "Users, Global Contacts"
 msgstr ""
 
-#: mod/admin.php:655
+#: mod/admin.php:823
 msgid "Users, Global Contacts/fallback"
 msgstr ""
 
-#: mod/admin.php:659
+#: mod/admin.php:827
 msgid "One month"
 msgstr ""
 
-#: mod/admin.php:660
+#: mod/admin.php:828
 msgid "Three months"
 msgstr ""
 
-#: mod/admin.php:661
+#: mod/admin.php:829
 msgid "Half a year"
 msgstr ""
 
-#: mod/admin.php:662
+#: mod/admin.php:830
 msgid "One year"
 msgstr ""
 
-#: mod/admin.php:667
+#: mod/admin.php:835
 msgid "Multi user instance"
 msgstr "Server voor meerdere gebruikers"
 
-#: mod/admin.php:690
+#: mod/admin.php:858
 msgid "Closed"
 msgstr "Gesloten"
 
-#: mod/admin.php:691
+#: mod/admin.php:859
 msgid "Requires approval"
 msgstr "Toestemming vereist"
 
-#: mod/admin.php:692
+#: mod/admin.php:860
 msgid "Open"
 msgstr "Open"
 
-#: mod/admin.php:696
+#: mod/admin.php:864
 msgid "No SSL policy, links will track page SSL state"
 msgstr "Geen SSL beleid, links zullen SSL status van pagina volgen"
 
-#: mod/admin.php:697
+#: mod/admin.php:865
 msgid "Force all links to use SSL"
 msgstr "Verplicht alle links om SSL te gebruiken"
 
-#: mod/admin.php:698
+#: mod/admin.php:866
 msgid "Self-signed certificate, use SSL for local links only (discouraged)"
 msgstr "Zelf-ondertekend certificaat, gebruik SSL alleen voor lokale links (afgeraden)"
 
-#: mod/admin.php:710 mod/admin.php:1268 mod/admin.php:1504 mod/admin.php:1592
-#: mod/settings.php:648 mod/settings.php:758 mod/settings.php:802
-#: mod/settings.php:871 mod/settings.php:957 mod/settings.php:1192
+#: mod/admin.php:878 mod/admin.php:1466 mod/admin.php:1714 mod/admin.php:1782
+#: mod/admin.php:1931 mod/settings.php:659 mod/settings.php:769
+#: mod/settings.php:813 mod/settings.php:882 mod/settings.php:969
+#: mod/settings.php:1204
 msgid "Save Settings"
 msgstr "Instellingen opslaan"
 
-#: mod/admin.php:711 mod/register.php:260
+#: mod/admin.php:879 mod/register.php:263
 msgid "Registration"
 msgstr "Registratie"
 
-#: mod/admin.php:712
+#: mod/admin.php:880
 msgid "File upload"
 msgstr "Uploaden bestand"
 
-#: mod/admin.php:713
+#: mod/admin.php:881
 msgid "Policies"
 msgstr "Beleid"
 
-#: mod/admin.php:714
+#: mod/admin.php:882
 msgid "Advanced"
 msgstr "Geavanceerd"
 
-#: mod/admin.php:715
+#: mod/admin.php:883
 msgid "Auto Discovered Contact Directory"
 msgstr ""
 
-#: mod/admin.php:716
+#: mod/admin.php:884
 msgid "Performance"
 msgstr "Performantie"
 
-#: mod/admin.php:717
+#: mod/admin.php:885
 msgid ""
 "Relocate - WARNING: advanced function. Could make this server unreachable."
 msgstr ""
 
-#: mod/admin.php:720
+#: mod/admin.php:888
 msgid "Site name"
 msgstr "Site naam"
 
-#: mod/admin.php:721
+#: mod/admin.php:889
 msgid "Host name"
 msgstr ""
 
-#: mod/admin.php:722
+#: mod/admin.php:890
 msgid "Sender Email"
 msgstr ""
 
-#: mod/admin.php:722
+#: mod/admin.php:890
 msgid ""
 "The email address your server shall use to send notification emails from."
 msgstr ""
 
-#: mod/admin.php:723
+#: mod/admin.php:891
 msgid "Banner/Logo"
 msgstr "Banner/Logo"
 
-#: mod/admin.php:724
+#: mod/admin.php:892
 msgid "Shortcut icon"
 msgstr ""
 
-#: mod/admin.php:724
+#: mod/admin.php:892
 msgid "Link to an icon that will be used for browsers."
 msgstr ""
 
-#: mod/admin.php:725
+#: mod/admin.php:893
 msgid "Touch icon"
 msgstr ""
 
-#: mod/admin.php:725
+#: mod/admin.php:893
 msgid "Link to an icon that will be used for tablets and mobiles."
 msgstr ""
 
-#: mod/admin.php:726
+#: mod/admin.php:894
 msgid "Additional Info"
 msgstr ""
 
-#: mod/admin.php:726
+#: mod/admin.php:894
 #, php-format
 msgid ""
 "For public servers: you can add additional information here that will be "
 "listed at %s/siteinfo."
 msgstr ""
 
-#: mod/admin.php:727
+#: mod/admin.php:895
 msgid "System language"
 msgstr "Systeemtaal"
 
-#: mod/admin.php:728
+#: mod/admin.php:896
 msgid "System theme"
 msgstr "Systeem thema"
 
-#: mod/admin.php:728
+#: mod/admin.php:896
 msgid ""
 "Default system theme - may be over-ridden by user profiles - <a href='#' "
 "id='cnftheme'>change theme settings</a>"
 msgstr "Standaard systeem thema - kan door gebruikersprofielen veranderd worden - <a href='#' id='cnftheme'>verander thema instellingen</a>"
 
-#: mod/admin.php:729
+#: mod/admin.php:897
 msgid "Mobile system theme"
 msgstr "Mobiel systeem thema"
 
-#: mod/admin.php:729
+#: mod/admin.php:897
 msgid "Theme for mobile devices"
 msgstr "Thema voor mobiele apparaten"
 
-#: mod/admin.php:730
+#: mod/admin.php:898
 msgid "SSL link policy"
 msgstr "Beleid SSL-links"
 
-#: mod/admin.php:730
+#: mod/admin.php:898
 msgid "Determines whether generated links should be forced to use SSL"
 msgstr "Bepaald of gegenereerde verwijzingen verplicht SSL moeten gebruiken"
 
-#: mod/admin.php:731
+#: mod/admin.php:899
 msgid "Force SSL"
 msgstr ""
 
-#: mod/admin.php:731
+#: mod/admin.php:899
 msgid ""
 "Force all Non-SSL requests to SSL - Attention: on some systems it could lead"
 " to endless loops."
 msgstr ""
 
-#: mod/admin.php:732
+#: mod/admin.php:900
 msgid "Old style 'Share'"
 msgstr ""
 
-#: mod/admin.php:732
+#: mod/admin.php:900
 msgid "Deactivates the bbcode element 'share' for repeating items."
 msgstr ""
 
-#: mod/admin.php:733
+#: mod/admin.php:901
 msgid "Hide help entry from navigation menu"
 msgstr "Verberg de 'help' uit het navigatiemenu"
 
-#: mod/admin.php:733
+#: mod/admin.php:901
 msgid ""
 "Hides the menu entry for the Help pages from the navigation menu. You can "
 "still access it calling /help directly."
 msgstr "Verbergt het menu-item voor de Help pagina's uit het navigatiemenu. Je kunt ze nog altijd vinden door /help direct in te geven."
 
-#: mod/admin.php:734
+#: mod/admin.php:902
 msgid "Single user instance"
 msgstr "Server voor één gebruiker"
 
-#: mod/admin.php:734
+#: mod/admin.php:902
 msgid "Make this instance multi-user or single-user for the named user"
 msgstr "Stel deze server in voor meerdere gebruikers, of enkel voor de geselecteerde gebruiker."
 
-#: mod/admin.php:735
+#: mod/admin.php:903
 msgid "Maximum image size"
 msgstr "Maximum afbeeldingsgrootte"
 
-#: mod/admin.php:735
+#: mod/admin.php:903
 msgid ""
 "Maximum size in bytes of uploaded images. Default is 0, which means no "
 "limits."
 msgstr "Maximum afmeting in bytes van afbeeldingen. Standaard is 0, dus geen beperking."
 
-#: mod/admin.php:736
+#: mod/admin.php:904
 msgid "Maximum image length"
 msgstr "Maximum afbeeldingslengte"
 
-#: mod/admin.php:736
+#: mod/admin.php:904
 msgid ""
 "Maximum length in pixels of the longest side of uploaded images. Default is "
 "-1, which means no limits."
 msgstr "Maximum lengte in pixels van de langste kant van afbeeldingen. Standaard is -1, dus geen beperkingen."
 
-#: mod/admin.php:737
+#: mod/admin.php:905
 msgid "JPEG image quality"
 msgstr "JPEG afbeeldingskwaliteit"
 
-#: mod/admin.php:737
+#: mod/admin.php:905
 msgid ""
 "Uploaded JPEGS will be saved at this quality setting [0-100]. Default is "
 "100, which is full quality."
 msgstr "JPEGS zullen met deze kwaliteitsinstelling bewaard worden [0-100]. Standaard is 100, dit is volledige kwaliteit."
 
-#: mod/admin.php:739
+#: mod/admin.php:907
 msgid "Register policy"
 msgstr "Registratiebeleid"
 
-#: mod/admin.php:740
+#: mod/admin.php:908
 msgid "Maximum Daily Registrations"
 msgstr "Maximum aantal registraties per dag"
 
-#: mod/admin.php:740
+#: mod/admin.php:908
 msgid ""
 "If registration is permitted above, this sets the maximum number of new user"
 " registrations to accept per day.  If register is set to closed, this "
 "setting has no effect."
 msgstr "Als registratie hierboven is toegelaten, zet dit het maximum aantal registraties van nieuwe gebruikers per dag. Als registratie niet is toegelaten heeft deze instelling geen effect."
 
-#: mod/admin.php:741
+#: mod/admin.php:909
 msgid "Register text"
 msgstr "Registratietekst"
 
-#: mod/admin.php:741
+#: mod/admin.php:909
 msgid "Will be displayed prominently on the registration page."
 msgstr "Dit zal prominent op de registratiepagina getoond worden."
 
-#: mod/admin.php:742
+#: mod/admin.php:910
 msgid "Accounts abandoned after x days"
 msgstr "Verlaten accounts na x dagen"
 
-#: mod/admin.php:742
+#: mod/admin.php:910
 msgid ""
 "Will not waste system resources polling external sites for abandonded "
 "accounts. Enter 0 for no time limit."
 msgstr "Dit zal geen systeembronnen verspillen aan het nakijken van externe sites voor verlaten accounts. Geef 0 is voor geen tijdslimiet."
 
-#: mod/admin.php:743
+#: mod/admin.php:911
 msgid "Allowed friend domains"
 msgstr "Toegelaten vriend domeinen"
 
-#: mod/admin.php:743
+#: mod/admin.php:911
 msgid ""
 "Comma separated list of domains which are allowed to establish friendships "
 "with this site. Wildcards are accepted. Empty to allow any domains"
 msgstr "Komma-gescheiden lijst van domeinen die een vriendschapsband met deze website mogen aangaan. Jokers zijn toegelaten. Laat leeg om alle domeinen toe te laten."
 
-#: mod/admin.php:744
+#: mod/admin.php:912
 msgid "Allowed email domains"
 msgstr "Toegelaten e-mail domeinen"
 
-#: mod/admin.php:744
+#: mod/admin.php:912
 msgid ""
 "Comma separated list of domains which are allowed in email addresses for "
 "registrations to this site. Wildcards are accepted. Empty to allow any "
 "domains"
 msgstr "Door komma's gescheiden lijst met e-maildomeinen die op deze website mogen registeren. Wildcards zijn toegestaan.\nLeeg laten om alle domeinen toe te staan."
 
-#: mod/admin.php:745
+#: mod/admin.php:913
 msgid "Block public"
 msgstr "Openbare toegang blokkeren"
 
-#: mod/admin.php:745
+#: mod/admin.php:913
 msgid ""
 "Check to block public access to all otherwise public personal pages on this "
 "site unless you are currently logged in."
 msgstr "Kruis dit aan om alle openbare persoonlijke pagina's alleen toegankelijk te maken voor ingelogde gebruikers."
 
-#: mod/admin.php:746
+#: mod/admin.php:914
 msgid "Force publish"
 msgstr "Dwing publiceren af"
 
-#: mod/admin.php:746
+#: mod/admin.php:914
 msgid ""
 "Check to force all profiles on this site to be listed in the site directory."
 msgstr "Kruis dit aan om af te dwingen dat alle profielen op deze website in de gids van deze website gepubliceerd worden."
 
-#: mod/admin.php:747
+#: mod/admin.php:915
 msgid "Global directory URL"
 msgstr ""
 
-#: mod/admin.php:747
+#: mod/admin.php:915
 msgid ""
 "URL to the global directory. If this is not set, the global directory is "
 "completely unavailable to the application."
 msgstr ""
 
-#: mod/admin.php:748
+#: mod/admin.php:916
 msgid "Allow threaded items"
 msgstr "Sta threads in conversaties toe"
 
-#: mod/admin.php:748
+#: mod/admin.php:916
 msgid "Allow infinite level threading for items on this site."
 msgstr "Sta oneindige niveaus threads in conversaties op deze website toe."
 
-#: mod/admin.php:749
+#: mod/admin.php:917
 msgid "Private posts by default for new users"
 msgstr "Privéberichten als standaard voor nieuwe gebruikers"
 
-#: mod/admin.php:749
+#: mod/admin.php:917
 msgid ""
 "Set default post permissions for all new members to the default privacy "
 "group rather than public."
 msgstr "Stel de standaardrechten van berichten voor nieuwe leden op de standaard privacygroep in, in plaats van openbaar."
 
-#: mod/admin.php:750
+#: mod/admin.php:918
 msgid "Don't include post content in email notifications"
 msgstr "De inhoud van het bericht niet insluiten bij e-mailnotificaties"
 
-#: mod/admin.php:750
+#: mod/admin.php:918
 msgid ""
 "Don't include the content of a post/comment/private message/etc. in the "
 "email notifications that are sent out from this site, as a privacy measure."
 msgstr "De inhoud van berichten/commentaar/privéberichten/enzovoort  niet insluiten in e-mailnotificaties die door deze website verzonden worden, voor de bescherming van je privacy."
 
-#: mod/admin.php:751
+#: mod/admin.php:919
 msgid "Disallow public access to addons listed in the apps menu."
 msgstr ""
 
-#: mod/admin.php:751
+#: mod/admin.php:919
 msgid ""
 "Checking this box will restrict addons listed in the apps menu to members "
 "only."
 msgstr ""
 
-#: mod/admin.php:752
+#: mod/admin.php:920
 msgid "Don't embed private images in posts"
 msgstr ""
 
-#: mod/admin.php:752
+#: mod/admin.php:920
 msgid ""
 "Don't replace locally-hosted private photos in posts with an embedded copy "
 "of the image. This means that contacts who receive posts containing private "
@@ -2483,208 +2488,228 @@ msgid ""
 "while."
 msgstr ""
 
-#: mod/admin.php:753
+#: mod/admin.php:921
 msgid "Allow Users to set remote_self"
 msgstr ""
 
-#: mod/admin.php:753
+#: mod/admin.php:921
 msgid ""
 "With checking this, every user is allowed to mark every contact as a "
 "remote_self in the repair contact dialog. Setting this flag on a contact "
 "causes mirroring every posting of that contact in the users stream."
 msgstr ""
 
-#: mod/admin.php:754
+#: mod/admin.php:922
 msgid "Block multiple registrations"
 msgstr "Blokkeer meerdere registraties"
 
-#: mod/admin.php:754
+#: mod/admin.php:922
 msgid "Disallow users to register additional accounts for use as pages."
 msgstr "Laat niet toe dat gebruikers meerdere accounts aanmaken."
 
-#: mod/admin.php:755
+#: mod/admin.php:923
 msgid "OpenID support"
 msgstr "OpenID ondersteuning"
 
-#: mod/admin.php:755
+#: mod/admin.php:923
 msgid "OpenID support for registration and logins."
 msgstr "OpenID ondersteuning voor registraties en logins."
 
-#: mod/admin.php:756
+#: mod/admin.php:924
 msgid "Fullname check"
 msgstr "Controleer volledige naam"
 
-#: mod/admin.php:756
+#: mod/admin.php:924
 msgid ""
 "Force users to register with a space between firstname and lastname in Full "
 "name, as an antispam measure"
 msgstr "Verplicht gebruikers om zich te registreren met een spatie tussen voornaam en achternaam, als anti-spam maatregel"
 
-#: mod/admin.php:757
+#: mod/admin.php:925
 msgid "UTF-8 Regular expressions"
 msgstr "UTF-8 reguliere uitdrukkingen"
 
-#: mod/admin.php:757
+#: mod/admin.php:925
 msgid "Use PHP UTF8 regular expressions"
 msgstr "Gebruik PHP UTF8 reguliere uitdrukkingen"
 
-#: mod/admin.php:758
+#: mod/admin.php:926
 msgid "Community Page Style"
 msgstr ""
 
-#: mod/admin.php:758
+#: mod/admin.php:926
 msgid ""
 "Type of community page to show. 'Global community' shows every public "
 "posting from an open distributed network that arrived on this server."
 msgstr ""
 
-#: mod/admin.php:759
+#: mod/admin.php:927
 msgid "Posts per user on community page"
 msgstr ""
 
-#: mod/admin.php:759
+#: mod/admin.php:927
 msgid ""
 "The maximum number of posts per user on the community page. (Not valid for "
 "'Global Community')"
 msgstr ""
 
-#: mod/admin.php:760
+#: mod/admin.php:928
 msgid "Enable OStatus support"
 msgstr "Activeer OStatus ondersteuning"
 
-#: mod/admin.php:760
+#: mod/admin.php:928
 msgid ""
 "Provide built-in OStatus (StatusNet, GNU Social etc.) compatibility. All "
 "communications in OStatus are public, so privacy warnings will be "
 "occasionally displayed."
 msgstr ""
 
-#: mod/admin.php:761
+#: mod/admin.php:929
 msgid "OStatus conversation completion interval"
 msgstr ""
 
-#: mod/admin.php:761
+#: mod/admin.php:929
 msgid ""
 "How often shall the poller check for new entries in OStatus conversations? "
 "This can be a very ressource task."
 msgstr ""
 
-#: mod/admin.php:762
+#: mod/admin.php:930
 msgid "OStatus support can only be enabled if threading is enabled."
 msgstr ""
 
-#: mod/admin.php:764
+#: mod/admin.php:932
 msgid ""
 "Diaspora support can't be enabled because Friendica was installed into a sub"
 " directory."
 msgstr ""
 
-#: mod/admin.php:765
+#: mod/admin.php:933
 msgid "Enable Diaspora support"
 msgstr "Activeer Diaspora ondersteuning"
 
-#: mod/admin.php:765
+#: mod/admin.php:933
 msgid "Provide built-in Diaspora network compatibility."
 msgstr "Bied ingebouwde ondersteuning voor het Diaspora netwerk."
 
-#: mod/admin.php:766
+#: mod/admin.php:934
 msgid "Only allow Friendica contacts"
 msgstr "Laat alleen Friendica contacten toe"
 
-#: mod/admin.php:766
+#: mod/admin.php:934
 msgid ""
 "All contacts must use Friendica protocols. All other built-in communication "
 "protocols disabled."
 msgstr "Alle contacten moeten een Friendica protocol gebruiken. Alle andere ingebouwde communicatieprotocols worden uitgeschakeld."
 
-#: mod/admin.php:767
+#: mod/admin.php:935
 msgid "Verify SSL"
 msgstr "Controleer SSL"
 
-#: mod/admin.php:767
+#: mod/admin.php:935
 msgid ""
 "If you wish, you can turn on strict certificate checking. This will mean you"
 " cannot connect (at all) to self-signed SSL sites."
 msgstr "Als je wilt kun je striktere certificaat controle activeren. Dit betekent dat je (totaal) niet kunt connecteren met sites die zelf-ondertekende SSL certificaten gebruiken."
 
-#: mod/admin.php:768
+#: mod/admin.php:936
 msgid "Proxy user"
 msgstr "Proxy-gebruiker"
 
-#: mod/admin.php:769
+#: mod/admin.php:937
 msgid "Proxy URL"
 msgstr "Proxy-URL"
 
-#: mod/admin.php:770
+#: mod/admin.php:938
 msgid "Network timeout"
 msgstr "Netwerk timeout"
 
-#: mod/admin.php:770
+#: mod/admin.php:938
 msgid "Value is in seconds. Set to 0 for unlimited (not recommended)."
 msgstr "Waarde is in seconden. Zet op 0 voor onbeperkt (niet aanbevolen)."
 
-#: mod/admin.php:771
+#: mod/admin.php:939
 msgid "Delivery interval"
 msgstr "Afleverinterval"
 
-#: mod/admin.php:771
+#: mod/admin.php:939
 msgid ""
 "Delay background delivery processes by this many seconds to reduce system "
 "load. Recommend: 4-5 for shared hosts, 2-3 for virtual private servers. 0-1 "
 "for large dedicated servers."
 msgstr "Stel achtergrond processen voor aflevering een aantal seconden uit om systeembelasting te beperken. Aanbevolen: 4-5 voor gedeelde hosten, 2-3 voor virtuele privé servers, 0-1 voor grote servers."
 
-#: mod/admin.php:772
+#: mod/admin.php:940
 msgid "Poll interval"
 msgstr "Poll-interval"
 
-#: mod/admin.php:772
+#: mod/admin.php:940
 msgid ""
 "Delay background polling processes by this many seconds to reduce system "
 "load. If 0, use delivery interval."
 msgstr "Stel achtergrondprocessen zoveel seconden uit om de systeembelasting te beperken. Indien 0 wordt het afleverinterval gebruikt."
 
-#: mod/admin.php:773
+#: mod/admin.php:941
 msgid "Maximum Load Average"
 msgstr "Maximum gemiddelde belasting"
 
-#: mod/admin.php:773
+#: mod/admin.php:941
 msgid ""
 "Maximum system load before delivery and poll processes are deferred - "
 "default 50."
 msgstr "Maximum systeembelasting voordat aflever- en poll-processen uitgesteld worden - standaard 50."
 
-#: mod/admin.php:774
+#: mod/admin.php:942
 msgid "Maximum Load Average (Frontend)"
 msgstr ""
 
-#: mod/admin.php:774
+#: mod/admin.php:942
 msgid "Maximum system load before the frontend quits service - default 50."
 msgstr ""
 
-#: mod/admin.php:776
+#: mod/admin.php:943
+msgid "Maximum table size for optimization"
+msgstr ""
+
+#: mod/admin.php:943
+msgid ""
+"Maximum table size (in MB) for the automatic optimization - default 100 MB. "
+"Enter -1 to disable it."
+msgstr ""
+
+#: mod/admin.php:944
+msgid "Minimum level of fragmentation"
+msgstr ""
+
+#: mod/admin.php:944
+msgid ""
+"Minimum fragmenation level to start the automatic optimization - default "
+"value is 30%."
+msgstr ""
+
+#: mod/admin.php:946
 msgid "Periodical check of global contacts"
 msgstr ""
 
-#: mod/admin.php:776
+#: mod/admin.php:946
 msgid ""
 "If enabled, the global contacts are checked periodically for missing or "
 "outdated data and the vitality of the contacts and servers."
 msgstr ""
 
-#: mod/admin.php:777
+#: mod/admin.php:947
 msgid "Days between requery"
 msgstr ""
 
-#: mod/admin.php:777
+#: mod/admin.php:947
 msgid "Number of days after which a server is requeried for his contacts."
 msgstr ""
 
-#: mod/admin.php:778
+#: mod/admin.php:948
 msgid "Discover contacts from other servers"
 msgstr ""
 
-#: mod/admin.php:778
+#: mod/admin.php:948
 msgid ""
 "Periodically query other servers for contacts. You can choose between "
 "'users': the users on the remote system, 'Global Contacts': active contacts "
@@ -2694,32 +2719,32 @@ msgid ""
 "Global Contacts'."
 msgstr ""
 
-#: mod/admin.php:779
+#: mod/admin.php:949
 msgid "Timeframe for fetching global contacts"
 msgstr ""
 
-#: mod/admin.php:779
+#: mod/admin.php:949
 msgid ""
 "When the discovery is activated, this value defines the timeframe for the "
 "activity of the global contacts that are fetched from other servers."
 msgstr ""
 
-#: mod/admin.php:780
+#: mod/admin.php:950
 msgid "Search the local directory"
 msgstr ""
 
-#: mod/admin.php:780
+#: mod/admin.php:950
 msgid ""
 "Search the local directory instead of the global directory. When searching "
 "locally, every search will be executed on the global directory in the "
 "background. This improves the search results when the search is repeated."
 msgstr ""
 
-#: mod/admin.php:782
+#: mod/admin.php:952
 msgid "Publish server information"
 msgstr ""
 
-#: mod/admin.php:782
+#: mod/admin.php:952
 msgid ""
 "If enabled, general server and usage data will be published. The data "
 "contains the name and version of the server, number of users with public "
@@ -2727,205 +2752,205 @@ msgid ""
 " href='http://the-federation.info/'>the-federation.info</a> for details."
 msgstr ""
 
-#: mod/admin.php:784
+#: mod/admin.php:954
 msgid "Use MySQL full text engine"
 msgstr "Gebruik de tekst-zoekfunctie van MySQL"
 
-#: mod/admin.php:784
+#: mod/admin.php:954
 msgid ""
 "Activates the full text engine. Speeds up search - but can only search for "
 "four and more characters."
 msgstr "Activeert de zoekmotor. Dit maakt zoeken sneller, maar het kan alleen zoeken naar teksten van minstens vier letters."
 
-#: mod/admin.php:785
+#: mod/admin.php:955
 msgid "Suppress Language"
 msgstr ""
 
-#: mod/admin.php:785
+#: mod/admin.php:955
 msgid "Suppress language information in meta information about a posting."
 msgstr ""
 
-#: mod/admin.php:786
+#: mod/admin.php:956
 msgid "Suppress Tags"
 msgstr ""
 
-#: mod/admin.php:786
+#: mod/admin.php:956
 msgid "Suppress showing a list of hashtags at the end of the posting."
 msgstr ""
 
-#: mod/admin.php:787
+#: mod/admin.php:957
 msgid "Path to item cache"
 msgstr "Pad naar cache voor items"
 
-#: mod/admin.php:787
+#: mod/admin.php:957
 msgid "The item caches buffers generated bbcode and external images."
 msgstr ""
 
-#: mod/admin.php:788
+#: mod/admin.php:958
 msgid "Cache duration in seconds"
 msgstr "Cache tijdsduur in seconden"
 
-#: mod/admin.php:788
+#: mod/admin.php:958
 msgid ""
 "How long should the cache files be hold? Default value is 86400 seconds (One"
 " day). To disable the item cache, set the value to -1."
 msgstr ""
 
-#: mod/admin.php:789
+#: mod/admin.php:959
 msgid "Maximum numbers of comments per post"
 msgstr ""
 
-#: mod/admin.php:789
+#: mod/admin.php:959
 msgid "How much comments should be shown for each post? Default value is 100."
 msgstr ""
 
-#: mod/admin.php:790
+#: mod/admin.php:960
 msgid "Path for lock file"
 msgstr "Pad voor lock bestand"
 
-#: mod/admin.php:790
+#: mod/admin.php:960
 msgid ""
 "The lock file is used to avoid multiple pollers at one time. Only define a "
 "folder here."
 msgstr ""
 
-#: mod/admin.php:791
+#: mod/admin.php:961
 msgid "Temp path"
 msgstr "Tijdelijk pad"
 
-#: mod/admin.php:791
+#: mod/admin.php:961
 msgid ""
 "If you have a restricted system where the webserver can't access the system "
 "temp path, enter another path here."
 msgstr ""
 
-#: mod/admin.php:792
+#: mod/admin.php:962
 msgid "Base path to installation"
 msgstr "Basispad voor installatie"
 
-#: mod/admin.php:792
+#: mod/admin.php:962
 msgid ""
 "If the system cannot detect the correct path to your installation, enter the"
 " correct path here. This setting should only be set if you are using a "
 "restricted system and symbolic links to your webroot."
 msgstr ""
 
-#: mod/admin.php:793
+#: mod/admin.php:963
 msgid "Disable picture proxy"
 msgstr ""
 
-#: mod/admin.php:793
+#: mod/admin.php:963
 msgid ""
 "The picture proxy increases performance and privacy. It shouldn't be used on"
 " systems with very low bandwith."
 msgstr ""
 
-#: mod/admin.php:794
+#: mod/admin.php:964
 msgid "Enable old style pager"
 msgstr ""
 
-#: mod/admin.php:794
+#: mod/admin.php:964
 msgid ""
 "The old style pager has page numbers but slows down massively the page "
 "speed."
 msgstr ""
 
-#: mod/admin.php:795
+#: mod/admin.php:965
 msgid "Only search in tags"
 msgstr ""
 
-#: mod/admin.php:795
+#: mod/admin.php:965
 msgid "On large systems the text search can slow down the system extremely."
 msgstr ""
 
-#: mod/admin.php:797
+#: mod/admin.php:967
 msgid "New base url"
 msgstr ""
 
-#: mod/admin.php:797
+#: mod/admin.php:967
 msgid ""
 "Change base url for this server. Sends relocate message to all DFRN contacts"
 " of all users."
 msgstr ""
 
-#: mod/admin.php:799
+#: mod/admin.php:969
 msgid "RINO Encryption"
 msgstr ""
 
-#: mod/admin.php:799
+#: mod/admin.php:969
 msgid "Encryption layer between nodes."
 msgstr ""
 
-#: mod/admin.php:800
+#: mod/admin.php:970
 msgid "Embedly API key"
 msgstr ""
 
-#: mod/admin.php:800
+#: mod/admin.php:970
 msgid ""
 "<a href='http://embed.ly'>Embedly</a> is used to fetch additional data for "
 "web pages. This is an optional parameter."
 msgstr ""
 
-#: mod/admin.php:818
+#: mod/admin.php:999
 msgid "Update has been marked successful"
 msgstr "Wijziging succesvol gemarkeerd "
 
-#: mod/admin.php:826
+#: mod/admin.php:1007
 #, php-format
 msgid "Database structure update %s was successfully applied."
 msgstr ""
 
-#: mod/admin.php:829
+#: mod/admin.php:1010
 #, php-format
 msgid "Executing of database structure update %s failed with error: %s"
 msgstr ""
 
-#: mod/admin.php:841
+#: mod/admin.php:1022
 #, php-format
 msgid "Executing %s failed with error: %s"
 msgstr ""
 
-#: mod/admin.php:844
+#: mod/admin.php:1025
 #, php-format
 msgid "Update %s was successfully applied."
 msgstr "Wijziging %s geslaagd."
 
-#: mod/admin.php:848
+#: mod/admin.php:1029
 #, php-format
 msgid "Update %s did not return a status. Unknown if it succeeded."
 msgstr "Wijziging %s gaf geen status terug. We weten niet of de wijziging geslaagd is."
 
-#: mod/admin.php:850
+#: mod/admin.php:1031
 #, php-format
 msgid "There was no additional update function %s that needed to be called."
 msgstr ""
 
-#: mod/admin.php:869
+#: mod/admin.php:1050
 msgid "No failed updates."
 msgstr "Geen misluke wijzigingen"
 
-#: mod/admin.php:870
+#: mod/admin.php:1051
 msgid "Check database structure"
 msgstr ""
 
-#: mod/admin.php:875
+#: mod/admin.php:1056
 msgid "Failed Updates"
 msgstr "Misluke wijzigingen"
 
-#: mod/admin.php:876
+#: mod/admin.php:1057
 msgid ""
 "This does not include updates prior to 1139, which did not return a status."
 msgstr "Dit is zonder de wijzigingen voor 1139, welke geen status teruggaven."
 
-#: mod/admin.php:877
+#: mod/admin.php:1058
 msgid "Mark success (if update was manually applied)"
 msgstr "Markeren als succes (als aanpassing manueel doorgevoerd werd)"
 
-#: mod/admin.php:878
+#: mod/admin.php:1059
 msgid "Attempt to execute this update step automatically"
 msgstr "Probeer deze stap automatisch uit te voeren"
 
-#: mod/admin.php:910
+#: mod/admin.php:1091
 #, php-format
 msgid ""
 "\n"
@@ -2933,7 +2958,7 @@ msgid ""
 "\t\t\t\tthe administrator of %2$s has set up an account for you."
 msgstr ""
 
-#: mod/admin.php:913
+#: mod/admin.php:1094
 #, php-format
 msgid ""
 "\n"
@@ -2963,295 +2988,318 @@ msgid ""
 "\t\t\tThank you and welcome to %4$s."
 msgstr ""
 
-#: mod/admin.php:945 include/user.php:421
+#: mod/admin.php:1126 include/user.php:423
 #, php-format
 msgid "Registration details for %s"
 msgstr "Registratie details voor %s"
 
-#: mod/admin.php:957
+#: mod/admin.php:1138
 #, php-format
 msgid "%s user blocked/unblocked"
 msgid_plural "%s users blocked/unblocked"
 msgstr[0] "%s gebruiker geblokkeerd/niet geblokkeerd"
 msgstr[1] "%s gebruikers geblokkeerd/niet geblokkeerd"
 
-#: mod/admin.php:964
+#: mod/admin.php:1145
 #, php-format
 msgid "%s user deleted"
 msgid_plural "%s users deleted"
 msgstr[0] "%s gebruiker verwijderd"
 msgstr[1] "%s gebruikers verwijderd"
 
-#: mod/admin.php:1003
+#: mod/admin.php:1192
 #, php-format
 msgid "User '%s' deleted"
 msgstr "Gebruiker '%s' verwijderd"
 
-#: mod/admin.php:1011
+#: mod/admin.php:1200
 #, php-format
 msgid "User '%s' unblocked"
 msgstr "Gebruiker '%s' niet meer geblokkeerd"
 
-#: mod/admin.php:1011
+#: mod/admin.php:1200
 #, php-format
 msgid "User '%s' blocked"
 msgstr "Gebruiker '%s' geblokkeerd"
 
-#: mod/admin.php:1104
+#: mod/admin.php:1291
 msgid "Add User"
 msgstr "Gebruiker toevoegen"
 
-#: mod/admin.php:1105
+#: mod/admin.php:1292
 msgid "select all"
 msgstr "Alles selecteren"
 
-#: mod/admin.php:1106
+#: mod/admin.php:1293
 msgid "User registrations waiting for confirm"
 msgstr "Gebruikersregistraties wachten op een bevestiging"
 
-#: mod/admin.php:1107
+#: mod/admin.php:1294
 msgid "User waiting for permanent deletion"
 msgstr ""
 
-#: mod/admin.php:1108
+#: mod/admin.php:1295
 msgid "Request date"
 msgstr "Registratiedatum"
 
-#: mod/admin.php:1108 mod/admin.php:1120 mod/admin.php:1121 mod/admin.php:1136
+#: mod/admin.php:1295 mod/admin.php:1307 mod/admin.php:1308 mod/admin.php:1323
 #: include/contact_selectors.php:79 include/contact_selectors.php:86
 msgid "Email"
 msgstr "E-mail"
 
-#: mod/admin.php:1109
+#: mod/admin.php:1296
 msgid "No registrations."
 msgstr "Geen registraties."
 
-#: mod/admin.php:1111
+#: mod/admin.php:1298
 msgid "Deny"
 msgstr "Weiger"
 
-#: mod/admin.php:1115
+#: mod/admin.php:1302
 msgid "Site admin"
 msgstr "Sitebeheerder"
 
-#: mod/admin.php:1116
+#: mod/admin.php:1303
 msgid "Account expired"
 msgstr "Account verlopen"
 
-#: mod/admin.php:1119
+#: mod/admin.php:1306
 msgid "New User"
 msgstr "Nieuwe gebruiker"
 
-#: mod/admin.php:1120 mod/admin.php:1121
+#: mod/admin.php:1307 mod/admin.php:1308
 msgid "Register date"
 msgstr "Registratiedatum"
 
-#: mod/admin.php:1120 mod/admin.php:1121
+#: mod/admin.php:1307 mod/admin.php:1308
 msgid "Last login"
 msgstr "Laatste login"
 
-#: mod/admin.php:1120 mod/admin.php:1121
+#: mod/admin.php:1307 mod/admin.php:1308
 msgid "Last item"
 msgstr "Laatste item"
 
-#: mod/admin.php:1120
+#: mod/admin.php:1307
 msgid "Deleted since"
 msgstr "Verwijderd sinds"
 
-#: mod/admin.php:1121 mod/settings.php:41
+#: mod/admin.php:1308 mod/settings.php:41
 msgid "Account"
 msgstr "Account"
 
-#: mod/admin.php:1123
+#: mod/admin.php:1310
 msgid ""
 "Selected users will be deleted!\\n\\nEverything these users had posted on "
 "this site will be permanently deleted!\\n\\nAre you sure?"
 msgstr "Geselecteerde gebruikers zullen verwijderd worden!\\n\\nAlles wat deze gebruikers gepost hebben op deze website zal permanent verwijderd worden!\\n\\nBen je zeker?"
 
-#: mod/admin.php:1124
+#: mod/admin.php:1311
 msgid ""
 "The user {0} will be deleted!\\n\\nEverything this user has posted on this "
 "site will be permanently deleted!\\n\\nAre you sure?"
 msgstr "De gebruiker {0} zal verwijderd worden!\\n\\nAlles wat deze gebruiker gepost heeft op deze website zal permanent verwijderd worden!\\n\\nBen je zeker?"
 
-#: mod/admin.php:1134
+#: mod/admin.php:1321
 msgid "Name of the new user."
 msgstr "Naam van nieuwe gebruiker"
 
-#: mod/admin.php:1135
+#: mod/admin.php:1322
 msgid "Nickname"
 msgstr "Bijnaam"
 
-#: mod/admin.php:1135
+#: mod/admin.php:1322
 msgid "Nickname of the new user."
 msgstr "Bijnaam van nieuwe gebruiker"
 
-#: mod/admin.php:1136
+#: mod/admin.php:1323
 msgid "Email address of the new user."
 msgstr "E-mailadres van nieuwe gebruiker"
 
-#: mod/admin.php:1169
+#: mod/admin.php:1366
 #, php-format
 msgid "Plugin %s disabled."
 msgstr "Plugin %s uitgeschakeld."
 
-#: mod/admin.php:1173
+#: mod/admin.php:1370
 #, php-format
 msgid "Plugin %s enabled."
 msgstr "Plugin %s ingeschakeld."
 
-#: mod/admin.php:1183 mod/admin.php:1407
+#: mod/admin.php:1381 mod/admin.php:1617
 msgid "Disable"
 msgstr "Uitschakelen"
 
-#: mod/admin.php:1185 mod/admin.php:1409
+#: mod/admin.php:1383 mod/admin.php:1619
 msgid "Enable"
 msgstr "Inschakelen"
 
-#: mod/admin.php:1208 mod/admin.php:1453
+#: mod/admin.php:1406 mod/admin.php:1664
 msgid "Toggle"
 msgstr "Schakelaar"
 
-#: mod/admin.php:1216 mod/admin.php:1463
+#: mod/admin.php:1414 mod/admin.php:1673
 msgid "Author: "
 msgstr "Auteur:"
 
-#: mod/admin.php:1217 mod/admin.php:1464
+#: mod/admin.php:1415 mod/admin.php:1674
 msgid "Maintainer: "
 msgstr "Onderhoud:"
 
-#: mod/admin.php:1269
+#: mod/admin.php:1467
 msgid "Reload active plugins"
 msgstr ""
 
-#: mod/admin.php:1367
+#: mod/admin.php:1472
+#, php-format
+msgid ""
+"There are currently no plugins available on your node. You can find the "
+"official plugin repository at %1$s and might find other interesting plugins "
+"in the open plugin registry at %2$s"
+msgstr ""
+
+#: mod/admin.php:1577
 msgid "No themes found."
 msgstr "Geen thema's gevonden."
 
-#: mod/admin.php:1445
+#: mod/admin.php:1655
 msgid "Screenshot"
 msgstr "Schermafdruk"
 
-#: mod/admin.php:1505
+#: mod/admin.php:1715
 msgid "Reload active themes"
 msgstr ""
 
-#: mod/admin.php:1509
+#: mod/admin.php:1720
+#, php-format
+msgid "No themes found on the system. They should be paced in %1$s"
+msgstr ""
+
+#: mod/admin.php:1721
 msgid "[Experimental]"
 msgstr "[Experimenteel]"
 
-#: mod/admin.php:1510
+#: mod/admin.php:1722
 msgid "[Unsupported]"
 msgstr "[Niet ondersteund]"
 
-#: mod/admin.php:1537
+#: mod/admin.php:1746
 msgid "Log settings updated."
 msgstr "Log instellingen gewijzigd"
 
-#: mod/admin.php:1593
+#: mod/admin.php:1783
 msgid "Clear"
 msgstr "Wis"
 
-#: mod/admin.php:1599
+#: mod/admin.php:1788
 msgid "Enable Debugging"
 msgstr ""
 
-#: mod/admin.php:1600
+#: mod/admin.php:1789
 msgid "Log file"
 msgstr "Logbestand"
 
-#: mod/admin.php:1600
+#: mod/admin.php:1789
 msgid ""
 "Must be writable by web server. Relative to your Friendica top-level "
 "directory."
 msgstr "De webserver moet hier kunnen schrijven. Relatief t.o.v. van de hoogste folder binnen uw Friendica-installatie."
 
-#: mod/admin.php:1601
+#: mod/admin.php:1790
 msgid "Log level"
 msgstr "Log niveau"
 
-#: mod/admin.php:1651 include/acl_selectors.php:347
-msgid "Close"
-msgstr "Afsluiten"
+#: mod/admin.php:1793
+msgid "PHP logging"
+msgstr ""
+
+#: mod/admin.php:1794
+msgid ""
+"To enable logging of PHP errors and warnings you can add the following to "
+"the .htconfig.php file of your installation. The filename set in the "
+"'error_log' line is relative to the friendica top-level directory and must "
+"be writeable by the web server. The option '1' for 'log_errors' and "
+"'display_errors' is to enable these options, set to '0' to disable them."
+msgstr ""
 
-#: mod/admin.php:1657
-msgid "FTP Host"
-msgstr "FTP Server"
+#: mod/admin.php:1920 mod/admin.php:1921 mod/settings.php:759
+msgid "Off"
+msgstr "Uit"
 
-#: mod/admin.php:1658
-msgid "FTP Path"
-msgstr "FTP Pad"
+#: mod/admin.php:1920 mod/admin.php:1921 mod/settings.php:759
+msgid "On"
+msgstr "Aan"
 
-#: mod/admin.php:1659
-msgid "FTP User"
-msgstr "FTP Gebruiker"
+#: mod/admin.php:1921
+#, php-format
+msgid "Lock feature %s"
+msgstr ""
 
-#: mod/admin.php:1660
-msgid "FTP Password"
-msgstr "FTP wachtwoord"
+#: mod/admin.php:1929
+msgid "Manage Additional Features"
+msgstr ""
 
-#: mod/network.php:143
+#: mod/network.php:146
 #, php-format
 msgid "Search Results For: %s"
 msgstr ""
 
-#: mod/network.php:195 mod/search.php:25
+#: mod/network.php:191 mod/search.php:25
 msgid "Remove term"
 msgstr "Verwijder zoekterm"
 
-#: mod/network.php:204 mod/search.php:34 include/features.php:43
+#: mod/network.php:200 mod/search.php:34 include/features.php:84
 msgid "Saved Searches"
 msgstr "Opgeslagen zoekopdrachten"
 
-#: mod/network.php:205 include/group.php:279
+#: mod/network.php:201 include/group.php:293
 msgid "add"
 msgstr "toevoegen"
 
-#: mod/network.php:366
+#: mod/network.php:365
 msgid "Commented Order"
 msgstr "Nieuwe reacties bovenaan"
 
-#: mod/network.php:369
+#: mod/network.php:368
 msgid "Sort by Comment Date"
 msgstr "Berichten met nieuwe reacties bovenaan"
 
-#: mod/network.php:374
+#: mod/network.php:373
 msgid "Posted Order"
 msgstr "Nieuwe berichten bovenaan"
 
-#: mod/network.php:377
+#: mod/network.php:376
 msgid "Sort by Post Date"
 msgstr "Nieuwe berichten bovenaan"
 
-#: mod/network.php:388
+#: mod/network.php:387
 msgid "Posts that mention or involve you"
 msgstr "Alleen berichten die jou vermelden of op jou betrekking hebben"
 
-#: mod/network.php:396
+#: mod/network.php:395
 msgid "New"
 msgstr "Nieuw"
 
-#: mod/network.php:399
+#: mod/network.php:398
 msgid "Activity Stream - by date"
 msgstr "Activiteitenstroom - volgens datum"
 
-#: mod/network.php:407
+#: mod/network.php:406
 msgid "Shared Links"
 msgstr "Gedeelde links"
 
-#: mod/network.php:410
+#: mod/network.php:409
 msgid "Interesting Links"
 msgstr "Interessante links"
 
-#: mod/network.php:418
+#: mod/network.php:417
 msgid "Starred"
 msgstr "Met ster"
 
-#: mod/network.php:421
+#: mod/network.php:420
 msgid "Favourite Posts"
 msgstr "Favoriete berichten"
 
-#: mod/network.php:480
+#: mod/network.php:479
 #, php-format
 msgid "Warning: This group contains %s member from an insecure network."
 msgid_plural ""
@@ -3259,49 +3307,31 @@ msgid_plural ""
 msgstr[0] "Waarschuwing: Deze groep bevat %s lid van een onveilig netwerk."
 msgstr[1] "Waarschuwing: Deze groep bevat %s leden van een onveilig netwerk."
 
-#: mod/network.php:483
+#: mod/network.php:482
 msgid "Private messages to this group are at risk of public disclosure."
 msgstr "Privéberichten naar deze groep kunnen openbaar gemaakt worden."
 
-#: mod/network.php:550 mod/content.php:119
+#: mod/network.php:549 mod/content.php:119
 msgid "No such group"
 msgstr "Zo'n groep bestaat niet"
 
-#: mod/network.php:567 mod/content.php:130
-msgid "Group is empty"
-msgstr "De groep is leeg"
-
-#: mod/network.php:578 mod/content.php:135
+#: mod/network.php:580 mod/content.php:135
 #, php-format
 msgid "Group: %s"
 msgstr ""
 
-#: mod/network.php:596
-#, php-format
-msgid "Contact: %s"
-msgstr ""
-
-#: mod/network.php:600
+#: mod/network.php:608
 msgid "Private messages to this person are at risk of public disclosure."
 msgstr "Privéberichten naar deze persoon kunnen openbaar gemaakt worden."
 
-#: mod/network.php:605
+#: mod/network.php:613
 msgid "Invalid contact."
 msgstr "Ongeldig contact."
 
-#: mod/allfriends.php:45
+#: mod/allfriends.php:43
 msgid "No friends to display."
 msgstr "Geen vrienden om te laten zien."
 
-#: mod/allfriends.php:79 mod/common.php:122
-msgid "Forum"
-msgstr ""
-
-#: mod/allfriends.php:92
-#, php-format
-msgid "Friends of %s"
-msgstr "Vrienden van %s"
-
 #: mod/events.php:71 mod/events.php:73
 msgid "Event can not end before it has started."
 msgstr ""
@@ -3338,31 +3368,31 @@ msgstr ""
 msgid "Sat"
 msgstr ""
 
-#: mod/events.php:208 mod/settings.php:936 include/text.php:1267
+#: mod/events.php:208 mod/settings.php:948 include/text.php:1274
 msgid "Sunday"
 msgstr "Zondag"
 
-#: mod/events.php:209 mod/settings.php:936 include/text.php:1267
+#: mod/events.php:209 mod/settings.php:948 include/text.php:1274
 msgid "Monday"
 msgstr "Maandag"
 
-#: mod/events.php:210 include/text.php:1267
+#: mod/events.php:210 include/text.php:1274
 msgid "Tuesday"
 msgstr "Dinsdag"
 
-#: mod/events.php:211 include/text.php:1267
+#: mod/events.php:211 include/text.php:1274
 msgid "Wednesday"
 msgstr "Woensdag"
 
-#: mod/events.php:212 include/text.php:1267
+#: mod/events.php:212 include/text.php:1274
 msgid "Thursday"
 msgstr "Donderdag"
 
-#: mod/events.php:213 include/text.php:1267
+#: mod/events.php:213 include/text.php:1274
 msgid "Friday"
 msgstr "Vrijdag"
 
-#: mod/events.php:214 include/text.php:1267
+#: mod/events.php:214 include/text.php:1274
 msgid "Saturday"
 msgstr "Zaterdag"
 
@@ -3382,7 +3412,7 @@ msgstr ""
 msgid "Apr"
 msgstr ""
 
-#: mod/events.php:219 mod/events.php:231 include/text.php:1271
+#: mod/events.php:219 mod/events.php:231 include/text.php:1278
 msgid "May"
 msgstr "Mei"
 
@@ -3414,47 +3444,47 @@ msgstr ""
 msgid "Dec"
 msgstr ""
 
-#: mod/events.php:227 include/text.php:1271
+#: mod/events.php:227 include/text.php:1278
 msgid "January"
 msgstr "Januari"
 
-#: mod/events.php:228 include/text.php:1271
+#: mod/events.php:228 include/text.php:1278
 msgid "February"
 msgstr "Februari"
 
-#: mod/events.php:229 include/text.php:1271
+#: mod/events.php:229 include/text.php:1278
 msgid "March"
 msgstr "Maart"
 
-#: mod/events.php:230 include/text.php:1271
+#: mod/events.php:230 include/text.php:1278
 msgid "April"
 msgstr "April"
 
-#: mod/events.php:232 include/text.php:1271
+#: mod/events.php:232 include/text.php:1278
 msgid "June"
 msgstr "Juni"
 
-#: mod/events.php:233 include/text.php:1271
+#: mod/events.php:233 include/text.php:1278
 msgid "July"
 msgstr "Juli"
 
-#: mod/events.php:234 include/text.php:1271
+#: mod/events.php:234 include/text.php:1278
 msgid "August"
 msgstr "Augustus"
 
-#: mod/events.php:235 include/text.php:1271
+#: mod/events.php:235 include/text.php:1278
 msgid "September"
 msgstr "September"
 
-#: mod/events.php:236 include/text.php:1271
+#: mod/events.php:236 include/text.php:1278
 msgid "October"
 msgstr "Oktober"
 
-#: mod/events.php:237 include/text.php:1271
+#: mod/events.php:237 include/text.php:1278
 msgid "November"
 msgstr "November"
 
-#: mod/events.php:238 include/text.php:1271
+#: mod/events.php:238 include/text.php:1278
 msgid "December"
 msgstr "December"
 
@@ -3482,11 +3512,11 @@ msgstr "l j F"
 msgid "Edit event"
 msgstr "Gebeurtenis bewerken"
 
-#: mod/events.php:421 include/text.php:1714 include/text.php:1721
+#: mod/events.php:421 include/text.php:1721 include/text.php:1728
 msgid "link to source"
 msgstr "Verwijzing naar bron"
 
-#: mod/events.php:456 include/identity.php:669 include/nav.php:79
+#: mod/events.php:456 include/identity.php:722 include/nav.php:79
 #: include/nav.php:140 view/theme/diabook/theme.php:127
 msgid "Events"
 msgstr "Gebeurtenissen"
@@ -3499,7 +3529,7 @@ msgstr "Maak een nieuwe gebeurtenis"
 msgid "Previous"
 msgstr "Vorige"
 
-#: mod/events.php:459 mod/install.php:212
+#: mod/events.php:459 mod/install.php:220
 msgid "Next"
 msgstr "Volgende"
 
@@ -3543,9 +3573,9 @@ msgstr "Titel:"
 msgid "Share this event"
 msgstr "Deel deze gebeurtenis"
 
-#: mod/events.php:572 mod/content.php:721 mod/editpost.php:144
-#: mod/photos.php:1623 mod/photos.php:1671 mod/photos.php:1759
-#: object/Item.php:719 include/conversation.php:1217
+#: mod/events.php:572 mod/content.php:721 mod/editpost.php:145
+#: mod/photos.php:1631 mod/photos.php:1679 mod/photos.php:1767
+#: object/Item.php:719 include/conversation.php:1216
 msgid "Preview"
 msgstr "Voorvertoning"
 
@@ -3560,7 +3590,7 @@ msgid ""
 "code or the translation of Friendica. Thank you all!"
 msgstr ""
 
-#: mod/content.php:439 mod/content.php:742 mod/photos.php:1714
+#: mod/content.php:439 mod/content.php:742 mod/photos.php:1722
 #: object/Item.php:133 include/conversation.php:634
 msgid "Select"
 msgstr "Kies"
@@ -3589,22 +3619,23 @@ msgstr[0] "%d reactie"
 msgstr[1] "%d reacties"
 
 #: mod/content.php:607 object/Item.php:421 object/Item.php:434
-#: include/text.php:1992
+#: include/text.php:1997
 msgid "comment"
 msgid_plural "comments"
 msgstr[0] "reactie"
 msgstr[1] "reacties"
 
-#: mod/content.php:608 boot.php:773 object/Item.php:422
-#: include/contact_widgets.php:205 include/items.php:5214
+#: mod/content.php:608 boot.php:863 object/Item.php:422
+#: include/contact_widgets.php:242 include/forums.php:110
+#: include/items.php:5184 view/theme/vier/theme.php:264
 msgid "show more"
 msgstr "toon meer"
 
-#: mod/content.php:622 mod/photos.php:1410 object/Item.php:117
+#: mod/content.php:622 mod/photos.php:1418 object/Item.php:117
 msgid "Private Message"
 msgstr "Privébericht"
 
-#: mod/content.php:686 mod/photos.php:1599 object/Item.php:253
+#: mod/content.php:686 mod/photos.php:1607 object/Item.php:253
 msgid "I like this (toggle)"
 msgstr "Vind ik leuk"
 
@@ -3612,7 +3643,7 @@ msgstr "Vind ik leuk"
 msgid "like"
 msgstr "leuk"
 
-#: mod/content.php:687 mod/photos.php:1600 object/Item.php:254
+#: mod/content.php:687 mod/photos.php:1608 object/Item.php:254
 msgid "I don't like this (toggle)"
 msgstr "Vind ik niet leuk"
 
@@ -3628,13 +3659,13 @@ msgstr "Delen"
 msgid "share"
 msgstr "Delen"
 
-#: mod/content.php:709 mod/photos.php:1619 mod/photos.php:1667
-#: mod/photos.php:1755 object/Item.php:707
+#: mod/content.php:709 mod/photos.php:1627 mod/photos.php:1675
+#: mod/photos.php:1763 object/Item.php:707
 msgid "This is you"
 msgstr "Dit ben jij"
 
-#: mod/content.php:711 mod/photos.php:1621 mod/photos.php:1669
-#: mod/photos.php:1757 boot.php:772 object/Item.php:393 object/Item.php:709
+#: mod/content.php:711 mod/photos.php:1629 mod/photos.php:1677
+#: mod/photos.php:1765 boot.php:862 object/Item.php:393 object/Item.php:709
 msgid "Comment"
 msgstr "Reacties"
 
@@ -3670,7 +3701,7 @@ msgstr "Link"
 msgid "Video"
 msgstr "Video"
 
-#: mod/content.php:730 mod/settings.php:710 object/Item.php:122
+#: mod/content.php:730 mod/settings.php:721 object/Item.php:122
 #: object/Item.php:124
 msgid "Edit"
 msgstr "Bewerken"
@@ -3725,105 +3756,105 @@ msgstr "Dit zal je account volledig verwijderen. Dit kan niet hersteld worden al
 msgid "Please enter your password for verification:"
 msgstr "Voer je wachtwoord in voor verificatie:"
 
-#: mod/install.php:120
+#: mod/install.php:128
 msgid "Friendica Communications Server - Setup"
 msgstr ""
 
-#: mod/install.php:126
+#: mod/install.php:134
 msgid "Could not connect to database."
 msgstr "Kon geen toegang krijgen tot de database."
 
-#: mod/install.php:130
+#: mod/install.php:138
 msgid "Could not create table."
 msgstr "Kon tabel niet aanmaken."
 
-#: mod/install.php:136
+#: mod/install.php:144
 msgid "Your Friendica site database has been installed."
 msgstr "De database van je Friendica-website is geïnstalleerd."
 
-#: mod/install.php:141
+#: mod/install.php:149
 msgid ""
 "You may need to import the file \"database.sql\" manually using phpmyadmin "
 "or mysql."
 msgstr "Het kan nodig zijn om het bestand \"database.sql\" manueel te importeren met phpmyadmin of mysql."
 
-#: mod/install.php:142 mod/install.php:211 mod/install.php:569
+#: mod/install.php:150 mod/install.php:219 mod/install.php:577
 msgid "Please see the file \"INSTALL.txt\"."
 msgstr "Zie het bestand \"INSTALL.txt\"."
 
-#: mod/install.php:154
+#: mod/install.php:162
 msgid "Database already in use."
 msgstr ""
 
-#: mod/install.php:208
+#: mod/install.php:216
 msgid "System check"
 msgstr "Systeemcontrole"
 
-#: mod/install.php:213
+#: mod/install.php:221
 msgid "Check again"
 msgstr "Controleer opnieuw"
 
-#: mod/install.php:232
+#: mod/install.php:240
 msgid "Database connection"
 msgstr "Verbinding met database"
 
-#: mod/install.php:233
+#: mod/install.php:241
 msgid ""
 "In order to install Friendica we need to know how to connect to your "
 "database."
 msgstr "Om Friendica te kunnen installeren moet ik weten hoe ik jouw database kan bereiken."
 
-#: mod/install.php:234
+#: mod/install.php:242
 msgid ""
 "Please contact your hosting provider or site administrator if you have "
 "questions about these settings."
 msgstr "Neem contact op met jouw hostingprovider of websitebeheerder, wanneer je vragen hebt over deze instellingen. "
 
-#: mod/install.php:235
+#: mod/install.php:243
 msgid ""
 "The database you specify below should already exist. If it does not, please "
 "create it before continuing."
 msgstr "De database die je hier opgeeft zou al moeten bestaan. Maak anders de database aan voordat je verder gaat."
 
-#: mod/install.php:239
+#: mod/install.php:247
 msgid "Database Server Name"
 msgstr "Servernaam database"
 
-#: mod/install.php:240
+#: mod/install.php:248
 msgid "Database Login Name"
 msgstr "Gebruikersnaam database"
 
-#: mod/install.php:241
+#: mod/install.php:249
 msgid "Database Login Password"
 msgstr "Wachtwoord database"
 
-#: mod/install.php:242
+#: mod/install.php:250
 msgid "Database Name"
 msgstr "Naam database"
 
-#: mod/install.php:243 mod/install.php:282
+#: mod/install.php:251 mod/install.php:290
 msgid "Site administrator email address"
 msgstr "E-mailadres van de websitebeheerder"
 
-#: mod/install.php:243 mod/install.php:282
+#: mod/install.php:251 mod/install.php:290
 msgid ""
 "Your account email address must match this in order to use the web admin "
 "panel."
 msgstr "Het e-mailadres van je account moet hiermee overeenkomen om het administratiepaneel te kunnen gebruiken."
 
-#: mod/install.php:247 mod/install.php:285
+#: mod/install.php:255 mod/install.php:293
 msgid "Please select a default timezone for your website"
 msgstr "Selecteer een standaard tijdzone voor uw website"
 
-#: mod/install.php:272
+#: mod/install.php:280
 msgid "Site settings"
 msgstr "Website-instellingen"
 
-#: mod/install.php:326
+#: mod/install.php:334
 msgid "Could not find a command line version of PHP in the web server PATH."
 msgstr "Kan geen command-line-versie van PHP vinden in het PATH van de webserver."
 
-#: mod/install.php:327
+#: mod/install.php:335
 msgid ""
 "If you don't have a command line version of PHP installed on server, you "
 "will not be able to run background polling via cron. See <a "
@@ -3831,216 +3862,216 @@ msgid ""
 "up-the-poller'>'Setup the poller'</a>"
 msgstr ""
 
-#: mod/install.php:331
+#: mod/install.php:339
 msgid "PHP executable path"
 msgstr "PATH van het PHP commando"
 
-#: mod/install.php:331
+#: mod/install.php:339
 msgid ""
 "Enter full path to php executable. You can leave this blank to continue the "
 "installation."
 msgstr "Vul het volledige path in naar het php programma. Je kunt dit blanco laten om de installatie verder te zetten."
 
-#: mod/install.php:336
+#: mod/install.php:344
 msgid "Command line PHP"
 msgstr "PHP-opdrachtregel"
 
-#: mod/install.php:345
+#: mod/install.php:353
 msgid "PHP executable is not the php cli binary (could be cgi-fgci version)"
 msgstr ""
 
-#: mod/install.php:346
+#: mod/install.php:354
 msgid "Found PHP version: "
 msgstr "Gevonden PHP versie:"
 
-#: mod/install.php:348
+#: mod/install.php:356
 msgid "PHP cli binary"
 msgstr ""
 
-#: mod/install.php:359
+#: mod/install.php:367
 msgid ""
 "The command line version of PHP on your system does not have "
 "\"register_argc_argv\" enabled."
 msgstr "De command-line versie van PHP op jouw systeem heeft \"register_argc_argv\" niet geactiveerd."
 
-#: mod/install.php:360
+#: mod/install.php:368
 msgid "This is required for message delivery to work."
 msgstr "Dit is nodig om het verzenden van berichten mogelijk te maken."
 
-#: mod/install.php:362
+#: mod/install.php:370
 msgid "PHP register_argc_argv"
 msgstr "PHP register_argc_argv"
 
-#: mod/install.php:383
+#: mod/install.php:391
 msgid ""
 "Error: the \"openssl_pkey_new\" function on this system is not able to "
 "generate encryption keys"
 msgstr ""
 
-#: mod/install.php:384
+#: mod/install.php:392
 msgid ""
 "If running under Windows, please see "
 "\"http://www.php.net/manual/en/openssl.installation.php\"."
 msgstr "Zie \"http://www.php.net/manual/en/openssl.installation.php\" wanneer u Friendica onder Windows draait."
 
-#: mod/install.php:386
+#: mod/install.php:394
 msgid "Generate encryption keys"
 msgstr ""
 
-#: mod/install.php:393
+#: mod/install.php:401
 msgid "libCurl PHP module"
 msgstr "libCurl PHP module"
 
-#: mod/install.php:394
+#: mod/install.php:402
 msgid "GD graphics PHP module"
 msgstr "GD graphics PHP module"
 
-#: mod/install.php:395
+#: mod/install.php:403
 msgid "OpenSSL PHP module"
 msgstr "OpenSSL PHP module"
 
-#: mod/install.php:396
+#: mod/install.php:404
 msgid "mysqli PHP module"
 msgstr "mysqli PHP module"
 
-#: mod/install.php:397
+#: mod/install.php:405
 msgid "mb_string PHP module"
 msgstr "mb_string PHP module"
 
-#: mod/install.php:398
+#: mod/install.php:406
 msgid "mcrypt PHP module"
 msgstr ""
 
-#: mod/install.php:403 mod/install.php:405
+#: mod/install.php:411 mod/install.php:413
 msgid "Apache mod_rewrite module"
 msgstr "Apache mod_rewrite module"
 
-#: mod/install.php:403
+#: mod/install.php:411
 msgid ""
 "Error: Apache webserver mod-rewrite module is required but not installed."
 msgstr "Fout: Apache-module mod-rewrite is vereist, maar niet geïnstalleerd."
 
-#: mod/install.php:411
+#: mod/install.php:419
 msgid "Error: libCURL PHP module required but not installed."
 msgstr "Fout: PHP-module libCURL is vereist, maar niet geïnstalleerd."
 
-#: mod/install.php:415
+#: mod/install.php:423
 msgid ""
 "Error: GD graphics PHP module with JPEG support required but not installed."
 msgstr "Fout: PHP-module GD graphics met JPEG support is vereist, maar niet geïnstalleerd."
 
-#: mod/install.php:419
+#: mod/install.php:427
 msgid "Error: openssl PHP module required but not installed."
 msgstr "Fout: PHP-module openssl is vereist, maar niet geïnstalleerd."
 
-#: mod/install.php:423
+#: mod/install.php:431
 msgid "Error: mysqli PHP module required but not installed."
 msgstr "Fout: PHP-module mysqli is vereist, maar niet geïnstalleerd."
 
-#: mod/install.php:427
+#: mod/install.php:435
 msgid "Error: mb_string PHP module required but not installed."
 msgstr "Fout: PHP-module mb_string is vereist, maar niet geïnstalleerd."
 
-#: mod/install.php:431
+#: mod/install.php:439
 msgid "Error: mcrypt PHP module required but not installed."
 msgstr ""
 
-#: mod/install.php:443
+#: mod/install.php:451
 msgid ""
 "Function mcrypt_create_iv() is not defined. This is needed to enable RINO2 "
 "encryption layer."
 msgstr ""
 
-#: mod/install.php:445
+#: mod/install.php:453
 msgid "mcrypt_create_iv() function"
 msgstr ""
 
-#: mod/install.php:461
+#: mod/install.php:469
 msgid ""
 "The web installer needs to be able to create a file called \".htconfig.php\""
 " in the top folder of your web server and it is unable to do so."
 msgstr "Het installatieprogramma moet een bestand \".htconfig.php\" in de bovenste map van je webserver aanmaken, maar kan dit niet doen."
 
-#: mod/install.php:462
+#: mod/install.php:470
 msgid ""
 "This is most often a permission setting, as the web server may not be able "
 "to write files in your folder - even if you can."
 msgstr "Dit is meestal een permissieprobleem, omdat de webserver niet in staat is om in deze map bestanden weg te schrijven - ook al kun je dit zelf wel."
 
-#: mod/install.php:463
+#: mod/install.php:471
 msgid ""
 "At the end of this procedure, we will give you a text to save in a file "
 "named .htconfig.php in your Friendica top folder."
 msgstr "Op het einde van deze procedure zal ik je een tekst geven om te bewaren in een bestand .htconfig.php in je hoogste Friendica map."
 
-#: mod/install.php:464
+#: mod/install.php:472
 msgid ""
 "You can alternatively skip this procedure and perform a manual installation."
 " Please see the file \"INSTALL.txt\" for instructions."
 msgstr "Je kunt ook deze procedure overslaan, en een manuele installatie uitvoeren. Lees het bestand \"INSTALL.txt\" voor instructies."
 
-#: mod/install.php:467
+#: mod/install.php:475
 msgid ".htconfig.php is writable"
 msgstr ".htconfig.php is schrijfbaar"
 
-#: mod/install.php:477
+#: mod/install.php:485
 msgid ""
 "Friendica uses the Smarty3 template engine to render its web views. Smarty3 "
 "compiles templates to PHP to speed up rendering."
 msgstr "Friendica gebruikt het Smarty3 sjabloon systeem om zijn webpagina's weer te geven. Smarty3 compileert sjablonen naar PHP om de weergave te versnellen."
 
-#: mod/install.php:478
+#: mod/install.php:486
 msgid ""
 "In order to store these compiled templates, the web server needs to have "
 "write access to the directory view/smarty3/ under the Friendica top level "
 "folder."
 msgstr "Om deze gecompileerde sjablonen op te slaan moet de webserver schrijftoegang hebben tot de folder view/smarty3, t.o.v. van de hoogste folder van je Friendica-installatie."
 
-#: mod/install.php:479
+#: mod/install.php:487
 msgid ""
 "Please ensure that the user that your web server runs as (e.g. www-data) has"
 " write access to this folder."
 msgstr "Zorg ervoor dat de gebruiker waaronder je webserver runt (bijv. www-data) schrijf-toegang heeft tot deze map."
 
-#: mod/install.php:480
+#: mod/install.php:488
 msgid ""
 "Note: as a security measure, you should give the web server write access to "
 "view/smarty3/ only--not the template files (.tpl) that it contains."
 msgstr "Opmerking: voor een goede beveiliging zou je de webserver alleen schrijf-toegang moeten geven voor de map view/smarty3 -- niet voor de template bestanden (.tpl) die in die map zitten."
 
-#: mod/install.php:483
+#: mod/install.php:491
 msgid "view/smarty3 is writable"
 msgstr "view/smarty3 is schrijfbaar"
 
-#: mod/install.php:499
+#: mod/install.php:507
 msgid ""
 "Url rewrite in .htaccess is not working. Check your server configuration."
 msgstr ""
 
-#: mod/install.php:501
+#: mod/install.php:509
 msgid "Url rewrite is working"
 msgstr ""
 
-#: mod/install.php:518
+#: mod/install.php:526
 msgid "ImageMagick PHP extension is installed"
 msgstr ""
 
-#: mod/install.php:520
+#: mod/install.php:528
 msgid "ImageMagick supports GIF"
 msgstr ""
 
-#: mod/install.php:528
+#: mod/install.php:536
 msgid ""
 "The database configuration file \".htconfig.php\" could not be written. "
 "Please use the enclosed text to create a configuration file in your web "
 "server root."
 msgstr "Het databaseconfiguratiebestand \".htconfig.php\" kon niet worden weggeschreven. Je kunt de bijgevoegde tekst gebruiken om in een configuratiebestand aan te maken in de hoogste map van je webserver."
 
-#: mod/install.php:567
+#: mod/install.php:575
 msgid "<h1>What next</h1>"
 msgstr "<h1>Wat nu</h1>"
 
-#: mod/install.php:568
+#: mod/install.php:576
 msgid ""
 "IMPORTANT: You will need to [manually] setup a scheduled task for the "
 "poller."
@@ -4066,19 +4097,19 @@ msgid ""
 "your site allow private mail from unknown senders."
 msgstr "Als je wilt dat %s antwoordt moet je nakijken dat de privacy-instellingen op jouw website privéberichten van onbekende afzenders toelaat."
 
-#: mod/help.php:31
+#: mod/help.php:41
 msgid "Help:"
 msgstr "Help:"
 
-#: mod/help.php:36 include/nav.php:113 view/theme/vier/theme.php:273
+#: mod/help.php:47 include/nav.php:113 view/theme/vier/theme.php:302
 msgid "Help"
 msgstr "Help"
 
-#: mod/help.php:42 mod/p.php:16 mod/p.php:25 index.php:269
+#: mod/help.php:53 mod/p.php:16 mod/p.php:25 index.php:270
 msgid "Not Found"
 msgstr "Niet gevonden"
 
-#: mod/help.php:45 index.php:272
+#: mod/help.php:56 index.php:273
 msgid "Page not found."
 msgstr "Pagina niet gevonden"
 
@@ -4113,11 +4144,11 @@ msgstr "Uploaden van bestand mislukt."
 msgid "No keywords to match. Please add keywords to your default profile."
 msgstr "Geen sleutelwoorden om te zoeken. Voeg sleutelwoorden toe aan je standaard profiel."
 
-#: mod/match.php:83
+#: mod/match.php:84
 msgid "is interested in:"
 msgstr "Is geïnteresseerd in:"
 
-#: mod/match.php:97
+#: mod/match.php:98
 msgid "Profile Match"
 msgstr "Profielmatch"
 
@@ -4134,727 +4165,715 @@ msgstr "Niet beschikbaar"
 msgid "Community"
 msgstr "Website"
 
-#: mod/community.php:62 mod/community.php:71 mod/search.php:218
+#: mod/community.php:62 mod/community.php:71 mod/search.php:228
 msgid "No results."
 msgstr "Geen resultaten."
 
-#: mod/settings.php:34 mod/photos.php:109
+#: mod/settings.php:34 mod/photos.php:117
 msgid "everybody"
 msgstr "iedereen"
 
-#: mod/settings.php:47
-msgid "Additional features"
-msgstr "Extra functies"
-
-#: mod/settings.php:53
+#: mod/settings.php:58
 msgid "Display"
 msgstr "Weergave"
 
-#: mod/settings.php:60 mod/settings.php:853
+#: mod/settings.php:65 mod/settings.php:864
 msgid "Social Networks"
 msgstr "Sociale netwerken"
 
-#: mod/settings.php:72 include/nav.php:180
+#: mod/settings.php:79 include/nav.php:180
 msgid "Delegations"
 msgstr ""
 
-#: mod/settings.php:78
+#: mod/settings.php:86
 msgid "Connected apps"
 msgstr "Verbonden applicaties"
 
-#: mod/settings.php:84 mod/uexport.php:85
+#: mod/settings.php:93 mod/uexport.php:85
 msgid "Export personal data"
 msgstr "Persoonlijke gegevens exporteren"
 
-#: mod/settings.php:90
+#: mod/settings.php:100
 msgid "Remove account"
 msgstr "Account verwijderen"
 
-#: mod/settings.php:143
+#: mod/settings.php:153
 msgid "Missing some important data!"
 msgstr "Een belangrijk gegeven ontbreekt!"
 
-#: mod/settings.php:256
+#: mod/settings.php:266
 msgid "Failed to connect with email account using the settings provided."
 msgstr "Ik kon geen verbinding maken met het e-mail account met de gegeven instellingen."
 
-#: mod/settings.php:261
+#: mod/settings.php:271
 msgid "Email settings updated."
 msgstr "E-mail instellingen bijgewerkt.."
 
-#: mod/settings.php:276
+#: mod/settings.php:286
 msgid "Features updated"
 msgstr "Functies bijgewerkt"
 
-#: mod/settings.php:341
+#: mod/settings.php:353
 msgid "Relocate message has been send to your contacts"
 msgstr ""
 
-#: mod/settings.php:355 include/user.php:39
+#: mod/settings.php:367 include/user.php:39
 msgid "Passwords do not match. Password unchanged."
 msgstr "Wachtwoorden komen niet overeen. Wachtwoord niet gewijzigd."
 
-#: mod/settings.php:360
+#: mod/settings.php:372
 msgid "Empty passwords are not allowed. Password unchanged."
 msgstr "Lege wachtwoorden zijn niet toegelaten. Wachtwoord niet gewijzigd."
 
-#: mod/settings.php:368
+#: mod/settings.php:380
 msgid "Wrong password."
 msgstr "Verkeerd wachtwoord."
 
-#: mod/settings.php:379
+#: mod/settings.php:391
 msgid "Password changed."
 msgstr "Wachtwoord gewijzigd."
 
-#: mod/settings.php:381
+#: mod/settings.php:393
 msgid "Password update failed. Please try again."
 msgstr "Wachtwoord-)wijziging mislukt. Probeer opnieuw."
 
-#: mod/settings.php:450
+#: mod/settings.php:462
 msgid " Please use a shorter name."
 msgstr "Gebruik een kortere naam."
 
-#: mod/settings.php:452
+#: mod/settings.php:464
 msgid " Name too short."
 msgstr "Naam te kort."
 
-#: mod/settings.php:461
+#: mod/settings.php:473
 msgid "Wrong Password"
 msgstr "Verkeerd wachtwoord"
 
-#: mod/settings.php:466
+#: mod/settings.php:478
 msgid " Not valid email."
 msgstr "Geen geldig e-mailadres."
 
-#: mod/settings.php:472
+#: mod/settings.php:484
 msgid " Cannot change to that email."
 msgstr "Kan niet veranderen naar die e-mail."
 
-#: mod/settings.php:528
+#: mod/settings.php:540
 msgid "Private forum has no privacy permissions. Using default privacy group."
 msgstr "Privéforum/-groep heeft geen privacyrechten. De standaard privacygroep wordt gebruikt."
 
-#: mod/settings.php:532
+#: mod/settings.php:544
 msgid "Private forum has no privacy permissions and no default privacy group."
 msgstr "Privéforum/-groep heeft geen privacyrechten en geen standaard privacygroep."
 
-#: mod/settings.php:571
+#: mod/settings.php:583
 msgid "Settings updated."
 msgstr "Instellingen bijgewerkt."
 
-#: mod/settings.php:647 mod/settings.php:673 mod/settings.php:709
+#: mod/settings.php:658 mod/settings.php:684 mod/settings.php:720
 msgid "Add application"
 msgstr "Toepassing toevoegen"
 
-#: mod/settings.php:651 mod/settings.php:677
+#: mod/settings.php:662 mod/settings.php:688
 msgid "Consumer Key"
 msgstr "Gebruikerssleutel"
 
-#: mod/settings.php:652 mod/settings.php:678
+#: mod/settings.php:663 mod/settings.php:689
 msgid "Consumer Secret"
 msgstr "Gebruikersgeheim"
 
-#: mod/settings.php:653 mod/settings.php:679
+#: mod/settings.php:664 mod/settings.php:690
 msgid "Redirect"
 msgstr "Doorverwijzing"
 
-#: mod/settings.php:654 mod/settings.php:680
+#: mod/settings.php:665 mod/settings.php:691
 msgid "Icon url"
 msgstr "URL pictogram"
 
-#: mod/settings.php:665
+#: mod/settings.php:676
 msgid "You can't edit this application."
 msgstr "Je kunt deze toepassing niet wijzigen."
 
-#: mod/settings.php:708
+#: mod/settings.php:719
 msgid "Connected Apps"
 msgstr "Verbonden applicaties"
 
-#: mod/settings.php:712
+#: mod/settings.php:723
 msgid "Client key starts with"
 msgstr ""
 
-#: mod/settings.php:713
+#: mod/settings.php:724
 msgid "No name"
 msgstr "Geen naam"
 
-#: mod/settings.php:714
+#: mod/settings.php:725
 msgid "Remove authorization"
 msgstr "Verwijder authorisatie"
 
-#: mod/settings.php:726
+#: mod/settings.php:737
 msgid "No Plugin settings configured"
 msgstr ""
 
-#: mod/settings.php:734
+#: mod/settings.php:745
 msgid "Plugin Settings"
 msgstr "Plugin Instellingen"
 
-#: mod/settings.php:748
-msgid "Off"
-msgstr "Uit"
-
-#: mod/settings.php:748
-msgid "On"
-msgstr "Aan"
-
-#: mod/settings.php:756
+#: mod/settings.php:767
 msgid "Additional Features"
 msgstr "Extra functies"
 
-#: mod/settings.php:766 mod/settings.php:770
+#: mod/settings.php:777 mod/settings.php:781
 msgid "General Social Media Settings"
 msgstr ""
 
-#: mod/settings.php:776
+#: mod/settings.php:787
 msgid "Disable intelligent shortening"
 msgstr ""
 
-#: mod/settings.php:778
+#: mod/settings.php:789
 msgid ""
 "Normally the system tries to find the best link to add to shortened posts. "
 "If this option is enabled then every shortened post will always point to the"
 " original friendica post."
 msgstr ""
 
-#: mod/settings.php:784
+#: mod/settings.php:795
 msgid "Automatically follow any GNU Social (OStatus) followers/mentioners"
 msgstr ""
 
-#: mod/settings.php:786
+#: mod/settings.php:797
 msgid ""
 "If you receive a message from an unknown OStatus user, this option decides "
 "what to do. If it is checked, a new contact will be created for every "
 "unknown user."
 msgstr ""
 
-#: mod/settings.php:795
+#: mod/settings.php:806
 msgid "Your legacy GNU Social account"
 msgstr ""
 
-#: mod/settings.php:797
+#: mod/settings.php:808
 msgid ""
 "If you enter your old GNU Social/Statusnet account name here (in the format "
 "user@domain.tld), your contacts will be added automatically. The field will "
 "be emptied when done."
 msgstr ""
 
-#: mod/settings.php:800
+#: mod/settings.php:811
 msgid "Repair OStatus subscriptions"
 msgstr ""
 
-#: mod/settings.php:809 mod/settings.php:810
+#: mod/settings.php:820 mod/settings.php:821
 #, php-format
 msgid "Built-in support for %s connectivity is %s"
 msgstr "Ingebouwde ondersteuning voor connectiviteit met %s is %s"
 
-#: mod/settings.php:809 mod/dfrn_request.php:856
+#: mod/settings.php:820 mod/dfrn_request.php:865
 #: include/contact_selectors.php:80
 msgid "Diaspora"
 msgstr "Diaspora"
 
-#: mod/settings.php:809 mod/settings.php:810
+#: mod/settings.php:820 mod/settings.php:821
 msgid "enabled"
 msgstr "ingeschakeld"
 
-#: mod/settings.php:809 mod/settings.php:810
+#: mod/settings.php:820 mod/settings.php:821
 msgid "disabled"
 msgstr "uitgeschakeld"
 
-#: mod/settings.php:810
+#: mod/settings.php:821
 msgid "GNU Social (OStatus)"
 msgstr ""
 
-#: mod/settings.php:846
+#: mod/settings.php:857
 msgid "Email access is disabled on this site."
 msgstr "E-mailtoegang is op deze website uitgeschakeld."
 
-#: mod/settings.php:858
+#: mod/settings.php:869
 msgid "Email/Mailbox Setup"
 msgstr "E-mail Instellen"
 
-#: mod/settings.php:859
+#: mod/settings.php:870
 msgid ""
 "If you wish to communicate with email contacts using this service "
 "(optional), please specify how to connect to your mailbox."
 msgstr "Als je wilt communiceren met e-mail contacten via deze dienst (optioneel), moet je hier opgeven hoe ik jouw mailbox kan bereiken."
 
-#: mod/settings.php:860
+#: mod/settings.php:871
 msgid "Last successful email check:"
 msgstr "Laatste succesvolle e-mail controle:"
 
-#: mod/settings.php:862
+#: mod/settings.php:873
 msgid "IMAP server name:"
 msgstr "IMAP server naam:"
 
-#: mod/settings.php:863
+#: mod/settings.php:874
 msgid "IMAP port:"
 msgstr "IMAP poort:"
 
-#: mod/settings.php:864
+#: mod/settings.php:875
 msgid "Security:"
 msgstr "Beveiliging:"
 
-#: mod/settings.php:864 mod/settings.php:869
+#: mod/settings.php:875 mod/settings.php:880
 msgid "None"
 msgstr "Geen"
 
-#: mod/settings.php:865
+#: mod/settings.php:876
 msgid "Email login name:"
 msgstr "E-mail login naam:"
 
-#: mod/settings.php:866
+#: mod/settings.php:877
 msgid "Email password:"
 msgstr "E-mail wachtwoord:"
 
-#: mod/settings.php:867
+#: mod/settings.php:878
 msgid "Reply-to address:"
 msgstr "Antwoord adres:"
 
-#: mod/settings.php:868
+#: mod/settings.php:879
 msgid "Send public posts to all email contacts:"
 msgstr "Openbare posts naar alle e-mail contacten versturen:"
 
-#: mod/settings.php:869
+#: mod/settings.php:880
 msgid "Action after import:"
 msgstr "Actie na importeren:"
 
-#: mod/settings.php:869
+#: mod/settings.php:880
 msgid "Mark as seen"
 msgstr "Als 'gelezen' markeren"
 
-#: mod/settings.php:869
+#: mod/settings.php:880
 msgid "Move to folder"
 msgstr "Naar map verplaatsen"
 
-#: mod/settings.php:870
+#: mod/settings.php:881
 msgid "Move to folder:"
 msgstr "Verplaatsen naar map:"
 
-#: mod/settings.php:955
+#: mod/settings.php:967
 msgid "Display Settings"
 msgstr "Scherminstellingen"
 
-#: mod/settings.php:961 mod/settings.php:979
+#: mod/settings.php:973 mod/settings.php:991
 msgid "Display Theme:"
 msgstr "Schermthema:"
 
-#: mod/settings.php:962
+#: mod/settings.php:974
 msgid "Mobile Theme:"
 msgstr "Mobiel thema:"
 
-#: mod/settings.php:963
+#: mod/settings.php:975
 msgid "Update browser every xx seconds"
 msgstr "Browser elke xx seconden verversen"
 
-#: mod/settings.php:963
-msgid "Minimum of 10 seconds, no maximum"
-msgstr "Minimum 10 seconden, geen maximum"
+#: mod/settings.php:975
+msgid "Minimum of 10 seconds. Enter -1 to disable it."
+msgstr ""
 
-#: mod/settings.php:964
+#: mod/settings.php:976
 msgid "Number of items to display per page:"
 msgstr "Aantal items te tonen per pagina:"
 
-#: mod/settings.php:964 mod/settings.php:965
+#: mod/settings.php:976 mod/settings.php:977
 msgid "Maximum of 100 items"
 msgstr "Maximum 100 items"
 
-#: mod/settings.php:965
+#: mod/settings.php:977
 msgid "Number of items to display per page when viewed from mobile device:"
 msgstr "Aantal items per pagina als je een mobiel toestel gebruikt:"
 
-#: mod/settings.php:966
+#: mod/settings.php:978
 msgid "Don't show emoticons"
 msgstr "Emoticons niet tonen"
 
-#: mod/settings.php:967
+#: mod/settings.php:979
 msgid "Calendar"
 msgstr ""
 
-#: mod/settings.php:968
+#: mod/settings.php:980
 msgid "Beginning of week:"
 msgstr ""
 
-#: mod/settings.php:969
+#: mod/settings.php:981
 msgid "Don't show notices"
 msgstr ""
 
-#: mod/settings.php:970
+#: mod/settings.php:982
 msgid "Infinite scroll"
 msgstr "Oneindig scrollen"
 
-#: mod/settings.php:971
+#: mod/settings.php:983
 msgid "Automatic updates only at the top of the network page"
 msgstr ""
 
-#: mod/settings.php:973 view/theme/cleanzero/config.php:82
+#: mod/settings.php:985 view/theme/cleanzero/config.php:82
 #: view/theme/dispy/config.php:72 view/theme/quattro/config.php:66
 #: view/theme/diabook/config.php:150 view/theme/vier/config.php:109
 #: view/theme/duepuntozero/config.php:61
 msgid "Theme settings"
 msgstr "Thema-instellingen"
 
-#: mod/settings.php:1050
+#: mod/settings.php:1062
 msgid "User Types"
 msgstr "Gebruikerstypes"
 
-#: mod/settings.php:1051
+#: mod/settings.php:1063
 msgid "Community Types"
 msgstr "Forum/groepstypes"
 
-#: mod/settings.php:1052
+#: mod/settings.php:1064
 msgid "Normal Account Page"
 msgstr "Normale accountpagina"
 
-#: mod/settings.php:1053
+#: mod/settings.php:1065
 msgid "This account is a normal personal profile"
 msgstr "Deze account is een normaal persoonlijk profiel"
 
-#: mod/settings.php:1056
+#: mod/settings.php:1068
 msgid "Soapbox Page"
 msgstr "Zeepkist-pagina"
 
-#: mod/settings.php:1057
+#: mod/settings.php:1069
 msgid "Automatically approve all connection/friend requests as read-only fans"
 msgstr "Keur automatisch alle vriendschaps-/connectieverzoeken goed als fans met alleen recht tot lezen."
 
-#: mod/settings.php:1060
+#: mod/settings.php:1072
 msgid "Community Forum/Celebrity Account"
 msgstr "Forum/groeps- of beroemdheid-account"
 
-#: mod/settings.php:1061
+#: mod/settings.php:1073
 msgid ""
 "Automatically approve all connection/friend requests as read-write fans"
 msgstr "Keur automatisch alle vriendschaps-/connectieverzoeken goed als fans met recht tot lezen en schrijven."
 
-#: mod/settings.php:1064
+#: mod/settings.php:1076
 msgid "Automatic Friend Page"
 msgstr "Automatisch Vriendschapspagina"
 
-#: mod/settings.php:1065
+#: mod/settings.php:1077
 msgid "Automatically approve all connection/friend requests as friends"
 msgstr "Keur automatisch alle vriendschaps-/connectieverzoeken goed als vrienden."
 
-#: mod/settings.php:1068
+#: mod/settings.php:1080
 msgid "Private Forum [Experimental]"
 msgstr "Privé-forum [experimenteel]"
 
-#: mod/settings.php:1069
+#: mod/settings.php:1081
 msgid "Private forum - approved members only"
 msgstr "Privé-forum - enkel voor goedgekeurde leden"
 
-#: mod/settings.php:1081
+#: mod/settings.php:1093
 msgid "OpenID:"
 msgstr "OpenID:"
 
-#: mod/settings.php:1081
+#: mod/settings.php:1093
 msgid "(Optional) Allow this OpenID to login to this account."
 msgstr "(Optioneel) Laat dit OpenID toe om in te loggen op deze account."
 
-#: mod/settings.php:1091
+#: mod/settings.php:1103
 msgid "Publish your default profile in your local site directory?"
 msgstr "Je standaardprofiel in je lokale gids publiceren?"
 
-#: mod/settings.php:1097
+#: mod/settings.php:1109
 msgid "Publish your default profile in the global social directory?"
 msgstr "Je standaardprofiel in de globale sociale gids publiceren?"
 
-#: mod/settings.php:1105
+#: mod/settings.php:1117
 msgid "Hide your contact/friend list from viewers of your default profile?"
 msgstr "Je vrienden/contacten verbergen voor bezoekers van je standaard profiel?"
 
-#: mod/settings.php:1109 include/acl_selectors.php:330
+#: mod/settings.php:1121 include/acl_selectors.php:331
 msgid "Hide your profile details from unknown viewers?"
 msgstr "Je profieldetails verbergen voor onbekende bezoekers?"
 
-#: mod/settings.php:1109
+#: mod/settings.php:1121
 msgid ""
 "If enabled, posting public messages to Diaspora and other networks isn't "
 "possible."
 msgstr ""
 
-#: mod/settings.php:1114
+#: mod/settings.php:1126
 msgid "Allow friends to post to your profile page?"
 msgstr "Vrienden toestaan om op jou profielpagina te posten?"
 
-#: mod/settings.php:1120
+#: mod/settings.php:1132
 msgid "Allow friends to tag your posts?"
 msgstr "Sta vrienden toe om jouw berichten te labelen?"
 
-#: mod/settings.php:1126
+#: mod/settings.php:1138
 msgid "Allow us to suggest you as a potential friend to new members?"
 msgstr "Sta je mij toe om jou als mogelijke vriend  voor te stellen aan nieuwe leden?"
 
-#: mod/settings.php:1132
+#: mod/settings.php:1144
 msgid "Permit unknown people to send you private mail?"
 msgstr "Mogen onbekende personen jou privé berichten sturen?"
 
-#: mod/settings.php:1140
+#: mod/settings.php:1152
 msgid "Profile is <strong>not published</strong>."
 msgstr "Profiel is <strong>niet gepubliceerd</strong>."
 
-#: mod/settings.php:1148
+#: mod/settings.php:1160
 #, php-format
 msgid "Your Identity Address is <strong>'%s'</strong> or '%s'."
 msgstr ""
 
-#: mod/settings.php:1155
+#: mod/settings.php:1167
 msgid "Automatically expire posts after this many days:"
 msgstr "Laat berichten automatisch vervallen na zo veel dagen:"
 
-#: mod/settings.php:1155
+#: mod/settings.php:1167
 msgid "If empty, posts will not expire. Expired posts will be deleted"
 msgstr "Berichten zullen niet vervallen indien leeg. Vervallen berichten zullen worden verwijderd."
 
-#: mod/settings.php:1156
+#: mod/settings.php:1168
 msgid "Advanced expiration settings"
 msgstr "Geavanceerde instellingen voor vervallen"
 
-#: mod/settings.php:1157
+#: mod/settings.php:1169
 msgid "Advanced Expiration"
 msgstr "Geavanceerd Verval:"
 
-#: mod/settings.php:1158
+#: mod/settings.php:1170
 msgid "Expire posts:"
 msgstr "Laat berichten vervallen:"
 
-#: mod/settings.php:1159
+#: mod/settings.php:1171
 msgid "Expire personal notes:"
 msgstr "Laat persoonlijke aantekeningen verlopen:"
 
-#: mod/settings.php:1160
+#: mod/settings.php:1172
 msgid "Expire starred posts:"
 msgstr "Laat berichten met ster verlopen"
 
-#: mod/settings.php:1161
+#: mod/settings.php:1173
 msgid "Expire photos:"
 msgstr "Laat foto's vervallen:"
 
-#: mod/settings.php:1162
+#: mod/settings.php:1174
 msgid "Only expire posts by others:"
 msgstr "Laat alleen berichten door anderen vervallen:"
 
-#: mod/settings.php:1190
+#: mod/settings.php:1202
 msgid "Account Settings"
 msgstr "Account Instellingen"
 
-#: mod/settings.php:1198
+#: mod/settings.php:1210
 msgid "Password Settings"
 msgstr "Wachtwoord Instellingen"
 
-#: mod/settings.php:1199 mod/register.php:271
+#: mod/settings.php:1211 mod/register.php:274
 msgid "New Password:"
 msgstr "Nieuw Wachtwoord:"
 
-#: mod/settings.php:1200 mod/register.php:272
+#: mod/settings.php:1212 mod/register.php:275
 msgid "Confirm:"
 msgstr "Bevestig:"
 
-#: mod/settings.php:1200
+#: mod/settings.php:1212
 msgid "Leave password fields blank unless changing"
 msgstr "Laat de wachtwoord-velden leeg, tenzij je het wilt veranderen"
 
-#: mod/settings.php:1201
+#: mod/settings.php:1213
 msgid "Current Password:"
 msgstr "Huidig wachtwoord:"
 
-#: mod/settings.php:1201 mod/settings.php:1202
+#: mod/settings.php:1213 mod/settings.php:1214
 msgid "Your current password to confirm the changes"
 msgstr "Je huidig wachtwoord om de wijzigingen te bevestigen"
 
-#: mod/settings.php:1202
+#: mod/settings.php:1214
 msgid "Password:"
 msgstr "Wachtwoord:"
 
-#: mod/settings.php:1206
+#: mod/settings.php:1218
 msgid "Basic Settings"
 msgstr "Basis Instellingen"
 
-#: mod/settings.php:1207 include/identity.php:539
+#: mod/settings.php:1219 include/identity.php:588
 msgid "Full Name:"
 msgstr "Volledige Naam:"
 
-#: mod/settings.php:1208
+#: mod/settings.php:1220
 msgid "Email Address:"
 msgstr "E-mailadres:"
 
-#: mod/settings.php:1209
+#: mod/settings.php:1221
 msgid "Your Timezone:"
 msgstr "Je Tijdzone:"
 
-#: mod/settings.php:1210
+#: mod/settings.php:1222
 msgid "Your Language:"
 msgstr ""
 
-#: mod/settings.php:1210
+#: mod/settings.php:1222
 msgid ""
 "Set the language we use to show you friendica interface and to send you "
 "emails"
 msgstr ""
 
-#: mod/settings.php:1211
+#: mod/settings.php:1223
 msgid "Default Post Location:"
 msgstr "Standaard locatie:"
 
-#: mod/settings.php:1212
+#: mod/settings.php:1224
 msgid "Use Browser Location:"
 msgstr "Gebruik Webbrowser Locatie:"
 
-#: mod/settings.php:1215
+#: mod/settings.php:1227
 msgid "Security and Privacy Settings"
 msgstr "Instellingen voor Beveiliging en Privacy"
 
-#: mod/settings.php:1217
+#: mod/settings.php:1229
 msgid "Maximum Friend Requests/Day:"
 msgstr "Maximum aantal vriendschapsverzoeken per dag:"
 
-#: mod/settings.php:1217 mod/settings.php:1247
+#: mod/settings.php:1229 mod/settings.php:1259
 msgid "(to prevent spam abuse)"
 msgstr "(om spam misbruik te voorkomen)"
 
-#: mod/settings.php:1218
+#: mod/settings.php:1230
 msgid "Default Post Permissions"
 msgstr "Standaard rechten voor nieuwe berichten"
 
-#: mod/settings.php:1219
+#: mod/settings.php:1231
 msgid "(click to open/close)"
 msgstr "(klik om te openen/sluiten)"
 
-#: mod/settings.php:1228 mod/photos.php:1191 mod/photos.php:1576
+#: mod/settings.php:1240 mod/photos.php:1199 mod/photos.php:1584
 msgid "Show to Groups"
 msgstr "Tonen aan groepen"
 
-#: mod/settings.php:1229 mod/photos.php:1192 mod/photos.php:1577
+#: mod/settings.php:1241 mod/photos.php:1200 mod/photos.php:1585
 msgid "Show to Contacts"
 msgstr "Tonen aan contacten"
 
-#: mod/settings.php:1230
+#: mod/settings.php:1242
 msgid "Default Private Post"
 msgstr "Standaard Privé Post"
 
-#: mod/settings.php:1231
+#: mod/settings.php:1243
 msgid "Default Public Post"
 msgstr "Standaard Publieke Post"
 
-#: mod/settings.php:1235
+#: mod/settings.php:1247
 msgid "Default Permissions for New Posts"
 msgstr "Standaard rechten voor nieuwe berichten"
 
-#: mod/settings.php:1247
+#: mod/settings.php:1259
 msgid "Maximum private messages per day from unknown people:"
 msgstr "Maximum aantal privé-berichten per dag van onbekende personen:"
 
-#: mod/settings.php:1250
+#: mod/settings.php:1262
 msgid "Notification Settings"
 msgstr "Notificatie Instellingen"
 
-#: mod/settings.php:1251
+#: mod/settings.php:1263
 msgid "By default post a status message when:"
 msgstr "Post automatisch een bericht op je tijdlijn wanneer:"
 
-#: mod/settings.php:1252
+#: mod/settings.php:1264
 msgid "accepting a friend request"
 msgstr "Een vriendschapsverzoek accepteren"
 
-#: mod/settings.php:1253
+#: mod/settings.php:1265
 msgid "joining a forum/community"
 msgstr "Lid worden van een groep/forum"
 
-#: mod/settings.php:1254
+#: mod/settings.php:1266
 msgid "making an <em>interesting</em> profile change"
 msgstr "Een <em>interessante</em> verandering aan je profiel"
 
-#: mod/settings.php:1255
+#: mod/settings.php:1267
 msgid "Send a notification email when:"
 msgstr "Stuur een notificatie e-mail wanneer:"
 
-#: mod/settings.php:1256
+#: mod/settings.php:1268
 msgid "You receive an introduction"
 msgstr "Je ontvangt een vriendschaps- of connectieverzoek"
 
-#: mod/settings.php:1257
+#: mod/settings.php:1269
 msgid "Your introductions are confirmed"
 msgstr "Jouw vriendschaps- of connectieverzoeken zijn bevestigd"
 
-#: mod/settings.php:1258
+#: mod/settings.php:1270
 msgid "Someone writes on your profile wall"
 msgstr "Iemand iets op je tijdlijn schrijft"
 
-#: mod/settings.php:1259
+#: mod/settings.php:1271
 msgid "Someone writes a followup comment"
 msgstr "Iemand een reactie schrijft"
 
-#: mod/settings.php:1260
+#: mod/settings.php:1272
 msgid "You receive a private message"
 msgstr "Je een privé-bericht ontvangt"
 
-#: mod/settings.php:1261
+#: mod/settings.php:1273
 msgid "You receive a friend suggestion"
 msgstr "Je een suggestie voor een vriendschap ontvangt"
 
-#: mod/settings.php:1262
+#: mod/settings.php:1274
 msgid "You are tagged in a post"
 msgstr "Je expliciet in een bericht bent genoemd"
 
-#: mod/settings.php:1263
+#: mod/settings.php:1275
 msgid "You are poked/prodded/etc. in a post"
 msgstr "Je in een bericht bent aangestoten/gepord/etc."
 
-#: mod/settings.php:1265
+#: mod/settings.php:1277
 msgid "Activate desktop notifications"
 msgstr ""
 
-#: mod/settings.php:1265
+#: mod/settings.php:1277
 msgid "Show desktop popup on new notifications"
 msgstr ""
 
-#: mod/settings.php:1267
+#: mod/settings.php:1279
 msgid "Text-only notification emails"
 msgstr ""
 
-#: mod/settings.php:1269
+#: mod/settings.php:1281
 msgid "Send text only notification emails, without the html part"
 msgstr ""
 
-#: mod/settings.php:1271
+#: mod/settings.php:1283
 msgid "Advanced Account/Page Type Settings"
 msgstr ""
 
-#: mod/settings.php:1272
+#: mod/settings.php:1284
 msgid "Change the behaviour of this account for special situations"
 msgstr ""
 
-#: mod/settings.php:1275
+#: mod/settings.php:1287
 msgid "Relocate"
 msgstr ""
 
-#: mod/settings.php:1276
+#: mod/settings.php:1288
 msgid ""
 "If you have moved this profile from another server, and some of your "
 "contacts don't receive your updates, try pushing this button."
 msgstr ""
 
-#: mod/settings.php:1277
+#: mod/settings.php:1289
 msgid "Resend relocate message to contacts"
 msgstr ""
 
-#: mod/dfrn_request.php:95
+#: mod/dfrn_request.php:96
 msgid "This introduction has already been accepted."
 msgstr "Verzoek is al goedgekeurd"
 
-#: mod/dfrn_request.php:120 mod/dfrn_request.php:518
+#: mod/dfrn_request.php:119 mod/dfrn_request.php:516
 msgid "Profile location is not valid or does not contain profile information."
 msgstr "Profiel is ongeldig of bevat geen informatie"
 
-#: mod/dfrn_request.php:125 mod/dfrn_request.php:523
+#: mod/dfrn_request.php:124 mod/dfrn_request.php:521
 msgid "Warning: profile location has no identifiable owner name."
 msgstr "Waarschuwing: de profiellocatie heeft geen identificeerbare eigenaar."
 
-#: mod/dfrn_request.php:127 mod/dfrn_request.php:525
+#: mod/dfrn_request.php:126 mod/dfrn_request.php:523
 msgid "Warning: profile location has no profile photo."
 msgstr "Waarschuwing: Profieladres heeft geen profielfoto."
 
-#: mod/dfrn_request.php:130 mod/dfrn_request.php:528
+#: mod/dfrn_request.php:129 mod/dfrn_request.php:526
 #, php-format
 msgid "%d required parameter was not found at the given location"
 msgid_plural "%d required parameters were not found at the given location"
@@ -4898,66 +4917,68 @@ msgstr "Geen geldig e-mailadres"
 msgid "This account has not been configured for email. Request failed."
 msgstr "Aanvraag mislukt. Dit account is niet geconfigureerd voor e-mail."
 
-#: mod/dfrn_request.php:463
-msgid "Unable to resolve your name at the provided location."
-msgstr "Ik kan jouw naam op het opgegeven adres niet vinden."
-
-#: mod/dfrn_request.php:476
+#: mod/dfrn_request.php:474
 msgid "You have already introduced yourself here."
 msgstr "Je hebt jezelf hier al voorgesteld."
 
-#: mod/dfrn_request.php:480
+#: mod/dfrn_request.php:478
 #, php-format
 msgid "Apparently you are already friends with %s."
 msgstr "Blijkbaar bent u al bevriend met %s."
 
-#: mod/dfrn_request.php:501
+#: mod/dfrn_request.php:499
 msgid "Invalid profile URL."
 msgstr "Ongeldig profiel adres."
 
-#: mod/dfrn_request.php:507 include/follow.php:72
+#: mod/dfrn_request.php:505 include/follow.php:72
 msgid "Disallowed profile URL."
 msgstr "Niet toegelaten profiel adres."
 
-#: mod/dfrn_request.php:597
+#: mod/dfrn_request.php:596
 msgid "Your introduction has been sent."
 msgstr "Je verzoek is verzonden."
 
-#: mod/dfrn_request.php:650
+#: mod/dfrn_request.php:636
+msgid ""
+"Remote subscription can't be done for your network. Please subscribe "
+"directly on your system."
+msgstr ""
+
+#: mod/dfrn_request.php:659
 msgid "Please login to confirm introduction."
 msgstr "Log in om je verzoek te bevestigen."
 
-#: mod/dfrn_request.php:660
+#: mod/dfrn_request.php:669
 msgid ""
 "Incorrect identity currently logged in. Please login to "
 "<strong>this</strong> profile."
 msgstr "Je huidige identiteit is niet de juiste. Log met <strong>dit</strong> profiel in."
 
-#: mod/dfrn_request.php:674 mod/dfrn_request.php:691
+#: mod/dfrn_request.php:683 mod/dfrn_request.php:700
 msgid "Confirm"
 msgstr "Bevestig"
 
-#: mod/dfrn_request.php:686
+#: mod/dfrn_request.php:695
 msgid "Hide this contact"
 msgstr "Verberg dit contact"
 
-#: mod/dfrn_request.php:689
+#: mod/dfrn_request.php:698
 #, php-format
 msgid "Welcome home %s."
 msgstr "Welkom terug %s."
 
-#: mod/dfrn_request.php:690
+#: mod/dfrn_request.php:699
 #, php-format
 msgid "Please confirm your introduction/connection request to %s."
 msgstr "Bevestig je vriendschaps-/connectieverzoek voor %s."
 
-#: mod/dfrn_request.php:819
+#: mod/dfrn_request.php:828
 msgid ""
 "Please enter your 'Identity Address' from one of the following supported "
 "communications networks:"
 msgstr "Vul hier uw 'Identiteitsadres' in van een van de volgende ondersteunde communicatienetwerken:"
 
-#: mod/dfrn_request.php:840
+#: mod/dfrn_request.php:849
 #, php-format
 msgid ""
 "If you are not yet a member of the free social web, <a "
@@ -4965,25 +4986,25 @@ msgid ""
 "join us today</a>."
 msgstr ""
 
-#: mod/dfrn_request.php:845
+#: mod/dfrn_request.php:854
 msgid "Friend/Connection Request"
 msgstr "Vriendschaps-/connectieverzoek"
 
-#: mod/dfrn_request.php:846
+#: mod/dfrn_request.php:855
 msgid ""
 "Examples: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, "
 "testuser@identi.ca"
 msgstr "Voorbeelden: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, testuser@identi.ca"
 
-#: mod/dfrn_request.php:854 include/contact_selectors.php:76
+#: mod/dfrn_request.php:863 include/contact_selectors.php:76
 msgid "Friendica"
 msgstr "Friendica"
 
-#: mod/dfrn_request.php:855
+#: mod/dfrn_request.php:864
 msgid "StatusNet/Federated Social Web"
 msgstr "StatusNet/Gefedereerde Sociale Web"
 
-#: mod/dfrn_request.php:857
+#: mod/dfrn_request.php:866
 #, php-format
 msgid ""
 " - please do not use this form.  Instead, enter %s into your Diaspora search"
@@ -5002,80 +5023,84 @@ msgid ""
 "password: %s<br><br>You can change your password after login."
 msgstr ""
 
-#: mod/register.php:107
+#: mod/register.php:104
+msgid "Registration successful."
+msgstr ""
+
+#: mod/register.php:110
 msgid "Your registration can not be processed."
 msgstr "Je registratie kan niet verwerkt worden."
 
-#: mod/register.php:150
+#: mod/register.php:153
 msgid "Your registration is pending approval by the site owner."
 msgstr "Jouw registratie wacht op goedkeuring van de beheerder."
 
-#: mod/register.php:188 mod/uimport.php:50
+#: mod/register.php:191 mod/uimport.php:50
 msgid ""
 "This site has exceeded the number of allowed daily account registrations. "
 "Please try again tomorrow."
 msgstr "Deze website heeft het toegelaten dagelijkse aantal registraties overschreden. Probeer morgen opnieuw."
 
-#: mod/register.php:216
+#: mod/register.php:219
 msgid ""
 "You may (optionally) fill in this form via OpenID by supplying your OpenID "
 "and clicking 'Register'."
 msgstr "Je kunt (optioneel) dit formulier invullen via OpenID door je OpenID in te geven en op 'Registreren' te klikken."
 
-#: mod/register.php:217
+#: mod/register.php:220
 msgid ""
 "If you are not familiar with OpenID, please leave that field blank and fill "
 "in the rest of the items."
 msgstr "Laat dit veld leeg als je niet vertrouwd bent met OpenID, en vul de rest van de items in."
 
-#: mod/register.php:218
+#: mod/register.php:221
 msgid "Your OpenID (optional): "
 msgstr "Je OpenID (optioneel):"
 
-#: mod/register.php:232
+#: mod/register.php:235
 msgid "Include your profile in member directory?"
 msgstr "Je profiel in de ledengids opnemen?"
 
-#: mod/register.php:256
+#: mod/register.php:259
 msgid "Membership on this site is by invitation only."
 msgstr "Lidmaatschap van deze website is uitsluitend op uitnodiging."
 
-#: mod/register.php:257
+#: mod/register.php:260
 msgid "Your invitation ID: "
 msgstr "Je uitnodigingsid:"
 
-#: mod/register.php:268
+#: mod/register.php:271
 msgid "Your Full Name (e.g. Joe Smith, real or real-looking): "
 msgstr ""
 
-#: mod/register.php:269
+#: mod/register.php:272
 msgid "Your Email Address: "
 msgstr "Je email adres:"
 
-#: mod/register.php:271
+#: mod/register.php:274
 msgid "Leave empty for an auto generated password."
 msgstr ""
 
-#: mod/register.php:273
+#: mod/register.php:276
 msgid ""
 "Choose a profile nickname. This must begin with a text character. Your "
 "profile address on this site will then be "
 "'<strong>nickname@$sitename</strong>'."
 msgstr "Kies een bijnaam voor je profiel. Deze moet met een letter beginnen. Je profieladres op deze website zal dan '<strong>bijnaam@$sitename</strong>' zijn."
 
-#: mod/register.php:274
+#: mod/register.php:277
 msgid "Choose a nickname: "
 msgstr "Kies een bijnaam:"
 
-#: mod/register.php:277 boot.php:1256 include/nav.php:108
+#: mod/register.php:280 boot.php:1379 include/nav.php:108
 msgid "Register"
 msgstr "Registreer"
 
-#: mod/register.php:283 mod/uimport.php:64
+#: mod/register.php:286 mod/uimport.php:64
 msgid "Import"
 msgstr "Importeren"
 
-#: mod/register.php:284
+#: mod/register.php:287
 msgid "Import your profile to this friendica instance"
 msgstr ""
 
@@ -5087,62 +5112,54 @@ msgstr "Systeem onbeschikbaar wegens onderhoud"
 msgid "Only logged in users are permitted to perform a search."
 msgstr ""
 
-#: mod/search.php:115
+#: mod/search.php:124
 msgid "Too Many Requests"
 msgstr ""
 
-#: mod/search.php:116
+#: mod/search.php:125
 msgid "Only one search per minute is permitted for not logged in users."
 msgstr ""
 
-#: mod/search.php:126 include/text.php:996 include/nav.php:118
+#: mod/search.php:136 include/text.php:1003 include/nav.php:118
 msgid "Search"
 msgstr "Zoeken"
 
-#: mod/search.php:224
+#: mod/search.php:234
 #, php-format
 msgid "Items tagged with: %s"
 msgstr ""
 
-#: mod/search.php:226
+#: mod/search.php:236
 #, php-format
 msgid "Search results for: %s"
 msgstr ""
 
-#: mod/directory.php:116 mod/profiles.php:760
-msgid "Age: "
-msgstr "Leeftijd:"
-
-#: mod/directory.php:119
-msgid "Gender: "
-msgstr "Geslacht:"
-
-#: mod/directory.php:143 include/identity.php:273 include/identity.php:561
+#: mod/directory.php:149 include/identity.php:313 include/identity.php:610
 msgid "Status:"
 msgstr "Tijdlijn:"
 
-#: mod/directory.php:145 include/identity.php:275 include/identity.php:572
+#: mod/directory.php:151 include/identity.php:315 include/identity.php:621
 msgid "Homepage:"
 msgstr "Website:"
 
-#: mod/directory.php:195 view/theme/diabook/theme.php:525
-#: view/theme/vier/theme.php:191
+#: mod/directory.php:203 view/theme/diabook/theme.php:525
+#: view/theme/vier/theme.php:205
 msgid "Global Directory"
 msgstr "Globale gids"
 
-#: mod/directory.php:197
+#: mod/directory.php:205
 msgid "Find on this site"
 msgstr "Op deze website zoeken"
 
-#: mod/directory.php:199
+#: mod/directory.php:207
 msgid "Finding:"
 msgstr ""
 
-#: mod/directory.php:201
+#: mod/directory.php:209
 msgid "Site Directory"
 msgstr "Websitegids"
 
-#: mod/directory.php:208
+#: mod/directory.php:216
 msgid "No entries (some entries may be hidden)."
 msgstr "Geen gegevens (sommige gegevens kunnen verborgen zijn)."
 
@@ -5181,14 +5198,10 @@ msgstr "Toevoegen"
 msgid "No entries."
 msgstr "Geen gegevens."
 
-#: mod/common.php:85
+#: mod/common.php:86
 msgid "No contacts in common."
 msgstr "Geen gedeelde contacten."
 
-#: mod/common.php:133
-msgid "Common Friends"
-msgstr "Gedeelde Vrienden"
-
 #: mod/uexport.php:77
 msgid "Export account"
 msgstr "Account exporteren"
@@ -5233,12 +5246,12 @@ msgid ""
 "hours."
 msgstr "Geen voorstellen beschikbaar. Als dit een nieuwe website is, kun je het over 24 uur nog eens proberen."
 
-#: mod/suggest.php:83 mod/suggest.php:100
+#: mod/suggest.php:83 mod/suggest.php:101
 msgid "Ignore/Hide"
 msgstr "Negeren/Verbergen"
 
-#: mod/suggest.php:110 include/contact_widgets.php:35
-#: view/theme/diabook/theme.php:527 view/theme/vier/theme.php:193
+#: mod/suggest.php:111 include/contact_widgets.php:35
+#: view/theme/diabook/theme.php:527 view/theme/vier/theme.php:207
 msgid "Friend Suggestions"
 msgstr "Vriendschapsvoorstellen"
 
@@ -5270,11 +5283,11 @@ msgstr "Echtelijke staat"
 msgid "Romantic Partner"
 msgstr "Romantische Partner"
 
-#: mod/profiles.php:344 mod/photos.php:1639 include/conversation.php:508
+#: mod/profiles.php:344 mod/photos.php:1647 include/conversation.php:508
 msgid "Likes"
 msgstr "Houdt van"
 
-#: mod/profiles.php:348 mod/photos.php:1639 include/conversation.php:508
+#: mod/profiles.php:348 mod/photos.php:1647 include/conversation.php:508
 msgid "Dislikes"
 msgstr "Houdt niet van"
 
@@ -5453,7 +5466,7 @@ msgstr "Voorbeelden: Kathleen123, Kathleen Peeters, kathleen@voorbeeld.nl"
 msgid "Since [date]:"
 msgstr "Sinds [datum]:"
 
-#: mod/profiles.php:724 include/identity.php:570
+#: mod/profiles.php:724 include/identity.php:619
 msgid "Sexual Preference:"
 msgstr "Seksuele Voorkeur:"
 
@@ -5461,11 +5474,11 @@ msgstr "Seksuele Voorkeur:"
 msgid "Homepage URL:"
 msgstr "Adres tijdlijn:"
 
-#: mod/profiles.php:726 include/identity.php:574
+#: mod/profiles.php:726 include/identity.php:623
 msgid "Hometown:"
 msgstr "Woonplaats:"
 
-#: mod/profiles.php:727 include/identity.php:578
+#: mod/profiles.php:727 include/identity.php:627
 msgid "Political Views:"
 msgstr "Politieke standpunten:"
 
@@ -5481,11 +5494,11 @@ msgstr "Publieke Sleutelwoorden:"
 msgid "Private Keywords:"
 msgstr "Privé Sleutelwoorden:"
 
-#: mod/profiles.php:731 include/identity.php:586
+#: mod/profiles.php:731 include/identity.php:635
 msgid "Likes:"
 msgstr "Houdt van:"
 
-#: mod/profiles.php:732 include/identity.php:588
+#: mod/profiles.php:732 include/identity.php:637
 msgid "Dislikes:"
 msgstr "Houdt niet van:"
 
@@ -5547,27 +5560,31 @@ msgid ""
 "be visible to anybody using the internet."
 msgstr "Dit is jouw <strong>publiek</strong> profiel.<br />Het <strong>kan</strong> zichtbaar zijn voor iedereen op het internet."
 
+#: mod/profiles.php:760
+msgid "Age: "
+msgstr "Leeftijd:"
+
 #: mod/profiles.php:813
 msgid "Edit/Manage Profiles"
 msgstr "Wijzig/Beheer Profielen"
 
-#: mod/profiles.php:814 include/identity.php:231 include/identity.php:257
+#: mod/profiles.php:814 include/identity.php:260 include/identity.php:286
 msgid "Change profile photo"
 msgstr "Profiel foto wijzigen"
 
-#: mod/profiles.php:815 include/identity.php:232
+#: mod/profiles.php:815 include/identity.php:261
 msgid "Create New Profile"
 msgstr "Maak nieuw profiel"
 
-#: mod/profiles.php:826 include/identity.php:242
+#: mod/profiles.php:826 include/identity.php:271
 msgid "Profile Image"
 msgstr "Profiel afbeelding"
 
-#: mod/profiles.php:828 include/identity.php:245
+#: mod/profiles.php:828 include/identity.php:274
 msgid "visible to everybody"
 msgstr "zichtbaar voor iedereen"
 
-#: mod/profiles.php:829 include/identity.php:246
+#: mod/profiles.php:829 include/identity.php:275
 msgid "Edit visibility"
 msgstr "Pas zichtbaarheid aan"
 
@@ -5579,111 +5596,111 @@ msgstr "Item niet gevonden"
 msgid "Edit post"
 msgstr "Bericht bewerken"
 
-#: mod/editpost.php:110 include/conversation.php:1185
+#: mod/editpost.php:111 include/conversation.php:1184
 msgid "upload photo"
 msgstr "Foto uploaden"
 
-#: mod/editpost.php:111 include/conversation.php:1186
+#: mod/editpost.php:112 include/conversation.php:1185
 msgid "Attach file"
 msgstr "Bestand bijvoegen"
 
-#: mod/editpost.php:112 include/conversation.php:1187
+#: mod/editpost.php:113 include/conversation.php:1186
 msgid "attach file"
 msgstr "bestand bijvoegen"
 
-#: mod/editpost.php:114 include/conversation.php:1189
+#: mod/editpost.php:115 include/conversation.php:1188
 msgid "web link"
 msgstr "webadres"
 
-#: mod/editpost.php:115 include/conversation.php:1190
+#: mod/editpost.php:116 include/conversation.php:1189
 msgid "Insert video link"
 msgstr "Voeg video toe"
 
-#: mod/editpost.php:116 include/conversation.php:1191
+#: mod/editpost.php:117 include/conversation.php:1190
 msgid "video link"
 msgstr "video adres"
 
-#: mod/editpost.php:117 include/conversation.php:1192
+#: mod/editpost.php:118 include/conversation.php:1191
 msgid "Insert audio link"
 msgstr "Voeg audio adres toe"
 
-#: mod/editpost.php:118 include/conversation.php:1193
+#: mod/editpost.php:119 include/conversation.php:1192
 msgid "audio link"
 msgstr "audio adres"
 
-#: mod/editpost.php:119 include/conversation.php:1194
+#: mod/editpost.php:120 include/conversation.php:1193
 msgid "Set your location"
 msgstr "Stel uw locatie in"
 
-#: mod/editpost.php:120 include/conversation.php:1195
+#: mod/editpost.php:121 include/conversation.php:1194
 msgid "set location"
 msgstr "Stel uw locatie in"
 
-#: mod/editpost.php:121 include/conversation.php:1196
+#: mod/editpost.php:122 include/conversation.php:1195
 msgid "Clear browser location"
 msgstr "Verwijder locatie uit uw webbrowser"
 
-#: mod/editpost.php:122 include/conversation.php:1197
+#: mod/editpost.php:123 include/conversation.php:1196
 msgid "clear location"
 msgstr "Verwijder locatie uit uw webbrowser"
 
-#: mod/editpost.php:124 include/conversation.php:1203
+#: mod/editpost.php:125 include/conversation.php:1202
 msgid "Permission settings"
 msgstr "Instellingen van rechten"
 
-#: mod/editpost.php:132 include/acl_selectors.php:343
+#: mod/editpost.php:133 include/acl_selectors.php:344
 msgid "CC: email addresses"
 msgstr "CC: e-mailadressen"
 
-#: mod/editpost.php:133 include/conversation.php:1212
+#: mod/editpost.php:134 include/conversation.php:1211
 msgid "Public post"
 msgstr "Openbare post"
 
-#: mod/editpost.php:136 include/conversation.php:1199
+#: mod/editpost.php:137 include/conversation.php:1198
 msgid "Set title"
 msgstr "Titel plaatsen"
 
-#: mod/editpost.php:138 include/conversation.php:1201
+#: mod/editpost.php:139 include/conversation.php:1200
 msgid "Categories (comma-separated list)"
 msgstr "Categorieën (komma-gescheiden lijst)"
 
-#: mod/editpost.php:139 include/acl_selectors.php:344
+#: mod/editpost.php:140 include/acl_selectors.php:345
 msgid "Example: bob@example.com, mary@example.com"
 msgstr "Voorbeeld: bob@voorbeeld.nl, an@voorbeeld.be"
 
-#: mod/friendica.php:59
+#: mod/friendica.php:70
 msgid "This is Friendica, version"
 msgstr "Dit is Friendica, versie"
 
-#: mod/friendica.php:60
+#: mod/friendica.php:71
 msgid "running at web location"
 msgstr "draaiend op web-adres"
 
-#: mod/friendica.php:62
+#: mod/friendica.php:73
 msgid ""
 "Please visit <a href=\"http://friendica.com\">Friendica.com</a> to learn "
 "more about the Friendica project."
 msgstr "Bezoek <a href=\"http://friendica.com\">Friendica.com</a> om meer te leren over het Friendica project."
 
-#: mod/friendica.php:64
+#: mod/friendica.php:75
 msgid "Bug reports and issues: please visit"
 msgstr "Bug rapporten en problemen: bezoek"
 
-#: mod/friendica.php:64
+#: mod/friendica.php:75
 msgid "the bugtracker at github"
 msgstr ""
 
-#: mod/friendica.php:65
+#: mod/friendica.php:76
 msgid ""
 "Suggestions, praise, donations, etc. - please email \"Info\" at Friendica - "
 "dot com"
 msgstr "Suggesties, lof, donaties, enzovoort - stuur een e-mail naar \"info\" op Friendica - dot com"
 
-#: mod/friendica.php:79
+#: mod/friendica.php:90
 msgid "Installed plugins/addons/apps:"
 msgstr "Geïnstalleerde plugins/toepassingen:"
 
-#: mod/friendica.php:92
+#: mod/friendica.php:103
 msgid "No installed plugins/addons/apps"
 msgstr "Geen plugins of toepassingen geïnstalleerd"
 
@@ -5713,7 +5730,7 @@ msgstr "Privacyinformatie op afstand niet beschikbaar."
 msgid "Visible to:"
 msgstr "Zichtbaar voor:"
 
-#: mod/notes.php:46 include/identity.php:677
+#: mod/notes.php:46 include/identity.php:730
 msgid "Personal Notes"
 msgstr "Persoonlijke Nota's"
 
@@ -5771,7 +5788,7 @@ msgid "Make this post private"
 msgstr "Dit bericht privé maken"
 
 #: mod/repair_ostatus.php:14
-msgid "Resubsribing to OStatus contacts"
+msgid "Resubscribing to OStatus contacts"
 msgstr ""
 
 #: mod/repair_ostatus.php:30
@@ -5870,197 +5887,197 @@ msgid ""
 "important, please visit http://friendica.com"
 msgstr "Voor meer informatie over het Friendica project en waarom wij denken dat het belangrijk is kun je http://friendica.com/ bezoeken"
 
-#: mod/photos.php:91 include/identity.php:652
+#: mod/photos.php:99 include/identity.php:705
 msgid "Photo Albums"
 msgstr "Fotoalbums"
 
-#: mod/photos.php:92 mod/photos.php:1891
+#: mod/photos.php:100 mod/photos.php:1899
 msgid "Recent Photos"
 msgstr "Recente foto's"
 
-#: mod/photos.php:95 mod/photos.php:1312 mod/photos.php:1893
+#: mod/photos.php:103 mod/photos.php:1320 mod/photos.php:1901
 msgid "Upload New Photos"
 msgstr "Nieuwe foto's uploaden"
 
-#: mod/photos.php:173
+#: mod/photos.php:181
 msgid "Contact information unavailable"
 msgstr "Contactinformatie niet beschikbaar"
 
-#: mod/photos.php:194
+#: mod/photos.php:202
 msgid "Album not found."
 msgstr "Album niet gevonden"
 
-#: mod/photos.php:224 mod/photos.php:236 mod/photos.php:1254
+#: mod/photos.php:232 mod/photos.php:244 mod/photos.php:1262
 msgid "Delete Album"
 msgstr "Verwijder album"
 
-#: mod/photos.php:234
+#: mod/photos.php:242
 msgid "Do you really want to delete this photo album and all its photos?"
 msgstr "Wil je echt dit fotoalbum en alle foto's erin verwijderen?"
 
-#: mod/photos.php:314 mod/photos.php:325 mod/photos.php:1572
+#: mod/photos.php:322 mod/photos.php:333 mod/photos.php:1580
 msgid "Delete Photo"
 msgstr "Verwijder foto"
 
-#: mod/photos.php:323
+#: mod/photos.php:331
 msgid "Do you really want to delete this photo?"
 msgstr "Wil je echt deze foto verwijderen?"
 
-#: mod/photos.php:698
+#: mod/photos.php:706
 #, php-format
 msgid "%1$s was tagged in %2$s by %3$s"
 msgstr "%1$s is gelabeld in %2$s door %3$s"
 
-#: mod/photos.php:698
+#: mod/photos.php:706
 msgid "a photo"
 msgstr "een foto"
 
-#: mod/photos.php:811
+#: mod/photos.php:819
 msgid "Image file is empty."
 msgstr "Afbeeldingsbestand is leeg."
 
-#: mod/photos.php:978
+#: mod/photos.php:986
 msgid "No photos selected"
 msgstr "Geen foto's geselecteerd"
 
-#: mod/photos.php:1139
+#: mod/photos.php:1147
 #, php-format
 msgid "You have used %1$.2f Mbytes of %2$.2f Mbytes photo storage."
 msgstr "Je hebt %1$.2f Mbytes van %2$.2f Mbytes foto-opslagruimte gebruikt."
 
-#: mod/photos.php:1174
+#: mod/photos.php:1182
 msgid "Upload Photos"
 msgstr "Upload foto's"
 
-#: mod/photos.php:1178 mod/photos.php:1249
+#: mod/photos.php:1186 mod/photos.php:1257
 msgid "New album name: "
 msgstr "Nieuwe albumnaam: "
 
-#: mod/photos.php:1179
+#: mod/photos.php:1187
 msgid "or existing album name: "
 msgstr "of bestaande albumnaam: "
 
-#: mod/photos.php:1180
+#: mod/photos.php:1188
 msgid "Do not show a status post for this upload"
 msgstr "Toon geen bericht op je tijdlijn van deze upload"
 
-#: mod/photos.php:1182 mod/photos.php:1567 include/acl_selectors.php:346
+#: mod/photos.php:1190 mod/photos.php:1575 include/acl_selectors.php:347
 msgid "Permissions"
 msgstr "Rechten"
 
-#: mod/photos.php:1193
+#: mod/photos.php:1201
 msgid "Private Photo"
 msgstr "Privé foto"
 
-#: mod/photos.php:1194
+#: mod/photos.php:1202
 msgid "Public Photo"
 msgstr "Publieke foto"
 
-#: mod/photos.php:1262
+#: mod/photos.php:1270
 msgid "Edit Album"
 msgstr "Album wijzigen"
 
-#: mod/photos.php:1268
+#: mod/photos.php:1276
 msgid "Show Newest First"
 msgstr "Toon niewste eerst"
 
-#: mod/photos.php:1270
+#: mod/photos.php:1278
 msgid "Show Oldest First"
 msgstr "Toon oudste eerst"
 
-#: mod/photos.php:1298 mod/photos.php:1876
+#: mod/photos.php:1306 mod/photos.php:1884
 msgid "View Photo"
 msgstr "Bekijk foto"
 
-#: mod/photos.php:1345
+#: mod/photos.php:1353
 msgid "Permission denied. Access to this item may be restricted."
 msgstr "Toegang geweigerd. Toegang tot dit item is mogelijk beperkt."
 
-#: mod/photos.php:1347
+#: mod/photos.php:1355
 msgid "Photo not available"
 msgstr "Foto is niet beschikbaar"
 
-#: mod/photos.php:1403
+#: mod/photos.php:1411
 msgid "View photo"
 msgstr "Bekijk foto"
 
-#: mod/photos.php:1403
+#: mod/photos.php:1411
 msgid "Edit photo"
 msgstr "Bewerk foto"
 
-#: mod/photos.php:1404
+#: mod/photos.php:1412
 msgid "Use as profile photo"
 msgstr "Gebruik als profielfoto"
 
-#: mod/photos.php:1429
+#: mod/photos.php:1437
 msgid "View Full Size"
 msgstr "Bekijk in volledig formaat"
 
-#: mod/photos.php:1515
+#: mod/photos.php:1523
 msgid "Tags: "
 msgstr "Labels: "
 
-#: mod/photos.php:1518
+#: mod/photos.php:1526
 msgid "[Remove any tag]"
 msgstr "[Alle labels verwijderen]"
 
-#: mod/photos.php:1558
+#: mod/photos.php:1566
 msgid "New album name"
 msgstr "Nieuwe albumnaam"
 
-#: mod/photos.php:1559
+#: mod/photos.php:1567
 msgid "Caption"
 msgstr "Onderschrift"
 
-#: mod/photos.php:1560
+#: mod/photos.php:1568
 msgid "Add a Tag"
 msgstr "Een label toevoegen"
 
-#: mod/photos.php:1560
+#: mod/photos.php:1568
 msgid ""
 "Example: @bob, @Barbara_Jensen, @jim@example.com, #California, #camping"
 msgstr "Voorbeeld: @bob, @Barbara_Jansen, @jan@voorbeeld.nl, #Ardennen, #camping "
 
-#: mod/photos.php:1561
+#: mod/photos.php:1569
 msgid "Do not rotate"
 msgstr ""
 
-#: mod/photos.php:1562
+#: mod/photos.php:1570
 msgid "Rotate CW (right)"
 msgstr "Roteren met de klok mee (rechts)"
 
-#: mod/photos.php:1563
+#: mod/photos.php:1571
 msgid "Rotate CCW (left)"
 msgstr "Roteren tegen de klok in (links)"
 
-#: mod/photos.php:1578
+#: mod/photos.php:1586
 msgid "Private photo"
 msgstr "Privé foto"
 
-#: mod/photos.php:1579
+#: mod/photos.php:1587
 msgid "Public photo"
 msgstr "Publieke foto"
 
-#: mod/photos.php:1601 include/conversation.php:1183
+#: mod/photos.php:1609 include/conversation.php:1182
 msgid "Share"
 msgstr "Delen"
 
-#: mod/photos.php:1640 include/conversation.php:509
-#: include/conversation.php:1405
+#: mod/photos.php:1648 include/conversation.php:509
+#: include/conversation.php:1413
 msgid "Attending"
 msgid_plural "Attending"
 msgstr[0] ""
 msgstr[1] ""
 
-#: mod/photos.php:1640 include/conversation.php:509
+#: mod/photos.php:1648 include/conversation.php:509
 msgid "Not attending"
 msgstr ""
 
-#: mod/photos.php:1640 include/conversation.php:509
+#: mod/photos.php:1648 include/conversation.php:509
 msgid "Might attend"
 msgstr ""
 
-#: mod/photos.php:1805
+#: mod/photos.php:1813
 msgid "Map"
 msgstr ""
 
@@ -6120,60 +6137,60 @@ msgstr "Item niet beschikbaar"
 msgid "Item was not found."
 msgstr "Item niet gevonden"
 
-#: boot.php:771
+#: boot.php:861
 msgid "Delete this item?"
 msgstr "Dit item verwijderen?"
 
-#: boot.php:774
+#: boot.php:864
 msgid "show fewer"
 msgstr "Minder tonen"
 
-#: boot.php:1148
+#: boot.php:1266
 #, php-format
 msgid "Update %s failed. See error logs."
 msgstr "Wijziging %s mislukt. Lees de error logbestanden."
 
-#: boot.php:1255
+#: boot.php:1378
 msgid "Create a New Account"
 msgstr "Nieuwe account aanmaken"
 
-#: boot.php:1280 include/nav.php:72
+#: boot.php:1403 include/nav.php:72
 msgid "Logout"
 msgstr "Uitloggen"
 
-#: boot.php:1283
+#: boot.php:1406
 msgid "Nickname or Email address: "
 msgstr "Bijnaam of e-mailadres:"
 
-#: boot.php:1284
+#: boot.php:1407
 msgid "Password: "
 msgstr "Wachtwoord:"
 
-#: boot.php:1285
+#: boot.php:1408
 msgid "Remember me"
 msgstr "Onthou me"
 
-#: boot.php:1288
+#: boot.php:1411
 msgid "Or login using OpenID: "
 msgstr "Of log in met OpenID:"
 
-#: boot.php:1294
+#: boot.php:1417
 msgid "Forgot your password?"
 msgstr "Wachtwoord vergeten?"
 
-#: boot.php:1297
+#: boot.php:1420
 msgid "Website Terms of Service"
 msgstr "Gebruikersvoorwaarden website"
 
-#: boot.php:1298
+#: boot.php:1421
 msgid "terms of service"
 msgstr "servicevoorwaarden"
 
-#: boot.php:1300
+#: boot.php:1423
 msgid "Website Privacy Policy"
 msgstr "Privacybeleid website"
 
-#: boot.php:1301
+#: boot.php:1424
 msgid "privacy policy"
 msgstr "privacybeleid"
 
@@ -6234,25 +6251,25 @@ msgid ""
 "[pre]%s[/pre]"
 msgstr ""
 
-#: include/dbstructure.php:152
+#: include/dbstructure.php:153
 msgid "Errors encountered creating database tables."
 msgstr "Tijdens het aanmaken van databasetabellen zijn fouten vastgesteld."
 
-#: include/dbstructure.php:210
+#: include/dbstructure.php:230
 msgid "Errors encountered performing database changes."
 msgstr ""
 
-#: include/auth.php:38
+#: include/auth.php:44
 msgid "Logged out."
 msgstr "Uitgelogd."
 
-#: include/auth.php:128 include/user.php:75
+#: include/auth.php:134 include/user.php:75
 msgid ""
 "We encountered a problem while logging in with the OpenID you provided. "
 "Please check the correct spelling of the ID."
 msgstr ""
 
-#: include/auth.php:128 include/user.php:75
+#: include/auth.php:134 include/user.php:75
 msgid "The error message was:"
 msgstr "De foutboodschap was:"
 
@@ -6288,7 +6305,7 @@ msgid "Examples: Robert Morgenstein, Fishing"
 msgstr "Voorbeelden: Jan Peeters, Vissen"
 
 #: include/contact_widgets.php:36 view/theme/diabook/theme.php:526
-#: view/theme/vier/theme.php:192
+#: view/theme/vier/theme.php:206
 msgid "Similar Interests"
 msgstr "Dezelfde interesses"
 
@@ -6297,205 +6314,228 @@ msgid "Random Profile"
 msgstr "Willekeurig Profiel"
 
 #: include/contact_widgets.php:38 view/theme/diabook/theme.php:528
-#: view/theme/vier/theme.php:194
+#: view/theme/vier/theme.php:208
 msgid "Invite Friends"
 msgstr "Vrienden uitnodigen"
 
-#: include/contact_widgets.php:71
+#: include/contact_widgets.php:108
 msgid "Networks"
 msgstr "Netwerken"
 
-#: include/contact_widgets.php:74
+#: include/contact_widgets.php:111
 msgid "All Networks"
 msgstr "Alle netwerken"
 
-#: include/contact_widgets.php:104 include/features.php:61
+#: include/contact_widgets.php:141 include/features.php:102
 msgid "Saved Folders"
 msgstr "Bewaarde Mappen"
 
-#: include/contact_widgets.php:107 include/contact_widgets.php:139
+#: include/contact_widgets.php:144 include/contact_widgets.php:176
 msgid "Everything"
 msgstr "Alles"
 
-#: include/contact_widgets.php:136
+#: include/contact_widgets.php:173
 msgid "Categories"
 msgstr "Categorieën"
 
-#: include/features.php:23
+#: include/contact_widgets.php:237
+#, php-format
+msgid "%d contact in common"
+msgid_plural "%d contacts in common"
+msgstr[0] "%d gedeeld contact"
+msgstr[1] "%d gedeelde contacten"
+
+#: include/features.php:63
 msgid "General Features"
 msgstr "Algemene functies"
 
-#: include/features.php:25
+#: include/features.php:65
 msgid "Multiple Profiles"
 msgstr "Meerdere profielen"
 
-#: include/features.php:25
+#: include/features.php:65
 msgid "Ability to create multiple profiles"
 msgstr "Mogelijkheid om meerdere profielen aan te maken"
 
-#: include/features.php:26
+#: include/features.php:66
 msgid "Photo Location"
 msgstr ""
 
-#: include/features.php:26
+#: include/features.php:66
 msgid ""
 "Photo metadata is normally stripped. This extracts the location (if present)"
 " prior to stripping metadata and links it to a map."
 msgstr ""
 
-#: include/features.php:31
+#: include/features.php:71
 msgid "Post Composition Features"
 msgstr "Functies voor het opstellen van berichten"
 
-#: include/features.php:32
+#: include/features.php:72
 msgid "Richtext Editor"
 msgstr "Tekstverwerker met opmaak"
 
-#: include/features.php:32
+#: include/features.php:72
 msgid "Enable richtext editor"
 msgstr "Gebruik een tekstverwerker met eenvoudige opmaakfuncties"
 
-#: include/features.php:33
+#: include/features.php:73
 msgid "Post Preview"
 msgstr "Voorvertoning bericht"
 
-#: include/features.php:33
+#: include/features.php:73
 msgid "Allow previewing posts and comments before publishing them"
 msgstr ""
 
-#: include/features.php:34
+#: include/features.php:74
 msgid "Auto-mention Forums"
 msgstr ""
 
-#: include/features.php:34
+#: include/features.php:74
 msgid ""
 "Add/remove mention when a fourm page is selected/deselected in ACL window."
 msgstr ""
 
-#: include/features.php:39
+#: include/features.php:79
 msgid "Network Sidebar Widgets"
 msgstr "Zijbalkwidgets op netwerkpagina"
 
-#: include/features.php:40
+#: include/features.php:80
 msgid "Search by Date"
 msgstr "Zoeken op datum"
 
-#: include/features.php:40
+#: include/features.php:80
 msgid "Ability to select posts by date ranges"
 msgstr "Mogelijkheid om berichten te selecteren volgens datumbereik"
 
-#: include/features.php:41
+#: include/features.php:81 include/features.php:111
+msgid "List Forums"
+msgstr ""
+
+#: include/features.php:81
+msgid "Enable widget to display the forums your are connected with"
+msgstr ""
+
+#: include/features.php:82
 msgid "Group Filter"
 msgstr "Groepsfilter"
 
-#: include/features.php:41
+#: include/features.php:82
 msgid "Enable widget to display Network posts only from selected group"
 msgstr "Sta de widget toe om netwerkberichten te tonen van bepaalde groepen"
 
-#: include/features.php:42
+#: include/features.php:83
 msgid "Network Filter"
 msgstr "Netwerkfilter"
 
-#: include/features.php:42
+#: include/features.php:83
 msgid "Enable widget to display Network posts only from selected network"
 msgstr "Sta de widget toe om netwerkberichten te tonen van bepaalde netwerken"
 
-#: include/features.php:43
+#: include/features.php:84
 msgid "Save search terms for re-use"
 msgstr "Sla zoekopdrachten op voor hergebruik"
 
-#: include/features.php:48
+#: include/features.php:89
 msgid "Network Tabs"
 msgstr "Netwerktabs"
 
-#: include/features.php:49
+#: include/features.php:90
 msgid "Network Personal Tab"
 msgstr "Persoonlijke netwerktab"
 
-#: include/features.php:49
+#: include/features.php:90
 msgid "Enable tab to display only Network posts that you've interacted on"
 msgstr "Sta het toe dat de tab netwerkberichten toont waarmee je interactie had"
 
-#: include/features.php:50
+#: include/features.php:91
 msgid "Network New Tab"
 msgstr "Nieuwe netwerktab"
 
-#: include/features.php:50
+#: include/features.php:91
 msgid "Enable tab to display only new Network posts (from the last 12 hours)"
 msgstr "Laat de tab alleen nieuwe netwerkberichten tonen (van de laatste 12 uur)"
 
-#: include/features.php:51
+#: include/features.php:92
 msgid "Network Shared Links Tab"
 msgstr ""
 
-#: include/features.php:51
+#: include/features.php:92
 msgid "Enable tab to display only Network posts with links in them"
 msgstr ""
 
-#: include/features.php:56
+#: include/features.php:97
 msgid "Post/Comment Tools"
 msgstr "Bericht-/reactiehulpmiddelen"
 
-#: include/features.php:57
+#: include/features.php:98
 msgid "Multiple Deletion"
 msgstr "Meervoudige verwijdering"
 
-#: include/features.php:57
+#: include/features.php:98
 msgid "Select and delete multiple posts/comments at once"
 msgstr "Selecteer en verwijder meerdere berichten/reacties in een keer"
 
-#: include/features.php:58
+#: include/features.php:99
 msgid "Edit Sent Posts"
 msgstr "Bewerk verzonden berichten"
 
-#: include/features.php:58
+#: include/features.php:99
 msgid "Edit and correct posts and comments after sending"
 msgstr "Bewerk en corrigeer berichten en reacties na verzending"
 
-#: include/features.php:59
+#: include/features.php:100
 msgid "Tagging"
 msgstr "Labelen"
 
-#: include/features.php:59
+#: include/features.php:100
 msgid "Ability to tag existing posts"
 msgstr "Mogelijkheid om bestaande berichten te labelen"
 
-#: include/features.php:60
+#: include/features.php:101
 msgid "Post Categories"
 msgstr "Categorieën berichten"
 
-#: include/features.php:60
+#: include/features.php:101
 msgid "Add categories to your posts"
 msgstr "Voeg categorieën toe aan je berichten"
 
-#: include/features.php:61
+#: include/features.php:102
 msgid "Ability to file posts under folders"
 msgstr "Mogelijkheid om berichten in mappen te bewaren"
 
-#: include/features.php:62
+#: include/features.php:103
 msgid "Dislike Posts"
 msgstr "Vind berichten niet leuk"
 
-#: include/features.php:62
+#: include/features.php:103
 msgid "Ability to dislike posts/comments"
 msgstr "Mogelijkheid om berichten of reacties niet leuk te vinden"
 
-#: include/features.php:63
+#: include/features.php:104
 msgid "Star Posts"
 msgstr "Geef berichten een ster"
 
-#: include/features.php:63
+#: include/features.php:104
 msgid "Ability to mark special posts with a star indicator"
 msgstr ""
 
-#: include/features.php:64
+#: include/features.php:105
 msgid "Mute Post Notifications"
 msgstr ""
 
-#: include/features.php:64
+#: include/features.php:105
 msgid "Ability to mute notifications for a thread"
 msgstr ""
 
+#: include/features.php:110
+msgid "Advanced Profile Settings"
+msgstr ""
+
+#: include/features.php:111
+msgid "Show visitors public community forums at the Advanced Profile Page"
+msgstr ""
+
 #: include/follow.php:77
 msgid "Connect URL missing."
 msgstr ""
@@ -6562,23 +6602,27 @@ msgstr "Een verwijderde groep met deze naam is weer tot leven gewekt. Bestaande
 msgid "Default privacy group for new contacts"
 msgstr ""
 
-#: include/group.php:228
+#: include/group.php:239
 msgid "Everybody"
 msgstr "Iedereen"
 
-#: include/group.php:251
+#: include/group.php:262
 msgid "edit"
 msgstr "verander"
 
-#: include/group.php:273
+#: include/group.php:285
+msgid "Edit groups"
+msgstr ""
+
+#: include/group.php:287
 msgid "Edit group"
 msgstr "Verander groep"
 
-#: include/group.php:274
+#: include/group.php:288
 msgid "Create a new group"
 msgstr "Maak nieuwe groep"
 
-#: include/group.php:277
+#: include/group.php:291
 msgid "Contacts not in any group"
 msgstr ""
 
@@ -6647,180 +6691,208 @@ msgstr "secondes"
 msgid "%1$d %2$s ago"
 msgstr "%1$d %2$s geleden"
 
-#: include/datetime.php:474 include/items.php:2484
+#: include/datetime.php:474 include/items.php:2477
 #, php-format
 msgid "%s's birthday"
 msgstr "%s's verjaardag"
 
-#: include/datetime.php:475 include/items.php:2485
+#: include/datetime.php:475 include/items.php:2478
 #, php-format
 msgid "Happy Birthday %s"
 msgstr "Gefeliciteerd %s"
 
-#: include/identity.php:38
+#: include/identity.php:42
 msgid "Requested account is not available."
 msgstr "Gevraagde account is niet beschikbaar."
 
-#: include/identity.php:121 include/identity.php:255 include/identity.php:608
+#: include/identity.php:95 include/identity.php:284 include/identity.php:662
 msgid "Edit profile"
 msgstr "Bewerk profiel"
 
-#: include/identity.php:220
+#: include/identity.php:244
+msgid "Atom feed"
+msgstr ""
+
+#: include/identity.php:249
 msgid "Message"
 msgstr "Bericht"
 
-#: include/identity.php:226 include/nav.php:185
+#: include/identity.php:255 include/nav.php:185
 msgid "Profiles"
 msgstr "Profielen"
 
-#: include/identity.php:226
+#: include/identity.php:255
 msgid "Manage/edit profiles"
 msgstr "Beheer/wijzig profielen"
 
-#: include/identity.php:342
-msgid "Network:"
-msgstr ""
-
-#: include/identity.php:374 include/identity.php:460
+#: include/identity.php:425 include/identity.php:509
 msgid "g A l F d"
 msgstr "G l j F"
 
-#: include/identity.php:375 include/identity.php:461
+#: include/identity.php:426 include/identity.php:510
 msgid "F d"
 msgstr "d F"
 
-#: include/identity.php:420 include/identity.php:507
+#: include/identity.php:471 include/identity.php:556
 msgid "[today]"
 msgstr "[vandaag]"
 
-#: include/identity.php:432
+#: include/identity.php:483
 msgid "Birthday Reminders"
 msgstr "Verjaardagsherinneringen"
 
-#: include/identity.php:433
+#: include/identity.php:484
 msgid "Birthdays this week:"
 msgstr "Verjaardagen deze week:"
 
-#: include/identity.php:494
+#: include/identity.php:543
 msgid "[No description]"
 msgstr "[Geen omschrijving]"
 
-#: include/identity.php:518
+#: include/identity.php:567
 msgid "Event Reminders"
 msgstr "Gebeurtenisherinneringen"
 
-#: include/identity.php:519
+#: include/identity.php:568
 msgid "Events this week:"
 msgstr "Gebeurtenissen deze week:"
 
-#: include/identity.php:546
+#: include/identity.php:595
 msgid "j F, Y"
 msgstr "F j Y"
 
-#: include/identity.php:547
+#: include/identity.php:596
 msgid "j F"
 msgstr "F j"
 
-#: include/identity.php:554
+#: include/identity.php:603
 msgid "Birthday:"
 msgstr "Verjaardag:"
 
-#: include/identity.php:558
+#: include/identity.php:607
 msgid "Age:"
 msgstr "Leeftijd:"
 
-#: include/identity.php:567
+#: include/identity.php:616
 #, php-format
 msgid "for %1$d %2$s"
 msgstr "voor %1$d %2$s"
 
-#: include/identity.php:580
+#: include/identity.php:629
 msgid "Religion:"
 msgstr "Religie:"
 
-#: include/identity.php:584
+#: include/identity.php:633
 msgid "Hobbies/Interests:"
 msgstr "Hobby:"
 
-#: include/identity.php:591
+#: include/identity.php:640
 msgid "Contact information and Social Networks:"
 msgstr "Contactinformatie en sociale netwerken:"
 
-#: include/identity.php:593
+#: include/identity.php:642
 msgid "Musical interests:"
 msgstr "Muzikale interesse "
 
-#: include/identity.php:595
+#: include/identity.php:644
 msgid "Books, literature:"
 msgstr "Boeken, literatuur:"
 
-#: include/identity.php:597
+#: include/identity.php:646
 msgid "Television:"
 msgstr "Televisie"
 
-#: include/identity.php:599
+#: include/identity.php:648
 msgid "Film/dance/culture/entertainment:"
 msgstr "Film/dans/cultuur/ontspanning:"
 
-#: include/identity.php:601
+#: include/identity.php:650
 msgid "Love/Romance:"
 msgstr "Liefde/romance:"
 
-#: include/identity.php:603
+#: include/identity.php:652
 msgid "Work/employment:"
 msgstr "Werk/beroep:"
 
-#: include/identity.php:605
+#: include/identity.php:654
 msgid "School/education:"
 msgstr "School/opleiding:"
 
-#: include/identity.php:633 include/nav.php:75
-msgid "Status"
-msgstr "Tijdlijn"
-
-#: include/identity.php:636
-msgid "Status Messages and Posts"
-msgstr "Berichten op jouw tijdlijn"
-
-#: include/identity.php:644
-msgid "Profile Details"
-msgstr "Profieldetails"
+#: include/identity.php:658
+msgid "Forums:"
+msgstr ""
 
-#: include/identity.php:657 include/identity.php:660 include/nav.php:78
+#: include/identity.php:710 include/identity.php:713 include/nav.php:78
 msgid "Videos"
 msgstr "Video's"
 
-#: include/identity.php:672 include/nav.php:140
+#: include/identity.php:725 include/nav.php:140
 msgid "Events and Calendar"
 msgstr "Gebeurtenissen en kalender"
 
-#: include/identity.php:680
+#: include/identity.php:733
 msgid "Only You Can See This"
 msgstr "Alleen jij kunt dit zien"
 
-#: include/acl_selectors.php:324
+#: include/like.php:167 include/conversation.php:122
+#: include/conversation.php:258 include/text.php:1991
+#: view/theme/diabook/theme.php:463
+msgid "event"
+msgstr "gebeurtenis"
+
+#: include/like.php:184 include/conversation.php:141 include/diaspora.php:2163
+#: view/theme/diabook/theme.php:480
+#, php-format
+msgid "%1$s likes %2$s's %3$s"
+msgstr "%1$s vindt het %3$s van %2$s leuk"
+
+#: include/like.php:186 include/conversation.php:144
+#, php-format
+msgid "%1$s doesn't like %2$s's %3$s"
+msgstr "%1$s vindt het %3$s van %2$s niet leuk"
+
+#: include/like.php:188
+#, php-format
+msgid "%1$s is attending %2$s's %3$s"
+msgstr ""
+
+#: include/like.php:190
+#, php-format
+msgid "%1$s is not attending %2$s's %3$s"
+msgstr ""
+
+#: include/like.php:192
+#, php-format
+msgid "%1$s may attend %2$s's %3$s"
+msgstr ""
+
+#: include/acl_selectors.php:325
 msgid "Post to Email"
 msgstr "Verzenden per e-mail"
 
-#: include/acl_selectors.php:329
+#: include/acl_selectors.php:330
 #, php-format
 msgid "Connectors disabled, since \"%s\" is enabled."
 msgstr ""
 
-#: include/acl_selectors.php:335
+#: include/acl_selectors.php:336
 msgid "Visible to everybody"
 msgstr "Zichtbaar voor iedereen"
 
-#: include/acl_selectors.php:336 view/theme/diabook/config.php:142
+#: include/acl_selectors.php:337 view/theme/diabook/config.php:142
 #: view/theme/diabook/theme.php:621 view/theme/vier/config.php:103
 msgid "show"
 msgstr "tonen"
 
-#: include/acl_selectors.php:337 view/theme/diabook/config.php:142
+#: include/acl_selectors.php:338 view/theme/diabook/config.php:142
 #: view/theme/diabook/theme.php:621 view/theme/vier/config.php:103
 msgid "don't show"
 msgstr "niet tonen"
 
+#: include/acl_selectors.php:348
+msgid "Close"
+msgstr "Afsluiten"
+
 #: include/message.php:15 include/message.php:173
 msgid "[no subject]"
 msgstr "[geen onderwerp]"
@@ -6829,31 +6901,31 @@ msgstr "[geen onderwerp]"
 msgid "stopped following"
 msgstr ""
 
-#: include/Contact.php:334 include/conversation.php:911
+#: include/Contact.php:337 include/conversation.php:911
 msgid "View Status"
 msgstr "Bekijk status"
 
-#: include/Contact.php:336 include/conversation.php:913
+#: include/Contact.php:339 include/conversation.php:913
 msgid "View Photos"
 msgstr "Bekijk foto's"
 
-#: include/Contact.php:337 include/conversation.php:914
+#: include/Contact.php:340 include/conversation.php:914
 msgid "Network Posts"
 msgstr "Netwerkberichten"
 
-#: include/Contact.php:338 include/conversation.php:915
+#: include/Contact.php:341 include/conversation.php:915
 msgid "Edit Contact"
 msgstr "Bewerk contact"
 
-#: include/Contact.php:339
+#: include/Contact.php:342
 msgid "Drop Contact"
 msgstr "Verwijder contact"
 
-#: include/Contact.php:340 include/conversation.php:916
+#: include/Contact.php:343 include/conversation.php:916
 msgid "Send PM"
 msgstr "Stuur een privébericht"
 
-#: include/Contact.php:341 include/conversation.php:920
+#: include/Contact.php:344 include/conversation.php:920
 msgid "Poke"
 msgstr "Aanstoten"
 
@@ -6916,406 +6988,407 @@ msgstr "Geselecteerde items verwijderen"
 msgid "Follow Thread"
 msgstr "Conversatie volgen"
 
-#: include/conversation.php:1035
+#: include/conversation.php:1034
 #, php-format
 msgid "%s likes this."
 msgstr "%s vindt dit leuk."
 
-#: include/conversation.php:1038
+#: include/conversation.php:1037
 #, php-format
 msgid "%s doesn't like this."
 msgstr "%s vindt dit niet leuk."
 
-#: include/conversation.php:1041
+#: include/conversation.php:1040
 #, php-format
 msgid "%s attends."
 msgstr ""
 
-#: include/conversation.php:1044
+#: include/conversation.php:1043
 #, php-format
 msgid "%s doesn't attend."
 msgstr ""
 
-#: include/conversation.php:1047
+#: include/conversation.php:1046
 #, php-format
 msgid "%s attends maybe."
 msgstr ""
 
-#: include/conversation.php:1057
+#: include/conversation.php:1056
 msgid "and"
 msgstr "en"
 
-#: include/conversation.php:1063
+#: include/conversation.php:1062
 #, php-format
 msgid ", and %d other people"
 msgstr ", en %d andere mensen"
 
-#: include/conversation.php:1072
+#: include/conversation.php:1071
 #, php-format
 msgid "<span  %1$s>%2$d people</span> like this"
 msgstr "<span  %1$s>%2$d mensen</span> vinden dit leuk"
 
-#: include/conversation.php:1073
+#: include/conversation.php:1072
 #, php-format
 msgid "%s like this."
 msgstr ""
 
-#: include/conversation.php:1076
+#: include/conversation.php:1075
 #, php-format
 msgid "<span  %1$s>%2$d people</span> don't like this"
 msgstr "<span  %1$s>%2$d people</span> vinden dit niet leuk"
 
-#: include/conversation.php:1077
+#: include/conversation.php:1076
 #, php-format
 msgid "%s don't like this."
 msgstr ""
 
-#: include/conversation.php:1080
+#: include/conversation.php:1079
 #, php-format
 msgid "<span  %1$s>%2$d people</span> attend"
 msgstr ""
 
-#: include/conversation.php:1081
+#: include/conversation.php:1080
 #, php-format
 msgid "%s attend."
 msgstr ""
 
-#: include/conversation.php:1084
+#: include/conversation.php:1083
 #, php-format
 msgid "<span  %1$s>%2$d people</span> don't attend"
 msgstr ""
 
-#: include/conversation.php:1085
+#: include/conversation.php:1084
 #, php-format
 msgid "%s don't attend."
 msgstr ""
 
-#: include/conversation.php:1088
+#: include/conversation.php:1087
 #, php-format
 msgid "<span  %1$s>%2$d people</span> anttend maybe"
 msgstr ""
 
-#: include/conversation.php:1089
+#: include/conversation.php:1088
 #, php-format
 msgid "%s anttend maybe."
 msgstr ""
 
-#: include/conversation.php:1128 include/conversation.php:1146
+#: include/conversation.php:1127 include/conversation.php:1145
 msgid "Visible to <strong>everybody</strong>"
 msgstr "Zichtbaar voor <strong>iedereen</strong>"
 
-#: include/conversation.php:1130 include/conversation.php:1148
+#: include/conversation.php:1129 include/conversation.php:1147
 msgid "Please enter a video link/URL:"
 msgstr "Vul een videolink/URL in:"
 
-#: include/conversation.php:1131 include/conversation.php:1149
+#: include/conversation.php:1130 include/conversation.php:1148
 msgid "Please enter an audio link/URL:"
 msgstr "Vul een audiolink/URL in:"
 
-#: include/conversation.php:1132 include/conversation.php:1150
+#: include/conversation.php:1131 include/conversation.php:1149
 msgid "Tag term:"
 msgstr "Label:"
 
-#: include/conversation.php:1134 include/conversation.php:1152
+#: include/conversation.php:1133 include/conversation.php:1151
 msgid "Where are you right now?"
 msgstr "Waar ben je nu?"
 
-#: include/conversation.php:1135
+#: include/conversation.php:1134
 msgid "Delete item(s)?"
 msgstr "Item(s) verwijderen?"
 
-#: include/conversation.php:1204
+#: include/conversation.php:1203
 msgid "permissions"
 msgstr "rechten"
 
-#: include/conversation.php:1227
+#: include/conversation.php:1226
 msgid "Post to Groups"
 msgstr "Verzenden naar Groepen"
 
-#: include/conversation.php:1228
+#: include/conversation.php:1227
 msgid "Post to Contacts"
 msgstr "Verzenden naar Contacten"
 
-#: include/conversation.php:1229
+#: include/conversation.php:1228
 msgid "Private post"
 msgstr "Privé verzending"
 
-#: include/conversation.php:1377
+#: include/conversation.php:1385
 msgid "View all"
 msgstr ""
 
-#: include/conversation.php:1399
+#: include/conversation.php:1407
 msgid "Like"
 msgid_plural "Likes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: include/conversation.php:1402
+#: include/conversation.php:1410
 msgid "Dislike"
 msgid_plural "Dislikes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: include/conversation.php:1408
+#: include/conversation.php:1416
 msgid "Not Attending"
 msgid_plural "Not Attending"
 msgstr[0] ""
 msgstr[1] ""
 
-#: include/conversation.php:1411 include/profile_selectors.php:6
+#: include/conversation.php:1419 include/profile_selectors.php:6
 msgid "Undecided"
 msgid_plural "Undecided"
 msgstr[0] ""
 msgstr[1] ""
 
+#: include/forums.php:105 include/text.php:1015 include/nav.php:126
+#: view/theme/vier/theme.php:259
+msgid "Forums"
+msgstr ""
+
+#: include/forums.php:107 view/theme/vier/theme.php:261
+msgid "External link to forum"
+msgstr ""
+
 #: include/network.php:967
 msgid "view full size"
 msgstr "Volledig formaat"
 
-#: include/text.php:299
+#: include/text.php:303
 msgid "newer"
 msgstr "nieuwere berichten"
 
-#: include/text.php:301
+#: include/text.php:305
 msgid "older"
 msgstr "oudere berichten"
 
-#: include/text.php:306
+#: include/text.php:310
 msgid "prev"
 msgstr "vorige"
 
-#: include/text.php:308
+#: include/text.php:312
 msgid "first"
 msgstr "eerste"
 
-#: include/text.php:340
+#: include/text.php:344
 msgid "last"
 msgstr "laatste"
 
-#: include/text.php:343
+#: include/text.php:347
 msgid "next"
 msgstr "volgende"
 
-#: include/text.php:398
+#: include/text.php:402
 msgid "Loading more entries..."
 msgstr ""
 
-#: include/text.php:399
+#: include/text.php:403
 msgid "The end"
 msgstr ""
 
-#: include/text.php:890
+#: include/text.php:894
 msgid "No contacts"
 msgstr "Geen contacten"
 
-#: include/text.php:905
+#: include/text.php:909
 #, php-format
 msgid "%d Contact"
 msgid_plural "%d Contacts"
 msgstr[0] "%d contact"
 msgstr[1] "%d contacten"
 
-#: include/text.php:1003 include/nav.php:121
+#: include/text.php:921
+msgid "View Contacts"
+msgstr "Bekijk contacten"
+
+#: include/text.php:1010 include/nav.php:121
 msgid "Full Text"
 msgstr ""
 
-#: include/text.php:1004 include/nav.php:122
+#: include/text.php:1011 include/nav.php:122
 msgid "Tags"
 msgstr ""
 
-#: include/text.php:1008 include/nav.php:126
-msgid "Forums"
-msgstr ""
-
-#: include/text.php:1059
+#: include/text.php:1066
 msgid "poke"
 msgstr "aanstoten"
 
-#: include/text.php:1059
+#: include/text.php:1066
 msgid "poked"
 msgstr "aangestoten"
 
-#: include/text.php:1060
+#: include/text.php:1067
 msgid "ping"
 msgstr "ping"
 
-#: include/text.php:1060
+#: include/text.php:1067
 msgid "pinged"
 msgstr "gepingd"
 
-#: include/text.php:1061
+#: include/text.php:1068
 msgid "prod"
 msgstr "porren"
 
-#: include/text.php:1061
+#: include/text.php:1068
 msgid "prodded"
 msgstr "gepord"
 
-#: include/text.php:1062
+#: include/text.php:1069
 msgid "slap"
 msgstr "slaan"
 
-#: include/text.php:1062
+#: include/text.php:1069
 msgid "slapped"
 msgstr "geslagen"
 
-#: include/text.php:1063
+#: include/text.php:1070
 msgid "finger"
 msgstr "finger"
 
-#: include/text.php:1063
+#: include/text.php:1070
 msgid "fingered"
 msgstr "gerfingerd"
 
-#: include/text.php:1064
+#: include/text.php:1071
 msgid "rebuff"
 msgstr "afpoeieren"
 
-#: include/text.php:1064
+#: include/text.php:1071
 msgid "rebuffed"
 msgstr "afgepoeierd"
 
-#: include/text.php:1078
+#: include/text.php:1085
 msgid "happy"
 msgstr "Blij"
 
-#: include/text.php:1079
+#: include/text.php:1086
 msgid "sad"
 msgstr "Verdrietig"
 
-#: include/text.php:1080
+#: include/text.php:1087
 msgid "mellow"
 msgstr "mellow"
 
-#: include/text.php:1081
+#: include/text.php:1088
 msgid "tired"
 msgstr "vermoeid"
 
-#: include/text.php:1082
+#: include/text.php:1089
 msgid "perky"
 msgstr "parmantig"
 
-#: include/text.php:1083
+#: include/text.php:1090
 msgid "angry"
 msgstr "boos"
 
-#: include/text.php:1084
+#: include/text.php:1091
 msgid "stupified"
 msgstr "verbijsterd"
 
-#: include/text.php:1085
+#: include/text.php:1092
 msgid "puzzled"
 msgstr "onzeker"
 
-#: include/text.php:1086
+#: include/text.php:1093
 msgid "interested"
 msgstr "Geïnteresseerd"
 
-#: include/text.php:1087
+#: include/text.php:1094
 msgid "bitter"
 msgstr "bitter"
 
-#: include/text.php:1088
+#: include/text.php:1095
 msgid "cheerful"
 msgstr "vrolijk"
 
-#: include/text.php:1089
+#: include/text.php:1096
 msgid "alive"
 msgstr "levend"
 
-#: include/text.php:1090
+#: include/text.php:1097
 msgid "annoyed"
 msgstr "verveeld"
 
-#: include/text.php:1091
+#: include/text.php:1098
 msgid "anxious"
 msgstr "bezorgd"
 
-#: include/text.php:1092
+#: include/text.php:1099
 msgid "cranky"
 msgstr "humeurig "
 
-#: include/text.php:1093
+#: include/text.php:1100
 msgid "disturbed"
 msgstr "verontrust"
 
-#: include/text.php:1094
+#: include/text.php:1101
 msgid "frustrated"
 msgstr "gefrustreerd"
 
-#: include/text.php:1095
+#: include/text.php:1102
 msgid "motivated"
 msgstr "gemotiveerd"
 
-#: include/text.php:1096
+#: include/text.php:1103
 msgid "relaxed"
 msgstr "ontspannen"
 
-#: include/text.php:1097
+#: include/text.php:1104
 msgid "surprised"
 msgstr "verbaasd"
 
-#: include/text.php:1490
+#: include/text.php:1497
 msgid "bytes"
 msgstr "bytes"
 
-#: include/text.php:1522 include/text.php:1534
+#: include/text.php:1529 include/text.php:1541
 msgid "Click to open/close"
 msgstr "klik om te openen/sluiten"
 
-#: include/text.php:1708
+#: include/text.php:1715
 msgid "View on separate page"
 msgstr ""
 
-#: include/text.php:1709
+#: include/text.php:1716
 msgid "view on separate page"
 msgstr ""
 
-#: include/text.php:1990
+#: include/text.php:1995
 msgid "activity"
 msgstr "activiteit"
 
-#: include/text.php:1993
+#: include/text.php:1998
 msgid "post"
 msgstr "bericht"
 
-#: include/text.php:2161
+#: include/text.php:2166
 msgid "Item filed"
 msgstr "Item bewaard"
 
-#: include/bbcode.php:474 include/bbcode.php:1132 include/bbcode.php:1133
+#: include/bbcode.php:482 include/bbcode.php:1157 include/bbcode.php:1158
 msgid "Image/photo"
 msgstr "Afbeelding/foto"
 
-#: include/bbcode.php:572
+#: include/bbcode.php:595
 #, php-format
 msgid "<a href=\"%1$s\" target=\"_blank\">%2$s</a> %3$s"
 msgstr ""
 
-#: include/bbcode.php:606
+#: include/bbcode.php:629
 #, php-format
 msgid ""
 "<span><a href=\"%s\" target=\"_blank\">%s</a> wrote the following <a "
 "href=\"%s\" target=\"_blank\">post</a>"
 msgstr ""
 
-#: include/bbcode.php:1092 include/bbcode.php:1112
+#: include/bbcode.php:1117 include/bbcode.php:1137
 msgid "$1 wrote:"
 msgstr "$1 schreef:"
 
-#: include/bbcode.php:1141 include/bbcode.php:1142
+#: include/bbcode.php:1166 include/bbcode.php:1167
 msgid "Encrypted content"
 msgstr "Versleutelde inhoud"
 
-#: include/notifier.php:840 include/delivery.php:456
-msgid "(no subject)"
-msgstr "(geen onderwerp)"
-
-#: include/notifier.php:850 include/delivery.php:467 include/enotify.php:37
-msgid "noreply"
-msgstr "geen reactie"
-
-#: include/dba_pdo.php:72 include/dba.php:56
+#: include/dba_pdo.php:72 include/dba.php:55
 #, php-format
 msgid "Cannot locate DNS info for database server '%s'"
 msgstr ""
@@ -7360,6 +7433,10 @@ msgstr "OStatus"
 msgid "RSS/Atom"
 msgstr "RSS/Atom"
 
+#: include/contact_selectors.php:81
+msgid "Facebook"
+msgstr "Facebook"
+
 #: include/contact_selectors.php:82
 msgid "Zot!"
 msgstr "Zot!"
@@ -7404,7 +7481,7 @@ msgstr ""
 msgid "Redmatrix"
 msgstr ""
 
-#: include/Scrape.php:603
+#: include/Scrape.php:624
 msgid " on Last.fm"
 msgstr " op Last.fm"
 
@@ -7416,15 +7493,15 @@ msgstr "Begint:"
 msgid "Finishes:"
 msgstr "Eindigt:"
 
-#: include/plugin.php:458 include/plugin.php:460
+#: include/plugin.php:522 include/plugin.php:524
 msgid "Click here to upgrade."
 msgstr ""
 
-#: include/plugin.php:466
+#: include/plugin.php:530
 msgid "This action exceeds the limits set by your subscription plan."
 msgstr ""
 
-#: include/plugin.php:471
+#: include/plugin.php:535
 msgid "This action is not available under your subscription plan."
 msgstr ""
 
@@ -7580,46 +7657,21 @@ msgstr "Navigatie"
 msgid "Site map"
 msgstr "Sitemap"
 
-#: include/api.php:321 include/api.php:332 include/api.php:441
-#: include/api.php:1151 include/api.php:1153
-msgid "User not found."
-msgstr "Gebruiker niet gevonden"
-
-#: include/api.php:799
+#: include/api.php:878
 #, php-format
 msgid "Daily posting limit of %d posts reached. The post was rejected."
 msgstr ""
 
-#: include/api.php:818
+#: include/api.php:897
 #, php-format
 msgid "Weekly posting limit of %d posts reached. The post was rejected."
 msgstr ""
 
-#: include/api.php:837
+#: include/api.php:916
 #, php-format
 msgid "Monthly posting limit of %d posts reached. The post was rejected."
 msgstr ""
 
-#: include/api.php:1360
-msgid "There is no status with this id."
-msgstr "Er is geen status met dit kenmerk"
-
-#: include/api.php:1434
-msgid "There is no conversation with this id."
-msgstr ""
-
-#: include/api.php:1713
-msgid "Invalid item."
-msgstr ""
-
-#: include/api.php:1723
-msgid "Invalid action. "
-msgstr ""
-
-#: include/api.php:1731
-msgid "DB error"
-msgstr ""
-
 #: include/user.php:48
 msgid "An invitation is required."
 msgstr "Een uitnodiging is vereist."
@@ -7664,37 +7716,37 @@ msgstr "Ik kan die e-mail niet gebruiken."
 msgid "Your \"nickname\" can only contain \"a-z\", \"0-9\" and \"_\"."
 msgstr ""
 
-#: include/user.php:146 include/user.php:244
+#: include/user.php:147 include/user.php:245
 msgid "Nickname is already registered. Please choose another."
 msgstr "Bijnaam is al geregistreerd. Kies een andere."
 
-#: include/user.php:156
+#: include/user.php:157
 msgid ""
 "Nickname was once registered here and may not be re-used. Please choose "
 "another."
 msgstr "Bijnaam was ooit hier geregistreerd en kan niet herbruikt worden. Kies een andere."
 
-#: include/user.php:172
+#: include/user.php:173
 msgid "SERIOUS ERROR: Generation of security keys failed."
 msgstr "ERNSTIGE FOUT: aanmaken van beveiligingssleutels mislukt."
 
-#: include/user.php:230
+#: include/user.php:231
 msgid "An error occurred during registration. Please try again."
 msgstr ""
 
-#: include/user.php:255 view/theme/duepuntozero/config.php:44
+#: include/user.php:256 view/theme/duepuntozero/config.php:44
 msgid "default"
 msgstr "standaard"
 
-#: include/user.php:265
+#: include/user.php:266
 msgid "An error occurred creating your default profile. Please try again."
 msgstr ""
 
-#: include/user.php:297 include/user.php:301 include/profile_selectors.php:42
+#: include/user.php:299 include/user.php:303 include/profile_selectors.php:42
 msgid "Friends"
 msgstr "Vrienden"
 
-#: include/user.php:385
+#: include/user.php:387
 #, php-format
 msgid ""
 "\n"
@@ -7703,7 +7755,7 @@ msgid ""
 "\t"
 msgstr ""
 
-#: include/user.php:389
+#: include/user.php:391
 #, php-format
 msgid ""
 "\n"
@@ -7733,19 +7785,27 @@ msgid ""
 "\t\tThank you and welcome to %2$s."
 msgstr ""
 
-#: include/diaspora.php:719
+#: include/diaspora.php:720
 msgid "Sharing notification from Diaspora network"
 msgstr ""
 
-#: include/diaspora.php:2574
+#: include/diaspora.php:2604
 msgid "Attachments:"
 msgstr "Bijlagen:"
 
-#: include/items.php:4933
+#: include/delivery.php:533
+msgid "(no subject)"
+msgstr "(geen onderwerp)"
+
+#: include/delivery.php:544 include/enotify.php:37
+msgid "noreply"
+msgstr "geen reactie"
+
+#: include/items.php:4903
 msgid "Do you really want to delete this item?"
 msgstr "Wil je echt dit item verwijderen?"
 
-#: include/items.php:5208
+#: include/items.php:5178
 msgid "Archives"
 msgstr "Archieven"
 
@@ -8261,11 +8321,11 @@ msgstr ""
 msgid "Please visit %s to approve or reject the request."
 msgstr ""
 
-#: include/oembed.php:220
+#: include/oembed.php:214
 msgid "Embedded content"
 msgstr "Ingebedde inhoud"
 
-#: include/oembed.php:229
+#: include/oembed.php:223
 msgid "Embedding disabled"
 msgstr "Inbedden uitgeschakeld"
 
@@ -8305,7 +8365,7 @@ msgstr[1] "%d contacten werden niet geïmporteerd"
 msgid "Done. You can now login with your username and password"
 msgstr "Gebeurd. Je kunt nu inloggen met je gebruikersnaam en wachtwoord"
 
-#: index.php:441
+#: index.php:442
 msgid "toggle mobile"
 msgstr "mobiel thema omwisselen"
 
@@ -8376,7 +8436,7 @@ msgstr ""
 
 #: view/theme/diabook/config.php:158 view/theme/diabook/theme.php:130
 #: view/theme/diabook/theme.php:544 view/theme/diabook/theme.php:624
-#: view/theme/vier/config.php:111 view/theme/vier/theme.php:230
+#: view/theme/vier/config.php:111
 msgid "Community Pages"
 msgstr "Forum/groepspagina's"
 
@@ -8387,7 +8447,7 @@ msgstr "Earth Layers"
 
 #: view/theme/diabook/config.php:160 view/theme/diabook/theme.php:391
 #: view/theme/diabook/theme.php:626 view/theme/vier/config.php:112
-#: view/theme/vier/theme.php:142
+#: view/theme/vier/theme.php:156
 msgid "Community Profiles"
 msgstr "Forum/groepsprofielen"
 
@@ -8398,19 +8458,19 @@ msgstr ""
 
 #: view/theme/diabook/config.php:162 view/theme/diabook/theme.php:606
 #: view/theme/diabook/theme.php:628 view/theme/vier/config.php:114
-#: view/theme/vier/theme.php:348
+#: view/theme/vier/theme.php:377
 msgid "Connect Services"
 msgstr "Diensten verbinden"
 
 #: view/theme/diabook/config.php:163 view/theme/diabook/theme.php:523
 #: view/theme/diabook/theme.php:629 view/theme/vier/config.php:115
-#: view/theme/vier/theme.php:189
+#: view/theme/vier/theme.php:203
 msgid "Find Friends"
 msgstr "Zoek vrienden"
 
 #: view/theme/diabook/config.php:164 view/theme/diabook/theme.php:412
 #: view/theme/diabook/theme.php:630 view/theme/vier/config.php:116
-#: view/theme/vier/theme.php:171
+#: view/theme/vier/theme.php:185
 msgid "Last users"
 msgstr "Laatste gebruikers"
 
@@ -8432,7 +8492,7 @@ msgstr "Jouw contacten"
 msgid "Your personal photos"
 msgstr "Jouw persoonlijke foto's"
 
-#: view/theme/diabook/theme.php:524 view/theme/vier/theme.php:190
+#: view/theme/diabook/theme.php:524 view/theme/vier/theme.php:204
 msgid "Local Directory"
 msgstr "Lokale gids"
 
@@ -8452,11 +8512,7 @@ msgstr ""
 msgid "Set style"
 msgstr ""
 
-#: view/theme/vier/theme.php:234
-msgid "External link to forum"
-msgstr ""
-
-#: view/theme/vier/theme.php:266
+#: view/theme/vier/theme.php:295
 msgid "Quick Start"
 msgstr ""
 
index f535f54be0853ceb37335aa12952a424347310d8..0e8399be7bd2665241c0b698003c32a7d9cf81a7 100644 (file)
@@ -5,9 +5,11 @@ function string_plural_select_nl($n){
        return ($n != 1);;
 }}
 ;
+$a->strings["Network:"] = "Netwerk:";
+$a->strings["Forum"] = "Forum";
 $a->strings["%d contact edited."] = array(
-       0 => "",
-       1 => "",
+       0 => "%d contact gewijzigd.",
+       1 => "%d contacten gewijzigd.",
 );
 $a->strings["Could not access contact record."] = "Kon geen toegang krijgen tot de contactgegevens";
 $a->strings["Could not locate selected profile."] = "Kon het geselecteerde profiel niet vinden.";
@@ -33,28 +35,11 @@ $a->strings["(Update was successful)"] = "(Wijziging is geslaagd)";
 $a->strings["(Update was not successful)"] = "(Wijziging is niet geslaagd)";
 $a->strings["Suggest friends"] = "Stel vrienden voor";
 $a->strings["Network type: %s"] = "Netwerk type: %s";
-$a->strings["%d contact in common"] = array(
-       0 => "%d gedeeld contact",
-       1 => "%d gedeelde contacten",
-);
-$a->strings["View all contacts"] = "Alle contacten zien";
-$a->strings["Unblock"] = "Blokkering opheffen";
-$a->strings["Block"] = "Blokkeren";
-$a->strings["Toggle Blocked status"] = "Schakel geblokkeerde status";
-$a->strings["Unignore"] = "Negeer niet meer";
-$a->strings["Ignore"] = "Negeren";
-$a->strings["Toggle Ignored status"] = "Schakel negeerstatus";
-$a->strings["Unarchive"] = "Archiveer niet meer";
-$a->strings["Archive"] = "Archiveer";
-$a->strings["Toggle Archive status"] = "Schakel archiveringsstatus";
-$a->strings["Repair"] = "Herstellen";
-$a->strings["Advanced Contact Settings"] = "Geavanceerde instellingen voor contacten";
 $a->strings["Communications lost with this contact!"] = "Communicatie met dit contact is verbroken!";
 $a->strings["Fetch further information for feeds"] = "";
 $a->strings["Disabled"] = "Uitgeschakeld";
 $a->strings["Fetch information"] = "";
 $a->strings["Fetch information and keywords"] = "";
-$a->strings["Contact Editor"] = "Contactbewerker";
 $a->strings["Submit"] = "Opslaan";
 $a->strings["Profile Visibility"] = "Zichtbaarheid profiel";
 $a->strings["Please choose the profile you would like to display to %s when viewing your profile securely."] = "Kies het profiel dat getoond moet worden wanneer %s uw profiel bezoekt. ";
@@ -70,6 +55,10 @@ $a->strings["Last update:"] = "Laatste wijziging:";
 $a->strings["Update public posts"] = "Openbare posts aanpassen";
 $a->strings["Update now"] = "Wijzig nu";
 $a->strings["Connect/Follow"] = "Verbind/Volg";
+$a->strings["Unblock"] = "Blokkering opheffen";
+$a->strings["Block"] = "Blokkeren";
+$a->strings["Unignore"] = "Negeer niet meer";
+$a->strings["Ignore"] = "Negeren";
 $a->strings["Currently blocked"] = "Op dit moment geblokkeerd";
 $a->strings["Currently ignored"] = "Op dit moment genegeerd";
 $a->strings["Currently archived"] = "Op dit moment gearchiveerd";
@@ -80,6 +69,9 @@ $a->strings["Send a notification of every new post of this contact"] = "";
 $a->strings["Blacklisted keywords"] = "";
 $a->strings["Comma separated list of keywords that should not be converted to hashtags, when \"Fetch information and keywords\" is selected"] = "";
 $a->strings["Profile URL"] = "Profiel url";
+$a->strings["Location:"] = "Plaats:";
+$a->strings["About:"] = "Over:";
+$a->strings["Tags:"] = "Labels:";
 $a->strings["Suggestions"] = "Voorstellen";
 $a->strings["Suggest potential friends"] = "Stel vrienden voor";
 $a->strings["All Contacts"] = "Alle Contacten";
@@ -99,7 +91,21 @@ $a->strings["Search your contacts"] = "Doorzoek je contacten";
 $a->strings["Finding: "] = "Gevonden:";
 $a->strings["Find"] = "Zoek";
 $a->strings["Update"] = "Wijzigen";
+$a->strings["Archive"] = "Archiveer";
+$a->strings["Unarchive"] = "Archiveer niet meer";
 $a->strings["Delete"] = "Verwijder";
+$a->strings["Status"] = "Tijdlijn";
+$a->strings["Status Messages and Posts"] = "Berichten op jouw tijdlijn";
+$a->strings["Profile"] = "Profiel";
+$a->strings["Profile Details"] = "Profieldetails";
+$a->strings["View all contacts"] = "Alle contacten zien";
+$a->strings["Common Friends"] = "Gedeelde Vrienden";
+$a->strings["View all common friends"] = "";
+$a->strings["Repair"] = "Herstellen";
+$a->strings["Advanced Contact Settings"] = "Geavanceerde instellingen voor contacten";
+$a->strings["Toggle Blocked status"] = "Schakel geblokkeerde status";
+$a->strings["Toggle Ignored status"] = "Schakel negeerstatus";
+$a->strings["Toggle Archive status"] = "Schakel archiveringsstatus";
 $a->strings["Mutual Friendship"] = "Wederzijdse vriendschap";
 $a->strings["is a fan of yours"] = "Is een fan van jou";
 $a->strings["you are a fan of"] = "Jij bent een fan van";
@@ -112,7 +118,6 @@ $a->strings["Post successful."] = "Bericht succesvol geplaatst.";
 $a->strings["Permission denied"] = "Toegang geweigerd";
 $a->strings["Invalid profile identifier."] = "Ongeldige profiel-identificatie.";
 $a->strings["Profile Visibility Editor"] = "";
-$a->strings["Profile"] = "Profiel";
 $a->strings["Click on a contact to add or remove."] = "Klik op een contact om het toe te voegen of te verwijderen.";
 $a->strings["Visible To"] = "Zichtbaar voor";
 $a->strings["All Contacts (with secure profile access)"] = "Alle contacten (met veilige profieltoegang)";
@@ -137,9 +142,6 @@ $a->strings["Edit your <strong>default</strong> profile to your liking. Review t
 $a->strings["Profile Keywords"] = "Sleutelwoorden voor dit profiel";
 $a->strings["Set some public keywords for your default profile which describe your interests. We may be able to find other people with similar interests and suggest friendships."] = "Stel enkele openbare sleutelwoorden in voor je standaard profiel die je interesses beschrijven. We kunnen dan misschien mensen vinden met gelijkaardige interesses, en vrienden voorstellen.";
 $a->strings["Connecting"] = "Verbinding aan het maken";
-$a->strings["Facebook"] = "Facebook";
-$a->strings["Authorise the Facebook Connector if you currently have a Facebook account and we will (optionally) import all your Facebook friends and conversations."] = "Machtig de Facebook Connector als je een Facebook account hebt. We zullen (optioneel) al je Facebook vrienden en conversaties importeren.";
-$a->strings["<em>If</em> this is your own personal server, installing the Facebook addon may ease your transition to the free social web."] = "<em>Als</em> dit jouw eigen persoonlijke server is kan het installeren van de Facebook toevoeging je overgang naar het vrije sociale web vergemakkelijken.";
 $a->strings["Importing Emails"] = "E-mails importeren";
 $a->strings["Enter your email access information on your Connector Settings page if you wish to import and interact with friends or mailing lists from your email INBOX"] = "Vul je e-mailtoegangsinformatie in op je pagina met verbindingsinstellingen als je vrienden of mailinglijsten uit je e-mail-inbox wilt importeren, en met hen wilt communiceren";
 $a->strings["Go to Your Contacts Page"] = "Ga naar je contactenpagina";
@@ -184,7 +186,7 @@ $a->strings["Tag removed"] = "Label verwijderd";
 $a->strings["Remove Item Tag"] = "Verwijder label van item";
 $a->strings["Select a tag to remove: "] = "Selecteer een label om te verwijderen: ";
 $a->strings["Remove"] = "Verwijderen";
-$a->strings["Subsribing to OStatus contacts"] = "";
+$a->strings["Subscribing to OStatus contacts"] = "";
 $a->strings["No contact provided."] = "";
 $a->strings["Couldn't fetch information for contact."] = "";
 $a->strings["Couldn't fetch friends for contact."] = "";
@@ -206,9 +208,6 @@ $a->strings["Does %s know you?"] = "Kent %s jou?";
 $a->strings["No"] = "Nee";
 $a->strings["Add a personal note:"] = "Voeg een persoonlijke opmerking toe:";
 $a->strings["Your Identity Address:"] = "Adres van uw identiteit:";
-$a->strings["Location:"] = "Plaats:";
-$a->strings["About:"] = "Over:";
-$a->strings["Tags:"] = "Labels:";
 $a->strings["Contact added"] = "Contact toegevoegd";
 $a->strings["Unable to locate original post."] = "Ik kan de originele post niet meer vinden.";
 $a->strings["Empty post discarded."] = "Lege post weggegooid.";
@@ -229,6 +228,7 @@ $a->strings["Group removed."] = "Groep verwijderd.";
 $a->strings["Unable to remove group."] = "Niet in staat om groep te verwijderen.";
 $a->strings["Group Editor"] = "Groepsbewerker";
 $a->strings["Members"] = "Leden";
+$a->strings["Group is empty"] = "De groep is leeg";
 $a->strings["You must be logged in to use addons. "] = "Je moet ingelogd zijn om deze addons te kunnen gebruiken. ";
 $a->strings["Applications"] = "Toepassingen";
 $a->strings["No installed applications."] = "Geen toepassingen geïnstalleerd";
@@ -287,17 +287,10 @@ $a->strings["Forgot your Password?"] = "Wachtwoord vergeten?";
 $a->strings["Enter your email address and submit to have your password reset. Then check your email for further instructions."] = "Voer je e-mailadres in en verstuur het om je wachtwoord opnieuw in te stellen. Kijk dan je e-mail na voor verdere instructies.";
 $a->strings["Nickname or Email: "] = "Bijnaam of e-mail:";
 $a->strings["Reset"] = "Opnieuw";
-$a->strings["event"] = "gebeurtenis";
-$a->strings["%1\$s likes %2\$s's %3\$s"] = "%1\$s vindt het %3\$s van %2\$s leuk";
-$a->strings["%1\$s doesn't like %2\$s's %3\$s"] = "%1\$s vindt het %3\$s van %2\$s niet leuk";
-$a->strings["%1\$s is attending %2\$s's %3\$s"] = "";
-$a->strings["%1\$s is not attending %2\$s's %3\$s"] = "";
-$a->strings["%1\$s may attend %2\$s's %3\$s"] = "";
 $a->strings["{0} wants to be your friend"] = "{0} wilt je vriend worden";
 $a->strings["{0} sent you a message"] = "{0} stuurde jou een bericht";
 $a->strings["{0} requested registration"] = "{0} vroeg om zich te registreren";
 $a->strings["No contacts."] = "Geen contacten.";
-$a->strings["View Contacts"] = "Bekijk contacten";
 $a->strings["Invalid request identifier."] = "Ongeldige <em>request identifier</em>.";
 $a->strings["Discard"] = "Verwerpen";
 $a->strings["System"] = "Systeem";
@@ -393,7 +386,6 @@ $a->strings["Please use your browser 'Back' button <strong>now</strong> if you a
 $a->strings["No mirroring"] = "";
 $a->strings["Mirror as forwarded posting"] = "";
 $a->strings["Mirror as my own posting"] = "";
-$a->strings["Repair Contact Settings"] = "Contactinstellingen herstellen";
 $a->strings["Return to contact editor"] = "Ga terug naar contactbewerker";
 $a->strings["Refetch contact data"] = "";
 $a->strings["Name"] = "Naam";
@@ -424,16 +416,22 @@ $a->strings["Site"] = "Website";
 $a->strings["Users"] = "Gebruiker";
 $a->strings["Plugins"] = "Plugins";
 $a->strings["Themes"] = "Thema's";
+$a->strings["Additional features"] = "Extra functies";
 $a->strings["DB updates"] = "DB aanpassingen";
 $a->strings["Inspect Queue"] = "";
+$a->strings["Federation Statistics"] = "";
 $a->strings["Logs"] = "Logs";
+$a->strings["View Logs"] = "";
 $a->strings["probe address"] = "";
 $a->strings["check webfinger"] = "";
 $a->strings["Admin"] = "Beheer";
 $a->strings["Plugin Features"] = "Plugin Functies";
 $a->strings["diagnostics"] = "";
 $a->strings["User registrations waiting for confirmation"] = "Gebruikersregistraties wachten op bevestiging";
+$a->strings["This page offers you some numbers to the known part of the federated social network your Friendica node is part of. These numbers are not complete but only reflect the part of the network your node is aware of."] = "";
+$a->strings["The <em>Auto Discovered Contact Directory</em> feature is not enabled, it will improve the data displayed here."] = "";
 $a->strings["Administration"] = "Beheer";
+$a->strings["Currently this node is aware of nodes from the following platforms:"] = "";
 $a->strings["ID"] = "ID";
 $a->strings["Recipient Name"] = "";
 $a->strings["Recipient Profile"] = "";
@@ -582,6 +580,10 @@ $a->strings["Maximum Load Average"] = "Maximum gemiddelde belasting";
 $a->strings["Maximum system load before delivery and poll processes are deferred - default 50."] = "Maximum systeembelasting voordat aflever- en poll-processen uitgesteld worden - standaard 50.";
 $a->strings["Maximum Load Average (Frontend)"] = "";
 $a->strings["Maximum system load before the frontend quits service - default 50."] = "";
+$a->strings["Maximum table size for optimization"] = "";
+$a->strings["Maximum table size (in MB) for the automatic optimization - default 100 MB. Enter -1 to disable it."] = "";
+$a->strings["Minimum level of fragmentation"] = "";
+$a->strings["Minimum fragmenation level to start the automatic optimization - default value is 30%."] = "";
 $a->strings["Periodical check of global contacts"] = "";
 $a->strings["If enabled, the global contacts are checked periodically for missing or outdated data and the vitality of the contacts and servers."] = "";
 $a->strings["Days between requery"] = "";
@@ -681,9 +683,11 @@ $a->strings["Toggle"] = "Schakelaar";
 $a->strings["Author: "] = "Auteur:";
 $a->strings["Maintainer: "] = "Onderhoud:";
 $a->strings["Reload active plugins"] = "";
+$a->strings["There are currently no plugins available on your node. You can find the official plugin repository at %1\$s and might find other interesting plugins in the open plugin registry at %2\$s"] = "";
 $a->strings["No themes found."] = "Geen thema's gevonden.";
 $a->strings["Screenshot"] = "Schermafdruk";
 $a->strings["Reload active themes"] = "";
+$a->strings["No themes found on the system. They should be paced in %1\$s"] = "";
 $a->strings["[Experimental]"] = "[Experimenteel]";
 $a->strings["[Unsupported]"] = "[Niet ondersteund]";
 $a->strings["Log settings updated."] = "Log instellingen gewijzigd";
@@ -692,11 +696,12 @@ $a->strings["Enable Debugging"] = "";
 $a->strings["Log file"] = "Logbestand";
 $a->strings["Must be writable by web server. Relative to your Friendica top-level directory."] = "De webserver moet hier kunnen schrijven. Relatief t.o.v. van de hoogste folder binnen uw Friendica-installatie.";
 $a->strings["Log level"] = "Log niveau";
-$a->strings["Close"] = "Afsluiten";
-$a->strings["FTP Host"] = "FTP Server";
-$a->strings["FTP Path"] = "FTP Pad";
-$a->strings["FTP User"] = "FTP Gebruiker";
-$a->strings["FTP Password"] = "FTP wachtwoord";
+$a->strings["PHP logging"] = "";
+$a->strings["To enable logging of PHP errors and warnings you can add the following to the .htconfig.php file of your installation. The filename set in the 'error_log' line is relative to the friendica top-level directory and must be writeable by the web server. The option '1' for 'log_errors' and 'display_errors' is to enable these options, set to '0' to disable them."] = "";
+$a->strings["Off"] = "Uit";
+$a->strings["On"] = "Aan";
+$a->strings["Lock feature %s"] = "";
+$a->strings["Manage Additional Features"] = "";
 $a->strings["Search Results For: %s"] = "";
 $a->strings["Remove term"] = "Verwijder zoekterm";
 $a->strings["Saved Searches"] = "Opgeslagen zoekopdrachten";
@@ -718,14 +723,10 @@ $a->strings["Warning: This group contains %s member from an insecure network."]
 );
 $a->strings["Private messages to this group are at risk of public disclosure."] = "Privéberichten naar deze groep kunnen openbaar gemaakt worden.";
 $a->strings["No such group"] = "Zo'n groep bestaat niet";
-$a->strings["Group is empty"] = "De groep is leeg";
 $a->strings["Group: %s"] = "";
-$a->strings["Contact: %s"] = "";
 $a->strings["Private messages to this person are at risk of public disclosure."] = "Privéberichten naar deze persoon kunnen openbaar gemaakt worden.";
 $a->strings["Invalid contact."] = "Ongeldig contact.";
 $a->strings["No friends to display."] = "Geen vrienden om te laten zien.";
-$a->strings["Forum"] = "";
-$a->strings["Friends of %s"] = "Vrienden van %s";
 $a->strings["Event can not end before it has started."] = "";
 $a->strings["Event title and start time are required."] = "Titel en begintijd van de gebeurtenis zijn vereist.";
 $a->strings["Sun"] = "";
@@ -922,7 +923,6 @@ $a->strings["Not available."] = "Niet beschikbaar";
 $a->strings["Community"] = "Website";
 $a->strings["No results."] = "Geen resultaten.";
 $a->strings["everybody"] = "iedereen";
-$a->strings["Additional features"] = "Extra functies";
 $a->strings["Display"] = "Weergave";
 $a->strings["Social Networks"] = "Sociale netwerken";
 $a->strings["Delegations"] = "";
@@ -959,8 +959,6 @@ $a->strings["No name"] = "Geen naam";
 $a->strings["Remove authorization"] = "Verwijder authorisatie";
 $a->strings["No Plugin settings configured"] = "";
 $a->strings["Plugin Settings"] = "Plugin Instellingen";
-$a->strings["Off"] = "Uit";
-$a->strings["On"] = "Aan";
 $a->strings["Additional Features"] = "Extra functies";
 $a->strings["General Social Media Settings"] = "";
 $a->strings["Disable intelligent shortening"] = "";
@@ -995,7 +993,7 @@ $a->strings["Display Settings"] = "Scherminstellingen";
 $a->strings["Display Theme:"] = "Schermthema:";
 $a->strings["Mobile Theme:"] = "Mobiel thema:";
 $a->strings["Update browser every xx seconds"] = "Browser elke xx seconden verversen";
-$a->strings["Minimum of 10 seconds, no maximum"] = "Minimum 10 seconden, geen maximum";
+$a->strings["Minimum of 10 seconds. Enter -1 to disable it."] = "";
 $a->strings["Number of items to display per page:"] = "Aantal items te tonen per pagina:";
 $a->strings["Maximum of 100 items"] = "Maximum 100 items";
 $a->strings["Number of items to display per page when viewed from mobile device:"] = "Aantal items per pagina als je een mobiel toestel gebruikt:";
@@ -1107,12 +1105,12 @@ $a->strings["Friends are advised to please try again in 24 hours."] = "Wij advis
 $a->strings["Invalid locator"] = "Ongeldige plaatsbepaler";
 $a->strings["Invalid email address."] = "Geen geldig e-mailadres";
 $a->strings["This account has not been configured for email. Request failed."] = "Aanvraag mislukt. Dit account is niet geconfigureerd voor e-mail.";
-$a->strings["Unable to resolve your name at the provided location."] = "Ik kan jouw naam op het opgegeven adres niet vinden.";
 $a->strings["You have already introduced yourself here."] = "Je hebt jezelf hier al voorgesteld.";
 $a->strings["Apparently you are already friends with %s."] = "Blijkbaar bent u al bevriend met %s.";
 $a->strings["Invalid profile URL."] = "Ongeldig profiel adres.";
 $a->strings["Disallowed profile URL."] = "Niet toegelaten profiel adres.";
 $a->strings["Your introduction has been sent."] = "Je verzoek is verzonden.";
+$a->strings["Remote subscription can't be done for your network. Please subscribe directly on your system."] = "";
 $a->strings["Please login to confirm introduction."] = "Log in om je verzoek te bevestigen.";
 $a->strings["Incorrect identity currently logged in. Please login to <strong>this</strong> profile."] = "Je huidige identiteit is niet de juiste. Log met <strong>dit</strong> profiel in.";
 $a->strings["Confirm"] = "Bevestig";
@@ -1128,6 +1126,7 @@ $a->strings["StatusNet/Federated Social Web"] = "StatusNet/Gefedereerde Sociale
 $a->strings[" - please do not use this form.  Instead, enter %s into your Diaspora search bar."] = "- Gebruik niet dit formulier. Vul %s in in je Diaspora zoekbalk.";
 $a->strings["Registration successful. Please check your email for further instructions."] = "Registratie geslaagd. Kijk je e-mail na voor verdere instructies.";
 $a->strings["Failed to send email message. Here your accout details:<br> login: %s<br> password: %s<br><br>You can change your password after login."] = "";
+$a->strings["Registration successful."] = "";
 $a->strings["Your registration can not be processed."] = "Je registratie kan niet verwerkt worden.";
 $a->strings["Your registration is pending approval by the site owner."] = "Jouw registratie wacht op goedkeuring van de beheerder.";
 $a->strings["This site has exceeded the number of allowed daily account registrations. Please try again tomorrow."] = "Deze website heeft het toegelaten dagelijkse aantal registraties overschreden. Probeer morgen opnieuw.";
@@ -1152,8 +1151,6 @@ $a->strings["Only one search per minute is permitted for not logged in users."]
 $a->strings["Search"] = "Zoeken";
 $a->strings["Items tagged with: %s"] = "";
 $a->strings["Search results for: %s"] = "";
-$a->strings["Age: "] = "Leeftijd:";
-$a->strings["Gender: "] = "Geslacht:";
 $a->strings["Status:"] = "Tijdlijn:";
 $a->strings["Homepage:"] = "Website:";
 $a->strings["Global Directory"] = "Globale gids";
@@ -1170,7 +1167,6 @@ $a->strings["Potential Delegates"] = "Mogelijke personen waaraan het paginabehee
 $a->strings["Add"] = "Toevoegen";
 $a->strings["No entries."] = "Geen gegevens.";
 $a->strings["No contacts in common."] = "Geen gedeelde contacten.";
-$a->strings["Common Friends"] = "Gedeelde Vrienden";
 $a->strings["Export account"] = "Account exporteren";
 $a->strings["Export your account info and contacts. Use this to make a backup of your account and/or to move it to another server."] = "Je account informatie en contacten exporteren. Gebruik dit om een backup van je account te maken en/of om het te verhuizen naar een andere server.";
 $a->strings["Export all"] = "Alles exporteren";
@@ -1257,6 +1253,7 @@ $a->strings["Love/romance"] = "Liefde/romance";
 $a->strings["Work/employment"] = "Werk";
 $a->strings["School/education"] = "School/opleiding";
 $a->strings["This is your <strong>public</strong> profile.<br />It <strong>may</strong> be visible to anybody using the internet."] = "Dit is jouw <strong>publiek</strong> profiel.<br />Het <strong>kan</strong> zichtbaar zijn voor iedereen op het internet.";
+$a->strings["Age: "] = "Leeftijd:";
 $a->strings["Edit/Manage Profiles"] = "Wijzig/Beheer Profielen";
 $a->strings["Change profile photo"] = "Profiel foto wijzigen";
 $a->strings["Create New Profile"] = "Maak nieuw profiel";
@@ -1310,7 +1307,7 @@ $a->strings["poke, prod or do other things to somebody"] = "aanstoten, porren of
 $a->strings["Recipient"] = "Ontvanger";
 $a->strings["Choose what you wish to do to recipient"] = "Kies wat je met de ontvanger wil doen";
 $a->strings["Make this post private"] = "Dit bericht privé maken";
-$a->strings["Resubsribing to OStatus contacts"] = "";
+$a->strings["Resubscribing to OStatus contacts"] = "";
 $a->strings["Error"] = "";
 $a->strings["Total invitation limit exceeded."] = "Totale uitnodigingslimiet overschreden.";
 $a->strings["%s : Not a valid email address."] = "%s: Geen geldig e-mailadres.";
@@ -1443,6 +1440,10 @@ $a->strings["All Networks"] = "Alle netwerken";
 $a->strings["Saved Folders"] = "Bewaarde Mappen";
 $a->strings["Everything"] = "Alles";
 $a->strings["Categories"] = "Categorieën";
+$a->strings["%d contact in common"] = array(
+       0 => "%d gedeeld contact",
+       1 => "%d gedeelde contacten",
+);
 $a->strings["General Features"] = "Algemene functies";
 $a->strings["Multiple Profiles"] = "Meerdere profielen";
 $a->strings["Ability to create multiple profiles"] = "Mogelijkheid om meerdere profielen aan te maken";
@@ -1458,6 +1459,8 @@ $a->strings["Add/remove mention when a fourm page is selected/deselected in ACL
 $a->strings["Network Sidebar Widgets"] = "Zijbalkwidgets op netwerkpagina";
 $a->strings["Search by Date"] = "Zoeken op datum";
 $a->strings["Ability to select posts by date ranges"] = "Mogelijkheid om berichten te selecteren volgens datumbereik";
+$a->strings["List Forums"] = "";
+$a->strings["Enable widget to display the forums your are connected with"] = "";
 $a->strings["Group Filter"] = "Groepsfilter";
 $a->strings["Enable widget to display Network posts only from selected group"] = "Sta de widget toe om netwerkberichten te tonen van bepaalde groepen";
 $a->strings["Network Filter"] = "Netwerkfilter";
@@ -1486,6 +1489,8 @@ $a->strings["Star Posts"] = "Geef berichten een ster";
 $a->strings["Ability to mark special posts with a star indicator"] = "";
 $a->strings["Mute Post Notifications"] = "";
 $a->strings["Ability to mute notifications for a thread"] = "";
+$a->strings["Advanced Profile Settings"] = "";
+$a->strings["Show visitors public community forums at the Advanced Profile Page"] = "";
 $a->strings["Connect URL missing."] = "";
 $a->strings["This site is not configured to allow communications with other networks."] = "Deze website is niet geconfigureerd voor communicatie met andere netwerken.";
 $a->strings["No compatible communication protocols or feeds were discovered."] = "Er werden geen compatibele communicatieprotocols of feeds ontdekt.";
@@ -1502,6 +1507,7 @@ $a->strings["A deleted group with this name was revived. Existing item permissio
 $a->strings["Default privacy group for new contacts"] = "";
 $a->strings["Everybody"] = "Iedereen";
 $a->strings["edit"] = "verander";
+$a->strings["Edit groups"] = "";
 $a->strings["Edit group"] = "Verander groep";
 $a->strings["Create a new group"] = "Maak nieuwe groep";
 $a->strings["Contacts not in any group"] = "";
@@ -1525,10 +1531,10 @@ $a->strings["%s's birthday"] = "%s's verjaardag";
 $a->strings["Happy Birthday %s"] = "Gefeliciteerd %s";
 $a->strings["Requested account is not available."] = "Gevraagde account is niet beschikbaar.";
 $a->strings["Edit profile"] = "Bewerk profiel";
+$a->strings["Atom feed"] = "";
 $a->strings["Message"] = "Bericht";
 $a->strings["Profiles"] = "Profielen";
 $a->strings["Manage/edit profiles"] = "Beheer/wijzig profielen";
-$a->strings["Network:"] = "";
 $a->strings["g A l F d"] = "G l j F";
 $a->strings["F d"] = "d F";
 $a->strings["[today]"] = "[vandaag]";
@@ -1552,17 +1558,22 @@ $a->strings["Film/dance/culture/entertainment:"] = "Film/dans/cultuur/ontspannin
 $a->strings["Love/Romance:"] = "Liefde/romance:";
 $a->strings["Work/employment:"] = "Werk/beroep:";
 $a->strings["School/education:"] = "School/opleiding:";
-$a->strings["Status"] = "Tijdlijn";
-$a->strings["Status Messages and Posts"] = "Berichten op jouw tijdlijn";
-$a->strings["Profile Details"] = "Profieldetails";
+$a->strings["Forums:"] = "";
 $a->strings["Videos"] = "Video's";
 $a->strings["Events and Calendar"] = "Gebeurtenissen en kalender";
 $a->strings["Only You Can See This"] = "Alleen jij kunt dit zien";
+$a->strings["event"] = "gebeurtenis";
+$a->strings["%1\$s likes %2\$s's %3\$s"] = "%1\$s vindt het %3\$s van %2\$s leuk";
+$a->strings["%1\$s doesn't like %2\$s's %3\$s"] = "%1\$s vindt het %3\$s van %2\$s niet leuk";
+$a->strings["%1\$s is attending %2\$s's %3\$s"] = "";
+$a->strings["%1\$s is not attending %2\$s's %3\$s"] = "";
+$a->strings["%1\$s may attend %2\$s's %3\$s"] = "";
 $a->strings["Post to Email"] = "Verzenden per e-mail";
 $a->strings["Connectors disabled, since \"%s\" is enabled."] = "";
 $a->strings["Visible to everybody"] = "Zichtbaar voor iedereen";
 $a->strings["show"] = "tonen";
 $a->strings["don't show"] = "niet tonen";
+$a->strings["Close"] = "Afsluiten";
 $a->strings["[no subject]"] = "[geen onderwerp]";
 $a->strings["stopped following"] = "";
 $a->strings["View Status"] = "Bekijk status";
@@ -1629,6 +1640,8 @@ $a->strings["Undecided"] = array(
        0 => "",
        1 => "",
 );
+$a->strings["Forums"] = "";
+$a->strings["External link to forum"] = "";
 $a->strings["view full size"] = "Volledig formaat";
 $a->strings["newer"] = "nieuwere berichten";
 $a->strings["older"] = "oudere berichten";
@@ -1643,9 +1656,9 @@ $a->strings["%d Contact"] = array(
        0 => "%d contact",
        1 => "%d contacten",
 );
+$a->strings["View Contacts"] = "Bekijk contacten";
 $a->strings["Full Text"] = "";
 $a->strings["Tags"] = "";
-$a->strings["Forums"] = "";
 $a->strings["poke"] = "aanstoten";
 $a->strings["poked"] = "aangestoten";
 $a->strings["ping"] = "ping";
@@ -1690,8 +1703,6 @@ $a->strings["<a href=\"%1\$s\" target=\"_blank\">%2\$s</a> %3\$s"] = "";
 $a->strings["<span><a href=\"%s\" target=\"_blank\">%s</a> wrote the following <a href=\"%s\" target=\"_blank\">post</a>"] = "";
 $a->strings["$1 wrote:"] = "$1 schreef:";
 $a->strings["Encrypted content"] = "Versleutelde inhoud";
-$a->strings["(no subject)"] = "(geen onderwerp)";
-$a->strings["noreply"] = "geen reactie";
 $a->strings["Cannot locate DNS info for database server '%s'"] = "";
 $a->strings["Unknown | Not categorised"] = "Onbekend | Niet ";
 $a->strings["Block immediately"] = "Onmiddellijk blokkeren";
@@ -1703,6 +1714,7 @@ $a->strings["Weekly"] = "wekelijks";
 $a->strings["Monthly"] = "maandelijks";
 $a->strings["OStatus"] = "OStatus";
 $a->strings["RSS/Atom"] = "RSS/Atom";
+$a->strings["Facebook"] = "Facebook";
 $a->strings["Zot!"] = "Zot!";
 $a->strings["LinkedIn"] = "Linkedln";
 $a->strings["XMPP/IM"] = "XMPP/IM";
@@ -1758,15 +1770,9 @@ $a->strings["Manage/edit friends and contacts"] = "Beheer/Wijzig vrienden en con
 $a->strings["Site setup and configuration"] = "Website opzetten en configureren";
 $a->strings["Navigation"] = "Navigatie";
 $a->strings["Site map"] = "Sitemap";
-$a->strings["User not found."] = "Gebruiker niet gevonden";
 $a->strings["Daily posting limit of %d posts reached. The post was rejected."] = "";
 $a->strings["Weekly posting limit of %d posts reached. The post was rejected."] = "";
 $a->strings["Monthly posting limit of %d posts reached. The post was rejected."] = "";
-$a->strings["There is no status with this id."] = "Er is geen status met dit kenmerk";
-$a->strings["There is no conversation with this id."] = "";
-$a->strings["Invalid item."] = "";
-$a->strings["Invalid action. "] = "";
-$a->strings["DB error"] = "";
 $a->strings["An invitation is required."] = "Een uitnodiging is vereist.";
 $a->strings["Invitation could not be verified."] = "Uitnodiging kon niet geverifieerd worden.";
 $a->strings["Invalid OpenID url"] = "Ongeldige OpenID url";
@@ -1789,6 +1795,8 @@ $a->strings["\n\t\tDear %1\$s,\n\t\t\tThank you for registering at %2\$s. Your a
 $a->strings["\n\t\tThe login details are as follows:\n\t\t\tSite Location:\t%3\$s\n\t\t\tLogin Name:\t%1\$s\n\t\t\tPassword:\t%5\$s\n\n\t\tYou may change your password from your account \"Settings\" page after logging\n\t\tin.\n\n\t\tPlease take a few moments to review the other account settings on that page.\n\n\t\tYou may also wish to add some basic information to your default profile\n\t\t(on the \"Profiles\" page) so that other people can easily find you.\n\n\t\tWe recommend setting your full name, adding a profile photo,\n\t\tadding some profile \"keywords\" (very useful in making new friends) - and\n\t\tperhaps what country you live in; if you do not wish to be more specific\n\t\tthan that.\n\n\t\tWe fully respect your right to privacy, and none of these items are necessary.\n\t\tIf you are new and do not know anybody here, they may help\n\t\tyou to make some new and interesting friends.\n\n\n\t\tThank you and welcome to %2\$s."] = "";
 $a->strings["Sharing notification from Diaspora network"] = "";
 $a->strings["Attachments:"] = "Bijlagen:";
+$a->strings["(no subject)"] = "(geen onderwerp)";
+$a->strings["noreply"] = "geen reactie";
 $a->strings["Do you really want to delete this item?"] = "Wil je echt dit item verwijderen?";
 $a->strings["Archives"] = "Archieven";
 $a->strings["Male"] = "Man";
@@ -1951,7 +1959,6 @@ $a->strings["Set zoomfactor for Earth Layers"] = "";
 $a->strings["Show/hide boxes at right-hand column:"] = "";
 $a->strings["Comma separated list of helper forums"] = "";
 $a->strings["Set style"] = "";
-$a->strings["External link to forum"] = "";
 $a->strings["Quick Start"] = "";
 $a->strings["greenzero"] = "";
 $a->strings["purplezero"] = "";
index bf9470b64efa65e3165a69512654016b14d5b7f4..7c68a73064ee065113c9ec507283df026b5cee65 100644 (file)
@@ -29,7 +29,8 @@ $(document).ready(function() {
                acl = new ACL(
                        baseurl+"/acl",
                        [ {{$allowcid}},{{$allowgid}},{{$denycid}},{{$denygid}} ],
-                       {{$features.aclautomention}}
+                       {{$features.aclautomention}},
+                       {{if $APP->is_mobile}}true{{else}}false{{/if}}
                );
        }
 });
index 4a6af88148266307ad6e32d6d39af5436019a9c3..5c9cce0d0c58c408f8e7f62af9306ec3779926ee 100644 (file)
                });
        });
 </script>
+
 <h4><a href="{{$admurl}}">{{$admtxt}}</a></h4>
 <ul class='admin linklist'>
-       <li class='admin link button {{$admin.site.2}}'><a href='{{$admin.site.0}}'>{{$admin.site.1}}</a></li>
-       <li class='admin link button {{$admin.users.2}}'><a href='{{$admin.users.0}}'>{{$admin.users.1}}</a><span id='pending-update' title='{{$h_pending}}'></span></li>
-       <li class='admin link button {{$admin.plugins.2}}'><a href='{{$admin.plugins.0}}'>{{$admin.plugins.1}}</a></li>
-       <li class='admin link button {{$admin.themes.2}}'><a href='{{$admin.themes.0}}'>{{$admin.themes.1}}</a></li>
-       <li class='admin link button {{$admin.dbsync.2}}'><a href='{{$admin.dbsync.0}}'>{{$admin.dbsync.1}}</a></li>
-       <li class='admin link button {{$admin.queue.2}}'><a href='{{$admin.queue.0}}'>{{$admin.queue.1}}</a></li>
+{{foreach $subpages as $page}}
+       <li class='admin link button {{$page.2}}'><a href='{{$page.0}}'>{{$page.1}}</a></li>
+{{/foreach}}
 </ul>
 
 {{if $admin.update}}
@@ -39,6 +37,7 @@
 <h4>{{$logtxt}}</h4>
 <ul class='admin linklist'>
        <li class='admin link button {{$admin.logs.2}}'><a href='{{$admin.logs.0}}'>{{$admin.logs.1}}</a></li>
+       <li class='admin link button {{$admin.viewlogs.2}}'><a href='{{$admin.viewlogs.0}}'>{{$admin.viewlogs.1}}</a></li>
 </ul>
 
 <h4>{{$diagnosticstxt}}</h4>
diff --git a/view/templates/admin_federation.tpl b/view/templates/admin_federation.tpl
new file mode 100644 (file)
index 0000000..fad87da
--- /dev/null
@@ -0,0 +1,58 @@
+<script src="{{$baseurl}}/library/Chart.js-1.0.2/Chart.min.js"></script>
+<canvas id="FederationChart" class="federation-graph"></canvas>
+<div id="adminpage">
+    <h1>{{$title}} - {{$page}}</h1>
+    <p>{{$intro}}</p>
+    {{if not $autoactive}}
+       <p class="error-message">{{$hint}}</p>
+    {{/if}}
+    <p>{{$legendtext}}
+    <ul>
+    {{foreach $counts as $c}}
+       {{if $c[0]['total'] > 0}}
+           <li>{{$c[0]['platform']}} ({{$c[0]['total']}})</li>
+       {{/if}}
+    {{/foreach}}
+    </ul>
+    </p>
+</div>
+<script>
+var FedData = [
+{{foreach $counts as $c}}
+    { value: {{$c[0]['total']}}, label: "{{$c[0]['platform']}}", color: "#90EE90", highlight: "#EE90A1", },
+{{/foreach}}
+];
+var ctx = document.getElementById("FederationChart").getContext("2d");
+var myDoughnutChart = new Chart(ctx).Doughnut(FedData, { animateRotate : false, });
+</script>
+
+<table id="federation-stats">
+{{foreach $counts as $c}}
+    {{if $c[0]['total'] > 0}}
+    <tr>
+           <th>{{$c[0]['platform']}}</th>
+           <th><strong>{{$c[0]['total']}}</strong></td>
+           <td>{{$c[0]['network']}}</td>
+    </tr>
+    <tr>
+    <td colspan="3" class="federation-data">
+    <canvas id="{{$c[2]}}Chart" class="federation-network-graph"></canvas>
+    <script>
+    var {{$c[2]}}data = [
+    {{foreach $c[1] as $v}}
+       { value: {{$v['total']}}, label: '{{$v['version']}}', color: "#90EE90", highlight: "#EE90A1",},
+    {{/foreach}}
+    ];
+    var ctx = document.getElementById("{{$c[2]}}Chart").getContext("2d");
+    var my{{$c[2]}}DoughnutChart = new Chart(ctx).Doughnut({{$c[2]}}data, {animateRotate : false,});
+    </script>
+    <ul class="federation-stats">
+    {{foreach $c[1] as $v}}
+       <li>{{if ($c[0]['platform']==='Friendica' and  $version===$v['version']) }}<span class="version-match">{{$v['version']}}</span>{{else}}{{$v['version']}}{{/if}} ({{$v['total']}})</li>
+    {{/foreach}}
+    </ul>
+    </td>
+    </tr>
+    {{/if}}
+{{/foreach}}
+</table>
index 4cc0acb66c346d1d5cb373ef05439c4fc527b0f1..e79c7d7af726ce7b52388c4ba7f4f35b2065de2d 100644 (file)
@@ -1,19 +1,21 @@
 <div id='adminpage'>
-       <h1>{{$title}} - {{$page}}</h1>
+    <h1>{{$title}} - {{$page}}</h1>
        
        <form action="{{$baseurl}}/admin/logs" method="post">
-    <input type='hidden' name='form_security_token' value="{{$form_security_token|escape:'html'}}">
+           <input type='hidden' name='form_security_token' value="{{$form_security_token|escape:'html'}}">
 
-       {{include file="field_checkbox.tpl" field=$debugging}}
-       {{include file="field_input.tpl" field=$logfile}}
-       {{include file="field_select.tpl" field=$loglevel}}
+           {{include file="field_checkbox.tpl" field=$debugging}}
+           {{include file="field_input.tpl" field=$logfile}}
+           {{include file="field_select.tpl" field=$loglevel}}
        
-       <div class="submit"><input type="submit" name="page_logs" value="{{$submit|escape:'html'}}" /></div>
+           <div class="submit"><input type="submit" name="page_logs" value="{{$submit|escape:'html'}}" /></div>
        
        </form>
+
+       <h2>{{$phpheader}}</h2>
+       <div>
+               <p>{{$phphint}}</p>
+               <pre>{{$phplogcode}}</pre>
+       </div>
        
-       <h3>{{$logname}}</h3>
-       <div style="width:100%; height:400px; overflow: auto; "><pre>{{$data}}</pre></div>
-<!--   <iframe src='{{$baseurl}}/{{$logname}}' style="width:100%; height:400px"></iframe> -->
-       <!-- <div class="submit"><input type="submit" name="page_logs_clear_log" value="{{$clear}}" /></div> -->
 </div>
index 6caa04a3a42dfc2228b2980294aac56459daec1c..75565dd2edc71797e00a3eb08edaf3ea35b3d917 100644 (file)
@@ -1,6 +1,11 @@
 
 <div id='adminpage'>
        <h1>{{$title}} - {{$page}}</h1>
+               {{if $pcount eq 0}}
+                   <div class="error-message">
+                   {{$noplugshint}}
+                   </div>
+               {{else}}
                <a class="btn" href="{{$baseurl}}/admin/{{$function}}?a=r&amp;t={{$form_security_token}}">{{$reload}}</a>
                <ul id='pluginslist'>
                {{foreach $plugins as $p}}
@@ -13,4 +18,5 @@
                        </li>
                {{/foreach}}
                </ul>
+               {{/if}}
 </div>
diff --git a/view/templates/admin_remoteupdate.tpl b/view/templates/admin_remoteupdate.tpl
deleted file mode 100644 (file)
index c3e85f2..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-
-<script src="js/jquery.htmlstream.js"></script>
-<script>
-       /* ajax updater */
-       function updateEnd(data){
-               //$("#updatepopup .panel_text").html(data);
-               $("#remoteupdate_form").find("input").removeAttr('disabled');
-               $(".panel_action_close").fadeIn()       
-       }
-       function updateOn(data){
-               
-               var patt=/§([^§]*)§/g; 
-               var matches = data.match(patt);
-               $(matches).each(function(id,data){
-                       data = data.replace(/§/g,"");
-                       d = data.split("@");
-                       console.log(d);
-                       elm = $("#updatepopup .panel_text #"+d[0]);
-                       html = "<div id='"+d[0]+"' class='progress'>"+d[1]+"<span>"+d[2]+"</span></div>";
-                       if (elm.length==0){
-                               $("#updatepopup .panel_text").append(html);
-                       } else {
-                               $(elm).replaceWith(html);
-                       }
-               });
-               
-               
-       }
-       
-       $(function(){
-               $("#remoteupdate_form").submit(function(){
-                       var data={};
-                       $(this).find("input").each(function(i, e){
-                               name = $(e).attr('name');
-                               value = $(e).val();
-                               e.disabled = true;
-                               data[name]=value;
-                       });
-
-                       $("#updatepopup .panel_text").html("");
-                       $("#updatepopup").show();
-                       $("#updatepopup .panel").hide().slideDown(500);
-                       $(".panel_action_close").hide().click(function(){
-                               $("#updatepopup .panel").slideUp(500, function(){
-                                       $("#updatepopup").hide();
-                               });                             
-                       });
-
-                       $.post(
-                               $(this).attr('action'), 
-                               data, 
-                               updateEnd,
-                               'text',
-                               updateOn
-                       );
-
-                       
-                       return false;
-               })
-       });
-</script>
-<div id="updatepopup" class="popup">
-       <div class="background"></div>
-       <div class="panel">
-               <div class="panel_in">
-                       <h1>Friendica Update</h1>
-                       <div class="panel_text"></div>
-                       <div class="panel_actions">
-                               <input type="button" value="{{$close|escape:'html'}}" class="panel_action_close">
-                       </div>
-               </div>
-       </div>
-</div>
-<div id="adminpage">
-       <dl> <dt>Your version:</dt><dd>{{$localversion}}</dd> </dl>
-{{if $needupdate}}
-       <dl> <dt>New version:</dt><dd>{{$remoteversion}}</dd> </dl>
-
-       <form id="remoteupdate_form" method="POST" action="{{$baseurl}}/admin/update">
-       <input type="hidden" name="{{$remotefile.0}}" value="{{$remotefile.2|escape:'html'}}">
-
-       {{if $canwrite}}
-               <div class="submit"><input type="submit" name="remoteupdate" value="{{$submit|escape:'html'}}" /></div>
-       {{else}}
-               <h3>Your friendica installation is not writable by web server.</h3>
-               {{if $canftp}}
-                       <p>You can try to update via FTP</p>
-                       {{include file="field_input.tpl" field=$ftphost}}
-                       {{include file="field_input.tpl" field=$ftppath}}
-                       {{include file="field_input.tpl" field=$ftpuser}}
-                       {{include file="field_password.tpl" field=$ftppwd}}
-                       <div class="submit"><input type="submit" name="remoteupdate" value="{{$submit|escape:'html'}}" /></div>
-               {{/if}}
-       {{/if}}
-       </form>
-{{else}}
-<h4>No updates</h4>
-{{/if}}
-</div>
diff --git a/view/templates/admin_settings_features.tpl b/view/templates/admin_settings_features.tpl
new file mode 100644 (file)
index 0000000..abcc527
--- /dev/null
@@ -0,0 +1,21 @@
+<h1>{{$title}}</h1>
+
+<form action="admin/features" method="post" autocomplete="off">
+<input type='hidden' name='form_security_token' value='{{$form_security_token}}'>
+
+{{foreach $features as $g => $f}}
+<h3 class="settings-heading"><a href="javascript:;">{{$f.0}}</a></h3>
+
+<div class="settings-content-block">
+       {{foreach $f.1 as $fcat}}
+               {{include file="field_yesno.tpl" field=$fcat.0}}
+               {{include file="field_yesno.tpl" field=$fcat.1}}
+       {{/foreach}}
+
+       <div class="settings-submit-wrapper" >
+               <input type="submit" name="submit" class="settings-features-submit" value="{{$submit|escape:'html'}}" />
+       </div>
+</div>
+{{/foreach}}
+
+</form>
diff --git a/view/templates/admin_settings_head.tpl b/view/templates/admin_settings_head.tpl
new file mode 100644 (file)
index 0000000..25c0f80
--- /dev/null
@@ -0,0 +1,9 @@
+<script>
+       $(document).ready(function() {
+               $('.settings-content-block').hide();
+               $('.settings-heading').click(function(){
+                       $('.settings-content-block').hide();
+                       $(this).next('.settings-content-block').toggle();
+               });
+       });
+</script>
diff --git a/view/templates/admin_viewlogs.tpl b/view/templates/admin_viewlogs.tpl
new file mode 100644 (file)
index 0000000..c80264c
--- /dev/null
@@ -0,0 +1,6 @@
+<div id='adminpage'>
+       <h1>{{$title}} - {{$page}}</h1>
+       
+       <h3>{{$logname}}</h3>
+       <div style="width:100%; height:400px; overflow: auto; "><pre>{{$data}}</pre></div>
+</div>
diff --git a/view/templates/atom_feed.tpl b/view/templates/atom_feed.tpl
deleted file mode 100644 (file)
index 7070f4c..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-
-<?xml version="1.0" encoding="utf-8" ?>
-<feed xmlns="http://www.w3.org/2005/Atom"
-      xmlns:thr="http://purl.org/syndication/thread/1.0"
-      xmlns:at="http://purl.org/atompub/tombstones/1.0"
-      xmlns:media="http://purl.org/syndication/atommedia"
-      xmlns:dfrn="http://purl.org/macgirvin/dfrn/1.0" 
-      xmlns:as="http://activitystrea.ms/spec/1.0/"
-      xmlns:georss="http://www.georss.org/georss" 
-      xmlns:poco="http://portablecontacts.net/spec/1.0" 
-      xmlns:ostatus="http://ostatus.org/schema/1.0" 
-         xmlns:statusnet="http://status.net/schema/api/1/" > 
-
-  <id>{{$feed_id}}</id>
-  <title>{{$feed_title}}</title>
-  <generator uri="http://friendica.com" version="{{$version}}">Friendica</generator>
-  <link rel="license" href="http://creativecommons.org/licenses/by/3.0/" />
-  <link rel="alternate" type="text/html" href="{{$alternatelink}}" />
-  {{$hub}}
-  {{$salmon}}
-  {{$community}}
-
-  <updated>{{$feed_updated}}</updated>
-
-  <dfrn:owner>
-    <name dfrn:updated="{{$namdate}}" >{{$name}}</name>
-    <uri dfrn:updated="{{$uridate}}" >{{$profile_page}}</uri>
-    <link rel="photo"  type="image/jpeg" dfrn:updated="{{$picdate}}" media:width="175" media:height="175" href="{{$photo}}" />
-    <link rel="avatar" type="image/jpeg" dfrn:updated="{{$picdate}}" media:width="175" media:height="175" href="{{$photo}}" />
-    {{$birthday}}
-  </dfrn:owner>
diff --git a/view/templates/atom_feed_dfrn.tpl b/view/templates/atom_feed_dfrn.tpl
deleted file mode 100644 (file)
index 2b1c16f..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-
-<?xml version="1.0" encoding="utf-8" ?>
-<feed xmlns="http://www.w3.org/2005/Atom"
-      xmlns:thr="http://purl.org/syndication/thread/1.0"
-      xmlns:at="http://purl.org/atompub/tombstones/1.0"
-      xmlns:media="http://purl.org/syndication/atommedia"
-      xmlns:dfrn="http://purl.org/macgirvin/dfrn/1.0" 
-      xmlns:as="http://activitystrea.ms/spec/1.0/"
-      xmlns:georss="http://www.georss.org/georss" 
-      xmlns:poco="http://portablecontacts.net/spec/1.0" 
-      xmlns:ostatus="http://ostatus.org/schema/1.0" 
-         xmlns:statusnet="http://status.net/schema/api/1/" > 
-
-  <id>{{$feed_id}}</id>
-  <title>{{$feed_title}}</title>
-  <generator uri="http://friendica.com" version="{{$version}}">Friendica</generator>
-  <link rel="license" href="http://creativecommons.org/licenses/by/3.0/" />
-  <link rel="alternate" type="text/html" href="{{$alternatelink}}" />
-  {{$hub}}
-  {{$salmon}}
-  {{$community}}
-
-  <updated>{{$feed_updated}}</updated>
-
-  <author>
-    <name dfrn:updated="{{$namdate}}" >{{$name}}</name>
-    <uri dfrn:updated="{{$uridate}}" >{{$profile_page}}</uri>
-    <link rel="photo"  type="image/jpeg" dfrn:updated="{{$picdate}}" media:width="175" media:height="175" href="{{$photo}}" />
-    <link rel="avatar" type="image/jpeg" dfrn:updated="{{$picdate}}" media:width="175" media:height="175" href="{{$photo}}" />
-    {{$birthday}}
-  </author>
diff --git a/view/templates/atom_mail.tpl b/view/templates/atom_mail.tpl
deleted file mode 100644 (file)
index e18e476..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-<dfrn:mail>
-
-       <dfrn:sender>
-               <dfrn:name>{{$name}}</dfrn:name>
-               <dfrn:uri>{{$profile_page}}</dfrn:uri>
-               <dfrn:avatar>{{$thumb}}</dfrn:avatar>
-       </dfrn:sender>
-
-       <dfrn:id>{{$item_id}}</dfrn:id>
-       <dfrn:in-reply-to>{{$parent_id}}</dfrn:in-reply-to>
-       <dfrn:sentdate>{{$created}}</dfrn:sentdate>
-       <dfrn:subject>{{$subject}}</dfrn:subject>
-       <dfrn:content>{{$content}}</dfrn:content>
-
-</dfrn:mail>
-
diff --git a/view/templates/atom_relocate.tpl b/view/templates/atom_relocate.tpl
deleted file mode 100644 (file)
index e6418fc..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-<dfrn:relocate>
-
-       <dfrn:url>{{$url}}</dfrn:url>
-       <dfrn:name>{{$name}}</dfrn:name>
-       <dfrn:photo>{{$photo}}</dfrn:photo>
-       <dfrn:thumb>{{$thumb}}</dfrn:thumb>
-       <dfrn:micro>{{$micro}}</dfrn:micro>
-       <dfrn:request>{{$request}}</dfrn:request>
-       <dfrn:confirm>{{$confirm}}</dfrn:confirm>
-       <dfrn:notify>{{$notify}}</dfrn:notify>
-       <dfrn:poll>{{$poll}}</dfrn:poll>
-       <dfrn:sitepubkey>{{$sitepubkey}}</dfrn:sitepubkey>
-
-
-</dfrn:relocate>
-
diff --git a/view/templates/atom_suggest.tpl b/view/templates/atom_suggest.tpl
deleted file mode 100644 (file)
index afc1d66..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-<dfrn:suggest>
-
-       <dfrn:url>{{$url}}</dfrn:url>
-       <dfrn:name>{{$name}}</dfrn:name>
-       <dfrn:photo>{{$photo}}</dfrn:photo>
-       <dfrn:request>{{$request}}</dfrn:request>
-       <dfrn:note>{{$note}}</dfrn:note>
-
-</dfrn:suggest>
-
index 525a5e0677f62577f80789513d9a8d057755c065..f723cb44c433836cb8f6dac068ee4524b144b2b5 100644 (file)
@@ -1,6 +1,6 @@
 
 {{$tabs}}
-<h2>{{$title}}</h2>
+{{include file="section_title.tpl"}}
 
 <div id="new-event-link"><a href="{{$new_event.0}}" >{{$new_event.1}}</a></div>
 
index 9aceb3f1cba5f602e83ef027228aec866a1ff059..2fcb1908d70b2e419036f30485ad819d12126808 100644 (file)
@@ -4,7 +4,7 @@
 <div id="event-wrapper" style="display: none;" ><div id="event-title">{{$event_title}}</div>
 <div id="event-title-end"></div>
 {{foreach $events as $event}}
-<div class="event-list" id="event-{{$event.id}}"> <a class="cboxElement" href="events/?id={{$event.id}}">{{$event.title}}</a> - {{$event.date}} </div>
+<div class="event-list" id="event-{{$event.id}}"> <a class="ajax-popupbox" href="events/?id={{$event.id}}">{{$event.title}}</a> - {{$event.date}} </div>
 {{/foreach}}
 </div>
 {{/if}}
index f2f2d3487a66d244c3c920759eea06929c1b4046..17c459c4d8a17bd90053ba2361e60963a8d545fa 100644 (file)
@@ -6,6 +6,7 @@
 <link rel="stylesheet" href="{{$baseurl}}/library/colorbox/colorbox.css" type="text/css" media="screen" />
 <link rel="stylesheet" href="{{$baseurl}}/library/jgrowl/jquery.jgrowl.css" type="text/css" media="screen" />
 <link rel="stylesheet" href="{{$baseurl}}/library/datetimepicker/jquery.datetimepicker.css" type="text/css" media="screen" />
+<link rel="stylesheet" href="{{$baseurl}}/library/perfect-scrollbar/perfect-scrollbar.min.css" type="text/css" media="screen" />
 
 <link rel="stylesheet" type="text/css" href="{{$stylesheet}}" media="all" />
 
@@ -37,6 +38,7 @@
 <script type="text/javascript" src="{{$baseurl}}/library/jgrowl/jquery.jgrowl_minimized.js"></script>
 <script type="text/javascript" src="{{$baseurl}}/library/datetimepicker/jquery.datetimepicker.js"></script>
 <script type="text/javascript" src="{{$baseurl}}/library/tinymce/jscripts/tiny_mce/tiny_mce_src.js" ></script>
+<script type="text/javascript" src="{{$baseurl}}/library/perfect-scrollbar/perfect-scrollbar.jquery.js" ></script>
 <script type="text/javascript" src="{{$baseurl}}/js/acl.js" ></script>
 <script type="text/javascript" src="{{$baseurl}}/js/webtoolkit.base64.js" ></script>
 <script type="text/javascript" src="{{$baseurl}}/js/main.js" ></script>
index 647f261c45f668f684201ec1c95c6fc3ec3d1c7b..b06f6032c30ea338f909fde7975da9309b01f30a 100644 (file)
@@ -8,16 +8,24 @@ var plaintext = '{{$editselect}}';
 
 function initEditor(cb){
        if (editor==false){
+               var  colorbox_options = {
+                       {{if $APP->is_mobile}}
+                       'width' : '100%',
+                       'height' : '100%',
+                       {{/if}}
+                       'inline' : true,
+                       'transition' : 'elastic'
+               }
+               
+               
+               
                $("#profile-jot-text-loading").show();
                if(plaintext == 'none') {
                        $("#profile-jot-text-loading").hide();
                        $("#profile-jot-text").css({ 'height': 200, 'color': '#000' });
                        $("#profile-jot-text").contact_autocomplete(baseurl+"/acl");
                        editor = true;
-                       $("a#jot-perms-icon").colorbox({
-                               'inline' : true,
-                               'transition' : 'elastic'
-                       });
+                       $("a#jot-perms-icon").colorbox(colorbox_options);
                        $(".jothidden").show();
                        if (typeof cb!="undefined") cb();
                        return;
@@ -107,10 +115,7 @@ function initEditor(cb){
                });
                editor = true;
                // setup acl popup
-               $("a#jot-perms-icon").colorbox({
-                       'inline' : true,
-                       'transition' : 'elastic'
-               });
+               $("a#jot-perms-icon").colorbox(colorbox_options);
        } else {
                if (typeof cb!="undefined") cb();
        }
index 0b7e77a07bb7b47b64f4a25db38c699a164c3220..54f9de0c7a37385abcde31d023aaf5dce03d0ef6 100644 (file)
@@ -2,7 +2,7 @@
 
 <h1>{{$notif_header}}</h1>
 
-{{include file="common_tabs.tpl"}}
+{{if $tabs }}{{include file="common_tabs.tpl"}}{{/if}}
 
 <div class="notif-network-wrapper">
        {{$notif_content}}
index 084fdf3b77dd508f7d65cb73c09756040961f6f2..32de6bfa9f2f0f2e549bc447b9cdefbea1c04283 100644 (file)
@@ -1,5 +1,5 @@
 
-<h2>{{$title}}</h2>
+{{include file="section_title.tpl"}}
 
 <dl id="aprofile-fullname" class="aprofile">
  <dt>{{$profile.fullname.0}}</dt>
index ad464760f74156ed93df0dcb311582e914612088..3a424ad5f4ca152643850483985199926bcdb763 100644 (file)
@@ -28,7 +28,9 @@
 <!--           <div class='main-content-container'>-->\r
                <div class='section-wrapper'>\r
                <?php if( ($a->module === 'settings' || $a->module === 'message' || $a->module === 'profile') && x($page,'aside')) echo $page['aside']; ?>\r
-               <section><?php if(x($page,'content')) echo $page['content']; ?>\r
+               <section>\r
+                       <?php if(x($page,'content')) echo $page['content']; ?>\r
+                       <div id="pause"></div> <!-- The pause/resume Ajax indicator -->\r
                        <div id="page-footer"></div>\r
                </section>\r
                </div>\r
@@ -41,4 +43,3 @@
        <?php if(x($page,'end')) echo $page['end']; ?>\r
 </body>\r
 </html>\r
-\r
index 95a9b1e8c7b7403e4adc1362f1c9101811c94e61..c379955f7968cdc61533d22f1469f1b7782b01f1 100644 (file)
@@ -28,7 +28,9 @@
                <!--<div class='main-content-loading'><img src="/view/theme/frost/images/ajax-loader.gif" alt="Please wait..."></div>-->\r
                <div class='main-content-container'>\r
                <aside><?php if(x($page,'aside')) echo $page['aside']; ?></aside>\r
-               <section><?php if(x($page,'content')) echo $page['content']; ?>\r
+               <section>\r
+                       <?php if(x($page,'content')) echo $page['content']; ?>\r
+                       <div id="pause"></div> <!-- The pause/resume Ajax indicator -->\r
                        <div id="page-footer"></div>\r
                </section>\r
                <right_aside><?php if(x($page,'right_aside')) echo $page['right_aside']; ?></right_aside>\r
@@ -39,4 +41,3 @@
        <?php if(x($page,'end')) echo $page['end']; ?>\r
 </body>\r
 </html>\r
-\r
index 9ac49820aad7cfd205a59deb16788f22d701d621..405e1cad34c4461b1f268bd1072a098ca6ad28c5 100644 (file)
@@ -19,7 +19,9 @@
 
        <aside><?php if(x($page,'aside')) echo $page['aside']; ?></aside>
 
-       <section><?php if(x($page,'content')) echo $page['content']; ?>
+       <section>
+               <?php if(x($page,'content')) echo $page['content']; ?>
+               <div id="pause"></div> <!-- The pause/resume Ajax indicator -->
                <div id="page-footer"></div>
        </section>
 
@@ -41,4 +43,3 @@
        <?php if (x($page, 'bottom')) echo $page['bottom']; ?>
 </body>
 </html>
-
index 8e128ae27fb5fd21288010266925824b135912a4..023e419464d7015c6dd22386ae14a26077b3e545 100644 (file)
@@ -35,6 +35,11 @@ body, section, blockquote, blockquote.shared_content, #profile-jot-form,
        background-color: #171B1F !important;
 }
 
+#profile-jot-acl-wrapper, #event-notice, #event-wrapper,
+#cboxLoadedContent, .contact-photo-menu {
+       background-color: #252C33 !important;
+}
+
 div.rte, .mceContentBody {
 background:none repeat scroll 0 0 #333333!important;
 color:#FFF!important;
@@ -63,3 +68,35 @@ li :hover {
 #viewcontact_wrapper-network {
        background-color: #343434;
 }
+
+table.smiley-preview{
+       background-color: #252C33 !important;
+}
+
+/* ACL permission popup */
+ .acl-list-item.groupshow {
+       border-color: #9ade00 !important;
+}
+
+.acl-list-item.grouphide {
+       border-color: #ff4141 !important;
+}
+
+/* Notifications */
+li.notify-unseen {
+       background-color: #252C33;
+}
+
+li.notify-seen {
+       background-color: #1C2126;
+}
+
+.photo-top-album-name {
+       background-color: #252C33;
+}
+
+#panel {
+       background-color: #252C33;
+       border: 3px solid #364e59;
+       color: #989898;
+}
index cde49b7b72684dece0d5494613062d6b947a9b8f..b67dc5f77121f0560a282da5deebb80490c533c9 100644 (file)
@@ -8,11 +8,11 @@ aside, header, #nav-events-link, #search-box, #nav-admin-link, #activitiy-by-dat
 }
 
 section {
-  /* left: calc((100% - (784px)) / 2); */
+  box-sizing: border-box;
   left: 0px;
-  width: calc(100% - 20px);
+  width: 100%;
   max-width: 100%;
-  padding: 10px;
+  padding: 5px;
 }
 
 body, section, nav .nav-menu, div.pager, ul.tabs {
@@ -67,10 +67,23 @@ nav ul {
 
 @media screen and (max-width: 480px) {
   .wall-item-container .wall-item-content img,
-  .children .wall-item-container .wall-item-item .wall-item-content img,
   .wall-item-container .wall-item-content .type-link img.attachment-image, .type-link img.attachment-image, .type-video img.attachment-image {
     max-width: 200px;
   }
+  /* fix img width in threaded view - maybe there exists a better possibility to do this 
+    maybe this needs also to be done for tablet view*/
+  .children .wall-item-container .wall-item-item .wall-item-content img {
+    max-width: 100%;
+  }
+  iframe {
+    max-width: 100%;
+  }
+
+  /* the top-nav notfication menu
+    Note: the device needs a min display width of 320px */
+  #nav-notifications-menu {
+    width: 270px;
+  }
 }
 
 .wall-item-container.thread_level_2,
@@ -79,8 +92,8 @@ nav ul {
 .wall-item-container.thread_level_5,
 .wall-item-container.thread_level_6,
 .wall-item-container.thread_level_7 {
-  margin-left: 60px;
-  width: calc(100% - 70px);
+  margin-left: 0;
+  width: calc(100% - 10px);
 }
 
 .wall-item-container.thread_level_2 .wall-item-content,
@@ -92,6 +105,10 @@ nav ul {
   max-width: 100%;
 }
 
+.wall-item-container .wall-item-info { width: auto; }
+.contact-photo-wrapper { width: 55px; }
+.wwto { left: 0px; }
+
 /* aside in/out */
 .mobile-aside-toggle {
        display: block !important;
@@ -148,3 +165,116 @@ aside.show {
 }
 .tabs.show::after { display: none; }
 .tabs.show .tab { display: block; }
+
+/* jot buttons */
+#profile-jot-submit,
+#jot-preview-link {
+    float: none;
+    display: block;
+    width: 100%;
+    margin: 0 0 1em 0;
+}
+#profile-jot-submit-wrapper > div {
+    margin: 0 1em 1em 0;
+}
+#profile-jot-submit-wrapper > div#profile-jot-perms { margin: 0; }
+/* ACL window */
+#profile-jot-acl-wrapper, #profile-jot-acl-wrapper * { box-sizing: border-box; }
+#acl-wrapper { width: 100%; float: none; }
+#acl-search { width: 100%; float: none; padding-right: 0px; margin-bottom: 1em; }
+#acl-showall { width: 100%; height: 48px; margin-bottom: 1em; }
+.acl-list-item { width: auto; float: none; height: auto; overflow: hidden; position: relative;}
+.acl-list-item img { width: 48px; height: 48px; }
+.acl-list-item p { height: auto; font-size: inherit; }
+.acl-list-item a {
+  float: none;
+  position: absolute;
+  top: 5px;
+  right: 5px;
+  height: 48px;
+  padding: 10px 2px 2px 2px;
+  font-size: 12px;
+  width: 20%;
+  text-align: center;
+  background-position: center 5px;
+}
+.acl-list-item a.acl-button-hide { right: 25%; }
+/* flexbox for ACL window */
+#cboxLoadedContent,
+#cboxLoadedContent > div,
+#acl-wrapper {
+  display: -ms-Flexbox !important;
+  -ms-box-orient: vertival;
+
+  display: -webkit-flex !important;
+  display: -moz-flex !important;
+  display: -ms-flex !important;
+  display: flex !important;
+
+  -webkit-flex-flow: column;
+  -moz-flex-flow: column;
+  -ms-flex-flow: column;
+  flex-flow: column;
+  
+  -webkit-flex: 1 100%;
+  -moz-flex: 1 100%;
+  -ms-flex: 1 100%;
+  flex: 1 100%;
+}
+#acl-list {
+  -webkit-flex: 1 1 auto;
+  -moz-flex: 1 1 auto;
+  -ms-flex: 1 1 auto;
+  flex: 1 1 auto;  
+}
+
+/** input elements **/
+input,
+textarea,
+select {
+    font-size: 18px;
+    border: 1px solid #888;
+    padding: 0.2em;
+}
+input:focus,
+textarea:focus,
+select:focus {
+    box-shadow: 1px 1px 10px rgba(46, 151, 255, 0.62);
+}
+
+.field, .field > * {  box-sizing: border-box; }
+.field label { width: 100%; float: none; display: block; }
+.field input, .field textarea, .field select { max-width: 100%; width: 100%; }
+.field.yesno .onoff,
+.field.checkbox input { width: auto; float: right; }
+.field.yesno label,
+.field.checkbox label { width: 70%; float: left; }
+.field .field_help { margin: 0; }
+
+/** event **/
+.event-start, .event-end { width: auto; }
+.event-start .dtstart, .event-end .dtend { float: none; display: block; }
+
+/** oembed **/
+.embed_video {
+  float: none;
+  margin: 0px;
+  height: 120px;
+  width: 100%;
+  overflow: hidden;
+  display: block;
+}
+.embed_video > img {
+  display: block;
+  width: 100% !important;
+  height: auto !important;
+  max-width: 100% !important;
+}
+.embed_video > div {
+  background-image: none !important; background-color: transparent !important;
+  width: 100% !important; height: 110px !important;
+}
+
+.type-link > a { height: 120px; width: 100%; overflow: hidden; display: block; }
+.type-link > a > img { display: block; max-width: 100%!important; width: 100% !important; height: auto !important; }
\ No newline at end of file
index 2f0bd9cbc0ed5f7971e766a2aa348a87a6ddc91f..9cf142ad6e8516e55c0607af1ee49865614c4297 100644 (file)
@@ -24,7 +24,7 @@ nav .nav-notify {
   padding: 1px 3px;
   border-radius: 5px 5px 5px 5px;
 }
-// -----
+
 nav .nav-menu-icon .nav-notify {
   top: 0px;
 }
index 84b3ff236169d14b32802fdbc16bdda344c058a7..e35556f5414005de273b5f75ad6560972f90922d 100644 (file)
@@ -127,8 +127,8 @@ img {
 .icon {
   background-color: transparent ;
   background-repeat: no-repeat;
-  width: 18px;
-  height: 18px;
+  width: 20px;
+  height: 20px;
   /* display: block; */
   display: inline-block;
   overflow: hidden;
@@ -269,7 +269,7 @@ div.pager {
 /* global */
 body {
 /*  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; */
-  font-family: system,-apple-system,".SFNSText-Regular","San Francisco","Roboto","Segoe UI","Helvetica Neue","Lucida Grande",Helvetica,Arial,sans-serif;
+  font-family: -apple-system,".SFNSText-Regular","San Francisco","Roboto","Segoe UI","Helvetica Neue","Lucida Grande",Helvetica,Arial,sans-serif;
   font-size: 14px;
   /* font-size: 13px;
   line-height: 19.5px; */
@@ -1042,6 +1042,9 @@ aside {
   box-shadow: 1px 2px 0px 0px #D8D8D8;
   color: #737373;
 }
+aside .vcard .tool {
+  clear: both;
+}
 aside .vcard .fn {
   font-size: 18px;
   font-weight: bold;
@@ -1050,11 +1053,11 @@ aside .vcard .fn {
 }
 aside .vcard .title {
   margin-bottom: 5px;
-  float: left;
 }
 aside .vcard dl {
   height: auto;
   overflow: auto;
+  word-break: break-word;
 }
 aside .vcard .account-type {
   margin-bottom: 13px;
@@ -1138,6 +1141,10 @@ aside h4, right_aside h4 {
   font-size: 1.17em;
 }
 
+aside img {
+  max-width: 175px;
+}
+
 .nets-ul {
   margin-top: 0px;
 }
@@ -1412,6 +1419,7 @@ section.minimal {
   /* font-size: 14px; */
   max-width: 660px;
   word-wrap: break-word;
+  word-break: break-word;
   /* line-height: 1.36; */
   padding-bottom: 6px;
 }
@@ -1581,63 +1589,34 @@ section.minimal {
   width: calc(100% - 165px);
 }
 
-.wall-item-container.comment .wall-item-content {
-  max-width: 585px;
-}
-.wall-item-container.thread_level_3 .wall-item-content {
-  max-width: 570px;
-}
-.wall-item-container.thread_level_4 .wall-item-content {
-  max-width: 555px;
-}
-.wall-item-container.thread_level_5 .wall-item-content {
-  max-width: 540px;
-}
-.wall-item-container.thread_level_6 .wall-item-content {
-  max-width: 525px;
-}
+.wall-item-container.comment .wall-item-content,
+.wall-item-container.thread_level_3 .wall-item-content,
+.wall-item-container.thread_level_4 .wall-item-content,
+.wall-item-container.thread_level_5 .wall-item-content,
+.wall-item-container.thread_level_6 .wall-item-content,
 .wall-item-container.thread_level_7 .wall-item-content {
-  max-width: 510px;
+  max-width: calc(100% - 1px);
 }
 
-.children .wall-item-comment-wrapper textarea {
-  width: 570px;
-}
-.wall-item-container.thread_level_3 .wall-item-comment-wrapper textarea {
-  width: 555px;
-}
-.wall-item-container.thread_level_4 .wall-item-comment-wrapper textarea {
-  width: 540px;
-}
-.wall-item-container.thread_level_5 .wall-item-comment-wrapper textarea {
-  width: 525px;
-}
-.wall-item-container.thread_level_6 .wall-item-comment-wrapper textarea {
-  width: 510px;
-}
+.children .wall-item-comment-wrapper textarea, 
+.wall-item-container.thread_level_3 .wall-item-comment-wrapper textarea,
+.wall-item-container.thread_level_4 .wall-item-comment-wrapper textarea,
+.wall-item-container.thread_level_5 .wall-item-comment-wrapper textarea,
+.wall-item-container.thread_level_6 .wall-item-comment-wrapper textarea,
 .wall-item-container.thread_level_7 .wall-item-comment-wrapper textarea {
-  width: 495px;
+  width: calc(100% - 11px);
 }
 
-.children .wall-item-bottom .comment-edit-preview {
-  width: 575px;
-}
-.wall-item-container.thread_level_3 .wall-item-bottom .comment-edit-preview {
-  width: 560px;
-}
-.wall-item-container.thread_level_4 .wall-item-bottom .comment-edit-preview {
-  width: 545px;
-}
-.wall-item-container.thread_level_5 .wall-item-bottom .comment-edit-preview {
-  width: 530px;
-}
-.wall-item-container.thread_level_6 .wall-item-bottom .comment-edit-preview {
-  width: 515px;
-}
+.children .wall-item-bottom .comment-edit-preview,
+.wall-item-container.thread_level_3 .wall-item-bottom .comment-edit-preview,
+.wall-item-container.thread_level_4 .wall-item-bottom .comment-edit-preview,
+.wall-item-container.thread_level_5 .wall-item-bottom .comment-edit-preview,
+.wall-item-container.thread_level_6 .wall-item-bottom .comment-edit-preview,
 .wall-item-container.thread_level_7 .wall-item-bottom .comment-edit-preview {
-  width: 500px;
+  width: calc(100% - 6px);
 }
 
+
 .wall-item-container.comment .contact-photo {
   width: 32px;
   height: 32px;
@@ -1781,7 +1760,8 @@ section.minimal {
 }
 
 #jot-preview-content {
-  padding-top: 25px;
+  padding-top: 4px;
+  clear: both;
 }
 
 #jot-preview-content .tread-wrapper {
@@ -2048,6 +2028,15 @@ section.minimal {
   cursor: pointer;
   margin-top: 3px;
   height: 10px;
+  display: inline-block;
+}
+#smileybutton {
+  position: absolute;
+  z-index: 99;
+}
+table.smiley-preview{
+  background-color: #FFF;
+  box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.7);
 }
 #jot-perms-icon {
   float: right;
@@ -2745,7 +2734,9 @@ a.mail-list-link {
         margin-bottom: 15px;
 }
 
-.vevent .event-description, .vevent .event-location {
+.vevent .event-summary,
+.vevent .event-description,
+.vevent .event-location {
         margin-left: 10px;
         margin-right: 10px;
 }
@@ -2944,35 +2935,41 @@ a.mail-list-link {
   color: #999999;
 }
 
+/* photo album page */
 .photo-top-image-wrapper {
-        position: relative;
-        float: left;
-        margin-top: 15px;
-        margin-right: 15px;
-        width: 200px; height: 200px;
-        overflow: hidden;
+  position: relative;
+  float: left;
+  margin-top: 15px;
+  margin-right: 15px;
+  width: 200px; height: 200px;
+  overflow: hidden;
 }
 .photo-top-album-name {
-        width: 100%;
-        min-height: 2em;
-        position: absolute;
-        bottom: 0px;
-        padding: 0px 3px;
-        padding-top: 0.5em;
-        background-color: rgb(255, 255, 255);
+  width: 100%;
+  min-height: 2em;
+  position: absolute;
+  bottom: 0px;
+  padding: 0px 3px;
+  padding-top: 0.5em;
+  background-color: rgb(255, 255, 255);
 }
 #photo-top-end {
-        clear: both;
+  clear: both;
 }
 
 #photo-top-links {
-        margin-bottom: 30px;
-        margin-left: 30px;
+  margin-bottom: 30px;
+  margin-left: 30px;
 }
 
 #photos-upload-newalbum-div {
-        float: left;
-        width: 175px;
+  float: left;
+  width: 175px;
+}
+img.photo-top-photo {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
 }
 
 .menu-profile-list{
index 72731a9bebc9843d3c7949c7a53e7a9abfe16593..3d3c776745de4c86a02527d334a0eab9a811b61f 100644 (file)
@@ -1,12 +1,7 @@
 <?php
-
-if (file_exists("$THEMEPATH//style.css")){
-       echo file_get_contents("$THEMEPATH//style.css");
-}
-
 $uid = get_theme_uid();
 
-$style = get_pconfig( $uid, 'vier', 'style');
+$style = get_pconfig($uid, 'vier', 'style');
 
 if ($style == "")
        $style = get_config('vier', 'style');
@@ -15,14 +10,45 @@ if ($style == "")
        $style = "plus";
 
 if ($style == "flat")
-       $stylecss = file_get_contents('view/theme/vier/flat.css');
+       $stylecssfile = 'view/theme/vier/flat.css';
 else if ($style == "netcolour")
-       $stylecss = file_get_contents('view/theme/vier/netcolour.css');
+       $stylecssfile = 'view/theme/vier/netcolour.css';
 else if ($style == "breathe")
-       $stylecss = file_get_contents('view/theme/vier/breathe.css');
+       $stylecssfile = 'view/theme/vier/breathe.css';
 else if ($style == "plus")
-       $stylecss = file_get_contents('view/theme/vier/plus.css');
+       $stylecssfile = 'view/theme/vier/plus.css';
 else if ($style == "dark")
-       $stylecss = file_get_contents('view/theme/vier/dark.css');
+       $stylecssfile = 'view/theme/vier/dark.css';
+
+if (file_exists($THEMEPATH."//style.css")) {
+       $stylecss = file_get_contents($THEMEPATH."//style.css")."\n";
+       $modified = filemtime($THEMEPATH."//style.css");
+}
+
+$stylemodified = filemtime($stylecssfile);
+$stylecss .= file_get_contents($stylecssfile);
+
+if ($stylemodified > $modified)
+       $modified = $stylemodified;
 
+$modified = gmdate('r', $modified);
+
+$etag = md5($stylecss);
+
+// Only send the CSS file if it was changed
+header('Cache-Control: public');
+header('ETag: "'.$etag.'"');
+header('Last-Modified: '.$modified);
+
+if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) || isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
+
+       $cached_modified = gmdate('r', strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']));
+       $cached_etag = str_replace(array('"', "-gzip"), array('', ''),
+                               stripslashes($_SERVER['HTTP_IF_NONE_MATCH']));
+
+       if (($cached_modified == $modified) AND ($cached_etag == $etag)) {
+               header('HTTP/1.1 304 Not Modified');
+               exit();
+       }
+}
 echo $stylecss;
index 809aa4c13ab36082f030f10eb929904a65891a02..8451188f0d8ba53a19ed296494a8030b9b9112c2 100644 (file)
@@ -4,7 +4,7 @@
   </div>
 {{/if}}
 
-<h2>{{$title}}</h2>
+{{include file="section_title.tpl"}}
 
 <dl id="aprofile-fullname" class="aprofile">
  <dt>{{$profile.fullname.0}}</dt>
 </dl>
 {{/if}}
 
-
-
-
+{{if $profile.forumlist}}
+<dl id="aprofile-forumlist" class="aprofile">
+ <dt>{{$profile.forumlist.0}}</dt>
+ <dd>{{$profile.forumlist.1}}</dd>
+</dl>
+{{/if}}
index 4afc5409c06b0be4692ab1008577efb2b091e55c..c2669f5a931ae8030d88ba603d596b243ec894b9 100644 (file)
@@ -220,7 +220,7 @@ function vier_community_info() {
        //Community_Pages at right_aside
        if($show_pages AND local_user()) {
 
-               require_once('include/forums.php');
+               require_once('include/ForumManager.php');
 
                if(x($_GET['cid']) && intval($_GET['cid']) != 0)
                        $cid = $_GET['cid'];
@@ -228,7 +228,7 @@ function vier_community_info() {
                //sort by last updated item
                $lastitem = true;
 
-               $contacts = get_forumlist($a->user['uid'],true,$lastitem, true);
+               $contacts = ForumManager::get_list($a->user['uid'],true,$lastitem, true);
                $total = count($contacts);
                $visible_forums = 10;