Repository: anantgarg/Qwench Branch: master Commit: e44f916d738b Files: 387 Total size: 1.2 MB Directory structure: gitextract_dpbewjre/ ├── .buildpath ├── .htaccess ├── .project ├── .settings/ │ └── org.eclipse.php.core.prefs ├── LICENSE.TXT ├── README.TXT ├── TODO.TXT ├── VERSION.TXT ├── config.default.php ├── controllers/ │ ├── answers.php │ ├── comments.php │ ├── helpers.php │ ├── questions.php │ ├── tags.php │ └── users.php ├── css/ │ ├── main.css │ ├── prettify.css │ ├── tagscomplete.css │ └── wmd.css ├── db/ │ ├── 1.txt │ ├── 2.txt │ └── 3.txt ├── index.php ├── install.php ├── js/ │ ├── fancyalert.js │ ├── jquery.js │ ├── prettify/ │ │ ├── lang-css.js │ │ ├── lang-hs.js │ │ ├── lang-lisp.js │ │ ├── lang-lua.js │ │ ├── lang-ml.js │ │ ├── lang-proto.js │ │ ├── lang-sql.js │ │ ├── lang-vb.js │ │ ├── lang-wiki.js │ │ └── prettify.js │ ├── showdown.js │ ├── tagscomplete.js │ └── wmd.js ├── libraries/ │ ├── helper.class.php │ ├── markdown.php │ ├── pagination.class.php │ ├── purifier/ │ │ ├── HTMLPurifier/ │ │ │ ├── AttrCollections.php │ │ │ ├── AttrDef/ │ │ │ │ ├── CSS/ │ │ │ │ │ ├── AlphaValue.php │ │ │ │ │ ├── Background.php │ │ │ │ │ ├── BackgroundPosition.php │ │ │ │ │ ├── Border.php │ │ │ │ │ ├── Color.php │ │ │ │ │ ├── Composite.php │ │ │ │ │ ├── DenyElementDecorator.php │ │ │ │ │ ├── Filter.php │ │ │ │ │ ├── Font.php │ │ │ │ │ ├── FontFamily.php │ │ │ │ │ ├── ImportantDecorator.php │ │ │ │ │ ├── Length.php │ │ │ │ │ ├── ListStyle.php │ │ │ │ │ ├── Multiple.php │ │ │ │ │ ├── Number.php │ │ │ │ │ ├── Percentage.php │ │ │ │ │ ├── TextDecoration.php │ │ │ │ │ └── URI.php │ │ │ │ ├── CSS.php │ │ │ │ ├── Enum.php │ │ │ │ ├── HTML/ │ │ │ │ │ ├── Bool.php │ │ │ │ │ ├── Class.php │ │ │ │ │ ├── Color.php │ │ │ │ │ ├── FrameTarget.php │ │ │ │ │ ├── ID.php │ │ │ │ │ ├── Length.php │ │ │ │ │ ├── LinkTypes.php │ │ │ │ │ ├── MultiLength.php │ │ │ │ │ ├── Nmtokens.php │ │ │ │ │ └── Pixels.php │ │ │ │ ├── Integer.php │ │ │ │ ├── Lang.php │ │ │ │ ├── Switch.php │ │ │ │ ├── Text.php │ │ │ │ ├── URI/ │ │ │ │ │ ├── Email/ │ │ │ │ │ │ └── SimpleCheck.php │ │ │ │ │ ├── Email.php │ │ │ │ │ ├── Host.php │ │ │ │ │ ├── IPv4.php │ │ │ │ │ └── IPv6.php │ │ │ │ └── URI.php │ │ │ ├── AttrDef.php │ │ │ ├── AttrTransform/ │ │ │ │ ├── Background.php │ │ │ │ ├── BdoDir.php │ │ │ │ ├── BgColor.php │ │ │ │ ├── BoolToCSS.php │ │ │ │ ├── Border.php │ │ │ │ ├── EnumToCSS.php │ │ │ │ ├── ImgRequired.php │ │ │ │ ├── ImgSpace.php │ │ │ │ ├── Input.php │ │ │ │ ├── Lang.php │ │ │ │ ├── Length.php │ │ │ │ ├── Name.php │ │ │ │ ├── NameSync.php │ │ │ │ ├── SafeEmbed.php │ │ │ │ ├── SafeObject.php │ │ │ │ ├── SafeParam.php │ │ │ │ ├── ScriptRequired.php │ │ │ │ └── Textarea.php │ │ │ ├── AttrTransform.php │ │ │ ├── AttrTypes.php │ │ │ ├── AttrValidator.php │ │ │ ├── Bootstrap.php │ │ │ ├── CSSDefinition.php │ │ │ ├── ChildDef/ │ │ │ │ ├── Chameleon.php │ │ │ │ ├── Custom.php │ │ │ │ ├── Empty.php │ │ │ │ ├── Optional.php │ │ │ │ ├── Required.php │ │ │ │ ├── StrictBlockquote.php │ │ │ │ └── Table.php │ │ │ ├── ChildDef.php │ │ │ ├── Config.php │ │ │ ├── ConfigSchema/ │ │ │ │ ├── Builder/ │ │ │ │ │ ├── ConfigSchema.php │ │ │ │ │ └── Xml.php │ │ │ │ ├── Exception.php │ │ │ │ ├── Interchange/ │ │ │ │ │ ├── Directive.php │ │ │ │ │ └── Id.php │ │ │ │ ├── Interchange.php │ │ │ │ ├── InterchangeBuilder.php │ │ │ │ ├── Validator.php │ │ │ │ ├── ValidatorAtom.php │ │ │ │ ├── schema/ │ │ │ │ │ ├── Attr.AllowedClasses.txt │ │ │ │ │ ├── Attr.AllowedFrameTargets.txt │ │ │ │ │ ├── Attr.AllowedRel.txt │ │ │ │ │ ├── Attr.AllowedRev.txt │ │ │ │ │ ├── Attr.ClassUseCDATA.txt │ │ │ │ │ ├── Attr.DefaultImageAlt.txt │ │ │ │ │ ├── Attr.DefaultInvalidImage.txt │ │ │ │ │ ├── Attr.DefaultInvalidImageAlt.txt │ │ │ │ │ ├── Attr.DefaultTextDir.txt │ │ │ │ │ ├── Attr.EnableID.txt │ │ │ │ │ ├── Attr.ForbiddenClasses.txt │ │ │ │ │ ├── Attr.IDBlacklist.txt │ │ │ │ │ ├── Attr.IDBlacklistRegexp.txt │ │ │ │ │ ├── Attr.IDPrefix.txt │ │ │ │ │ ├── Attr.IDPrefixLocal.txt │ │ │ │ │ ├── AutoFormat.AutoParagraph.txt │ │ │ │ │ ├── AutoFormat.Custom.txt │ │ │ │ │ ├── AutoFormat.DisplayLinkURI.txt │ │ │ │ │ ├── AutoFormat.Linkify.txt │ │ │ │ │ ├── AutoFormat.PurifierLinkify.DocURL.txt │ │ │ │ │ ├── AutoFormat.PurifierLinkify.txt │ │ │ │ │ ├── AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt │ │ │ │ │ ├── AutoFormat.RemoveEmpty.RemoveNbsp.txt │ │ │ │ │ ├── AutoFormat.RemoveEmpty.txt │ │ │ │ │ ├── CSS.AllowImportant.txt │ │ │ │ │ ├── CSS.AllowTricky.txt │ │ │ │ │ ├── CSS.AllowedProperties.txt │ │ │ │ │ ├── CSS.DefinitionRev.txt │ │ │ │ │ ├── CSS.MaxImgLength.txt │ │ │ │ │ ├── CSS.Proprietary.txt │ │ │ │ │ ├── Cache.DefinitionImpl.txt │ │ │ │ │ ├── Cache.SerializerPath.txt │ │ │ │ │ ├── Core.AggressivelyFixLt.txt │ │ │ │ │ ├── Core.CollectErrors.txt │ │ │ │ │ ├── Core.ColorKeywords.txt │ │ │ │ │ ├── Core.ConvertDocumentToFragment.txt │ │ │ │ │ ├── Core.DirectLexLineNumberSyncInterval.txt │ │ │ │ │ ├── Core.Encoding.txt │ │ │ │ │ ├── Core.EscapeInvalidChildren.txt │ │ │ │ │ ├── Core.EscapeInvalidTags.txt │ │ │ │ │ ├── Core.EscapeNonASCIICharacters.txt │ │ │ │ │ ├── Core.HiddenElements.txt │ │ │ │ │ ├── Core.Language.txt │ │ │ │ │ ├── Core.LexerImpl.txt │ │ │ │ │ ├── Core.MaintainLineNumbers.txt │ │ │ │ │ ├── Core.RemoveInvalidImg.txt │ │ │ │ │ ├── Core.RemoveScriptContents.txt │ │ │ │ │ ├── Filter.Custom.txt │ │ │ │ │ ├── Filter.ExtractStyleBlocks.Escaping.txt │ │ │ │ │ ├── Filter.ExtractStyleBlocks.Scope.txt │ │ │ │ │ ├── Filter.ExtractStyleBlocks.TidyImpl.txt │ │ │ │ │ ├── Filter.ExtractStyleBlocks.txt │ │ │ │ │ ├── Filter.YouTube.txt │ │ │ │ │ ├── HTML.Allowed.txt │ │ │ │ │ ├── HTML.AllowedAttributes.txt │ │ │ │ │ ├── HTML.AllowedElements.txt │ │ │ │ │ ├── HTML.AllowedModules.txt │ │ │ │ │ ├── HTML.Attr.Name.UseCDATA.txt │ │ │ │ │ ├── HTML.BlockWrapper.txt │ │ │ │ │ ├── HTML.CoreModules.txt │ │ │ │ │ ├── HTML.CustomDoctype.txt │ │ │ │ │ ├── HTML.DefinitionID.txt │ │ │ │ │ ├── HTML.DefinitionRev.txt │ │ │ │ │ ├── HTML.Doctype.txt │ │ │ │ │ ├── HTML.ForbiddenAttributes.txt │ │ │ │ │ ├── HTML.ForbiddenElements.txt │ │ │ │ │ ├── HTML.MaxImgLength.txt │ │ │ │ │ ├── HTML.Parent.txt │ │ │ │ │ ├── HTML.Proprietary.txt │ │ │ │ │ ├── HTML.SafeEmbed.txt │ │ │ │ │ ├── HTML.SafeObject.txt │ │ │ │ │ ├── HTML.Strict.txt │ │ │ │ │ ├── HTML.TidyAdd.txt │ │ │ │ │ ├── HTML.TidyLevel.txt │ │ │ │ │ ├── HTML.TidyRemove.txt │ │ │ │ │ ├── HTML.Trusted.txt │ │ │ │ │ ├── HTML.XHTML.txt │ │ │ │ │ ├── Output.CommentScriptContents.txt │ │ │ │ │ ├── Output.Newline.txt │ │ │ │ │ ├── Output.SortAttr.txt │ │ │ │ │ ├── Output.TidyFormat.txt │ │ │ │ │ ├── Test.ForceNoIconv.txt │ │ │ │ │ ├── URI.AllowedSchemes.txt │ │ │ │ │ ├── URI.Base.txt │ │ │ │ │ ├── URI.DefaultScheme.txt │ │ │ │ │ ├── URI.DefinitionID.txt │ │ │ │ │ ├── URI.DefinitionRev.txt │ │ │ │ │ ├── URI.Disable.txt │ │ │ │ │ ├── URI.DisableExternal.txt │ │ │ │ │ ├── URI.DisableExternalResources.txt │ │ │ │ │ ├── URI.DisableResources.txt │ │ │ │ │ ├── URI.Host.txt │ │ │ │ │ ├── URI.HostBlacklist.txt │ │ │ │ │ ├── URI.MakeAbsolute.txt │ │ │ │ │ ├── URI.Munge.txt │ │ │ │ │ ├── URI.MungeResources.txt │ │ │ │ │ ├── URI.MungeSecretKey.txt │ │ │ │ │ ├── URI.OverrideAllowedSchemes.txt │ │ │ │ │ └── info.ini │ │ │ │ └── schema.ser │ │ │ ├── ConfigSchema.php │ │ │ ├── ContentSets.php │ │ │ ├── Context.php │ │ │ ├── Definition.php │ │ │ ├── DefinitionCache/ │ │ │ │ ├── Decorator/ │ │ │ │ │ ├── Cleanup.php │ │ │ │ │ ├── Memory.php │ │ │ │ │ └── Template.php.in │ │ │ │ ├── Decorator.php │ │ │ │ ├── Null.php │ │ │ │ ├── Serializer/ │ │ │ │ │ ├── HTML/ │ │ │ │ │ │ └── 4.0.0,ddc9b993d7fc8d4a185e8dbf5b9a0996,1.ser │ │ │ │ │ ├── README │ │ │ │ │ └── URI/ │ │ │ │ │ └── 4.0.0,05c766101e813c246917b022f97b5e6e,1.ser │ │ │ │ └── Serializer.php │ │ │ ├── DefinitionCache.php │ │ │ ├── DefinitionCacheFactory.php │ │ │ ├── Doctype.php │ │ │ ├── DoctypeRegistry.php │ │ │ ├── ElementDef.php │ │ │ ├── Encoder.php │ │ │ ├── EntityLookup/ │ │ │ │ └── entities.ser │ │ │ ├── EntityLookup.php │ │ │ ├── EntityParser.php │ │ │ ├── ErrorCollector.php │ │ │ ├── ErrorStruct.php │ │ │ ├── Exception.php │ │ │ ├── Filter/ │ │ │ │ ├── ExtractStyleBlocks.php │ │ │ │ └── YouTube.php │ │ │ ├── Filter.php │ │ │ ├── Generator.php │ │ │ ├── HTMLDefinition.php │ │ │ ├── HTMLModule/ │ │ │ │ ├── Bdo.php │ │ │ │ ├── CommonAttributes.php │ │ │ │ ├── Edit.php │ │ │ │ ├── Forms.php │ │ │ │ ├── Hypertext.php │ │ │ │ ├── Image.php │ │ │ │ ├── Legacy.php │ │ │ │ ├── List.php │ │ │ │ ├── Name.php │ │ │ │ ├── NonXMLCommonAttributes.php │ │ │ │ ├── Object.php │ │ │ │ ├── Presentation.php │ │ │ │ ├── Proprietary.php │ │ │ │ ├── Ruby.php │ │ │ │ ├── SafeEmbed.php │ │ │ │ ├── SafeObject.php │ │ │ │ ├── Scripting.php │ │ │ │ ├── StyleAttribute.php │ │ │ │ ├── Tables.php │ │ │ │ ├── Target.php │ │ │ │ ├── Text.php │ │ │ │ ├── Tidy/ │ │ │ │ │ ├── Name.php │ │ │ │ │ ├── Proprietary.php │ │ │ │ │ ├── Strict.php │ │ │ │ │ ├── Transitional.php │ │ │ │ │ ├── XHTML.php │ │ │ │ │ └── XHTMLAndHTML4.php │ │ │ │ ├── Tidy.php │ │ │ │ └── XMLCommonAttributes.php │ │ │ ├── HTMLModule.php │ │ │ ├── HTMLModuleManager.php │ │ │ ├── IDAccumulator.php │ │ │ ├── Injector/ │ │ │ │ ├── AutoParagraph.php │ │ │ │ ├── DisplayLinkURI.php │ │ │ │ ├── Linkify.php │ │ │ │ ├── PurifierLinkify.php │ │ │ │ ├── RemoveEmpty.php │ │ │ │ └── SafeObject.php │ │ │ ├── Injector.php │ │ │ ├── Language/ │ │ │ │ ├── classes/ │ │ │ │ │ └── en-x-test.php │ │ │ │ └── messages/ │ │ │ │ ├── en-x-test.php │ │ │ │ ├── en-x-testmini.php │ │ │ │ └── en.php │ │ │ ├── Language.php │ │ │ ├── LanguageFactory.php │ │ │ ├── Length.php │ │ │ ├── Lexer/ │ │ │ │ ├── DOMLex.php │ │ │ │ ├── DirectLex.php │ │ │ │ ├── PEARSax3.php │ │ │ │ └── PH5P.php │ │ │ ├── Lexer.php │ │ │ ├── PercentEncoder.php │ │ │ ├── Printer/ │ │ │ │ ├── CSSDefinition.php │ │ │ │ ├── ConfigForm.css │ │ │ │ ├── ConfigForm.js │ │ │ │ ├── ConfigForm.php │ │ │ │ └── HTMLDefinition.php │ │ │ ├── Printer.php │ │ │ ├── PropertyList.php │ │ │ ├── PropertyListIterator.php │ │ │ ├── Strategy/ │ │ │ │ ├── Composite.php │ │ │ │ ├── Core.php │ │ │ │ ├── FixNesting.php │ │ │ │ ├── MakeWellFormed.php │ │ │ │ ├── RemoveForeignElements.php │ │ │ │ └── ValidateAttributes.php │ │ │ ├── Strategy.php │ │ │ ├── StringHash.php │ │ │ ├── StringHashParser.php │ │ │ ├── TagTransform/ │ │ │ │ ├── Font.php │ │ │ │ └── Simple.php │ │ │ ├── TagTransform.php │ │ │ ├── Token/ │ │ │ │ ├── Comment.php │ │ │ │ ├── Empty.php │ │ │ │ ├── End.php │ │ │ │ ├── Start.php │ │ │ │ ├── Tag.php │ │ │ │ └── Text.php │ │ │ ├── Token.php │ │ │ ├── TokenFactory.php │ │ │ ├── URI.php │ │ │ ├── URIDefinition.php │ │ │ ├── URIFilter/ │ │ │ │ ├── DisableExternal.php │ │ │ │ ├── DisableExternalResources.php │ │ │ │ ├── HostBlacklist.php │ │ │ │ ├── MakeAbsolute.php │ │ │ │ └── Munge.php │ │ │ ├── URIFilter.php │ │ │ ├── URIParser.php │ │ │ ├── URIScheme/ │ │ │ │ ├── ftp.php │ │ │ │ ├── http.php │ │ │ │ ├── https.php │ │ │ │ ├── mailto.php │ │ │ │ ├── news.php │ │ │ │ └── nntp.php │ │ │ ├── URIScheme.php │ │ │ ├── URISchemeRegistry.php │ │ │ ├── UnitConverter.php │ │ │ ├── VarParser/ │ │ │ │ ├── Flexible.php │ │ │ │ └── Native.php │ │ │ ├── VarParser.php │ │ │ └── VarParserException.php │ │ ├── HTMLPurifier.auto.php │ │ ├── HTMLPurifier.autoload.php │ │ ├── HTMLPurifier.func.php │ │ ├── HTMLPurifier.includes.php │ │ ├── HTMLPurifier.kses.php │ │ ├── HTMLPurifier.path.php │ │ ├── HTMLPurifier.php │ │ └── HTMLPurifier.safe-includes.php │ ├── score.php │ ├── shared.php │ ├── template.class.php │ └── timeago.php ├── views/ │ ├── answers/ │ │ └── edit.php │ ├── comments/ │ │ └── post.php │ ├── footer.php │ ├── header.php │ ├── helpers/ │ │ └── getuser.php │ ├── questions/ │ │ ├── ask.php │ │ ├── cache.php │ │ ├── edit.php │ │ ├── index.php │ │ └── view.php │ ├── tags/ │ │ └── index.php │ └── users/ │ ├── edit.php │ ├── index.php │ ├── login.php │ ├── logout.php │ ├── register.php │ └── view.php └── web.config ================================================ FILE CONTENTS ================================================ ================================================ FILE: .buildpath ================================================ ================================================ FILE: .htaccess ================================================ RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php/$1 [QSA,L] ================================================ FILE: .project ================================================ qwench_new org.eclipse.wst.validation.validationbuilder org.eclipse.dltk.core.scriptbuilder org.eclipse.php.core.PHPNature ================================================ FILE: .settings/org.eclipse.php.core.prefs ================================================ #Sat Oct 19 18:13:01 WAT 2013 eclipse.preferences.version=1 include_path=0;/qwench_new ================================================ FILE: LICENSE.TXT ================================================ Qwench Copyright (c) 2009-2010 Inscripts Qwench ('the Software') is a copyrighted work of authorship. Inscripts retains ownership of the Software and any copies of it, regardless of the form in which the copies may exist. By installing and using Qwench on your server, you agree to the following terms and conditions. A Modification, or a Mod, is defined as instructions, to be performed manually or in an automated manner, that alter any part of this Package. A Modified Package is defined as this Package or a derivative of it with one or more Modification applied to it. Distribution is defined as allowing one or more other people to in any way download or receive a copy of this Package, a Modified Package, or a derivative of this Package. Such agreement is either on your own behalf or on behalf of any corporate entity which employs you or which you represent ('Corporate Licensee'). In this Agreement, 'you' includes both the reader and any Corporate Licensee and 'Inscripts' means Inscripts (I) Private Limited: 1. Permission is hereby granted to use, copy, modify and/or distribute this Package, provided that: a. All copyright notices within source files and as generated by the Software as output are retained, unchanged. b. Any Distribution of this Package, whether as a Modified Package or not, includes this license and is released under the terms of this Agreement. This clause is not dependant upon any measure of changes made to this Package. c. This Package, Modified Packages, and derivative works may not be sold or released under any paid license. Copying fees for the transport of this Package, support fees for installation or other services, and hosting fees for hosting the Software may, however, be imposed. d. Any Distribution of this Package, whether as a Modified Package or not, requires express written consent from Inscripts. 2.You may make Modifications to this Package or a derivative of it, and distribute your Modifications in a form that is separate from the Package, such as patches. The following restrictions apply to Modifications: a. A Modification must not alter or remove any copyright notices in the Software or Package, generated or otherwise. b. When a Modification to the Package is released, a non-exclusive royalty-free right is granted to Inscripts to distribute the Modification in future versions of the Package provided such versions remain available under the terms of this Agreement in addition to any other license(s) of the initial developer. c. Any Distribution of a Modified Package or derivative requires express written consent from Inscripts. 3.Permission is hereby also granted to distribute programs which depend on this Package, provided that you do not distribute any Modified Package without express written consent. All Software copyright notices within the scripts must remain unchanged (and visible). The Software may not be used for anything that would represent or is associated with an Intellectual Property violation, including, but not limited to, engaging in any activity that infringes or misappropriates the intellectual property rights of others, including copyrights, trademarks, service marks, trade secrets, software piracy, and patents held by individuals, corporations, or other entities. If any of the terms of this Agreement are violated, Inscripts reserves the right to revoke the Software license at any time. 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. ================================================ FILE: README.TXT ================================================ Qwench Copyright (c) 2009-2010 Inscripts Installation Instructions ----------------------------------------------- 1. Modify config.php as per your site 2. Run install.php (using your browser) 3. Done! (Delete install.php) 4. Register on your new site :) ----------------------------------------------- ================================================ FILE: TODO.TXT ================================================ 1 Visit the installation process, make more precise and show errors, rather than assume everything works fine 2 Edit all forms and add placeholders ================================================ FILE: VERSION.TXT ================================================ 1.0 ================================================ FILE: config.default.php ================================================ EOD; $template->set('js',$js); $sql = ("select * from answers where id = '".escape($answerid)."'"); $query = mysql_query($sql); $result = mysql_fetch_array($query); $template->set('description',$result['description']); $template->set('answerid',$result['id']); } function post() { authenticate(1); $basePath = basePath(); $description = sanitize($_POST['description'],"markdown"); $questionid = sanitize($_POST['questionid'],"int"); $sql = ("select * from questions where id = '".escape($questionid)."'"); $query = mysql_query($sql); $result = mysql_fetch_array($query); if (strlen($description) < 15 || $result['id'] == '' || $result['id'] == 0) { header("Location: $basePath/questions/view/$questionid/{$result['slug']}"); exit; } $sql = ("insert into answers (questionid,description,created,updated,userid,accepted,votes) values ('".escape($questionid)."','".escape($description)."',NOW(),NOW(),'".escape($_SESSION['userid'])."','0','0')"); $query = mysql_query($sql); $sql = ("update questions set updated = NOW(), answers=answers+1 where id = '".escape($result['id'])."'"); $query = mysql_query($sql); header("Location: $basePath/questions/view/$questionid/{$result['slug']}"); } function update() { authenticate(1); $answerid = sanitize($_POST['id'],"int"); $description = sanitize($_POST['description'],"markdown"); $sql = ("select * from answers where id = '".escape($answerid)."'"); $query = mysql_query($sql); $result = mysql_fetch_array($query); $sql = ("select * from questions where id = '".escape($result['questionid'])."'"); $query = mysql_query($sql); $qresult = mysql_fetch_array($query); if ($qresult['userid'] != $_SESSION['userid']) { $basePath = basePath(); header("Location: $basePath/questions/view/{$qresult['id']}/{$qresult['slug']}"); } $sql = ("update answers set description = '".escape($description)."', updated = NOW() where userid = '".escape($_SESSION['userid'])."' and id = '".escape($answerid)."'"); $query = mysql_query($sql); $sql = ("update questions set updated = NOW() where id = '".escape($result['questionid'])."'"); $query = mysql_query($sql); $basePath = basePath(); header("Location: $basePath/questions/view/{$qresult['id']}/{$qresult['slug']}"); } function vote() { if ($_SESSION['userid'] == '') { echo "0Please login to vote"; exit; } $id = sanitize($_POST['id'],"int"); $vote = sanitize($_POST['vote'],"string"); if ($vote == 'plus') { $vote = '+1'; } else { $vote = '-1'; } $sql = ("select answers.userid,answers_votes.id qvid,answers_votes.vote qvvote from answers left join answers_votes on (answers.id = answers_votes.answerid and answers_votes.userid = '".escape($_SESSION['userid'])."') where answers.id = '".escape($id)."'"); $query = mysql_query($sql); $answer = mysql_fetch_array($query); if ($answer['userid'] == $_SESSION['userid']) { echo "0"."You cannot up/down vote your own answer"; exit; } if ($answer['qvid'] > 0) { if ($answer['qvvote'] == 1 && $vote == '+1') { $vote = "-1"; score('a_upvoted_removed',$id,$answer['userid']); } else if ($answer['qvvote'] == 1 && $vote == '-1') { $vote = "-2"; score('a_upvoted_removed',$id,$answer['userid']); score('a_downvoter',$id); score('a_downvoted',$id,$answer['userid']); } else if ($answer['qvvote'] == -1 && $vote == '-1') { $vote = "+1"; score('a_downvoter_removed',$id); score('a_downvoted_removed',$id,$answer['userid']); } else if ($answer['qvvote'] == -1 && $vote == '+1') { $vote = "+2"; score('a_downvoter_removed',$id); score('a_downvoted_removed',$id,$answer['userid']); score('a_upvoted',$id,$answer['userid']); } else if ($answer['qvvote'] == 0) { if ($vote == 1) { score('a_upvoted',$id,$answer['userid']); } else { score('a_downvoter',$id); score('a_downvoted',$id,$answer['userid']); } } $sql = ("update answers_votes set vote = vote".escape($vote)." where id = '".$answer['qvid']."'"); $query = mysql_query($sql); } else { $sql = ("insert into answers_votes (answerid,userid,vote) values ('".escape($id)."','".escape($_SESSION['userid'])."','".escape($vote)."')"); $query = mysql_query($sql); if ($vote == 1) { score('a_upvoted',$id,$answer['userid']); } else { score('a_downvoter',$id); score('a_downvoted',$id,$answer['userid']); } } $sql_nest = ("update answers set votes = votes".escape($vote)." where id = '".escape($id)."'"); $query_nest = mysql_query($sql_nest); echo "1Thankyou for voting"; exit; } function accept() { authenticate(1); $answerid = sanitize($_GET['id'],"int"); $sql = ("select questionid,userid from answers where id = '".escape($answerid)."'"); $query = mysql_query($sql); $answer = mysql_fetch_array($query); $sql = ("select questions.*,answers.id answerid, answers.userid answeruserid from questions left join answers on (questions.id = answers.questionid and answers.accepted = 1) where questions.id = '".escape($answer['questionid'])."'"); $query = mysql_query($sql); $result = mysql_fetch_array($query); if ($result['kb'] == 1) { header("Location: $basePath/questions/view/{$result['id']}/{$result['slug']}"); exit; } if ($result['answerid'] > 0) { score('a_accepted_removed',$answerid,$result['answeruserid']); } else { score('a_accepter',$answerid); } if ($result['userid'] == $_SESSION['userid']) { $sql = ("update answers set accepted = '0' where questionid = '".escape($result['id'])."'"); $query = mysql_query($sql); $sql = ("update answers set accepted = '1' where questionid = '".escape($result['id'])."' and id = '".escape($answerid)."'"); $query = mysql_query($sql); $sql = ("update questions set accepted = '1' where id = '".escape($result['id'])."' and userid = '".escape($_SESSION['userid'])."'"); $query = mysql_query($sql); score('a_accepted',$answerid,$answer['userid']); } $basePath = basePath(); header("Location: $basePath/questions/view/{$result['id']}/{$result['slug']}"); } ================================================ FILE: controllers/comments.php ================================================ 600) { echo "0An error has occurred. Please try again later"; exit; } $sql = ("insert into comments (type,comment,votes,created,userid,typeid) values ('".escape($type)."','".escape($comment)."','0',NOW(),'".escape($_SESSION['userid'])."','".escape($typeid)."')"); $query = mysql_query($sql); $template->set('comment',$comment); $firstname = $_SESSION['name']; $pos = strpos($_SESSION['name'],' '); if ($pos > 0) { $firstname = substr($_SESSION['name'],0,$pos); } $template->set('username',$firstname); $template->set('userid',$_SESSION['userid']); } function vote() { if ($_SESSION['userid'] == '') { echo "0Please login to vote"; exit; } $id = sanitize($_POST['id'],"int"); $sql = ("select userid from comments where id = '".escape($id)."'"); $query = mysql_query($sql); $comment = mysql_fetch_array($query); if ($comment['userid'] == $_SESSION['userid']) { echo "0You cannot upvote your own comment"; exit; } $sql = ("select * from comments_votes where commentid = '".escape($id)."' and userid = '".escape($_SESSION['userid'])."'"); $query = mysql_query($sql); $result = mysql_fetch_array($query); if ($result['id'] > 0) { $sql = ("delete from comments_votes where commentid = '".escape($id)."' and userid = '".escape($_SESSION['userid'])."'"); $query = mysql_query($sql); $sql_nest = ("update comments set votes = votes-1 where id = '".escape($id)."'"); $query_nest = mysql_query($sql_nest); score('c_upvoted_removed',$id,$comment['userid']); } else { $sql = ("insert into comments_votes (commentid,userid) values ('".escape($id)."','".escape($_SESSION['userid'])."')"); $query = mysql_query($sql); $sql_nest = ("update comments set votes = votes+1 where id = '".escape($id)."'"); $query_nest = mysql_query($sql_nest); score('c_upvoted',$id,$comment['userid']); } echo "1"; exit; } function del() { $id = sanitize($_POST['id'],"int"); $sql = ("delete from comments where id = '".escape($id)."' and userid = '".escape($_SESSION['userid'])."'"); $query = mysql_query($sql); echo "1Comment successfully deleted"; exit; } ================================================ FILE: controllers/helpers.php ================================================ set('user',$result); return $helper->render(); } ================================================ FILE: controllers/questions.php ================================================ EOD; $template->set('js',$js); } function edit() { authenticate(1); global $path; global $template; $basePath = basePath(); $basePathNS = basePathNS(); $questionid = sanitize($path[2],"int"); $js = << EOD; $template->set('js',$js); $sql = ("select * from questions where id = '".escape($questionid)."'"); $query = mysql_query($sql); $result = mysql_fetch_array($query); $template->set('title',$result['title']); $template->set('description',$result['description']); $template->set('link',$result['link']); $template->set('kb',$result['kb']); $sql = ("select tag from tags_questions, tags where questionid = '".escape($questionid)."' and tags.id = tags_questions.tagid order by tag"); $query = mysql_query($sql); $tags = array(); while ($result = mysql_fetch_array($query)) { $tags[] = $result['tag']; } $template->set('tags',$tags); $template->set('questionid',$questionid); } function post() { authenticate(1); $basePath = basePath(); $title = sanitize($_POST['title'],"string"); $description = sanitize($_POST['description'],"markdown"); $link = sanitize($_POST['link'],"url"); $slug = createSlug($title); $kb = 0; if (!empty($_POST['answercheck'])) { $kb = sanitize($_POST['answercheck'],"int"); } $cache = ''; if (!empty($link)) { $cache = fetchURL($link); } if (strlen($title) < 15 || strlen($description)<15) { header("Location: $basePath/questions/ask"); exit; } $sql = ("insert into questions (title,description,created,updated,link,userid,slug,linkcache,votes,accepted,answers,kb) values ('".escape($title)."','".escape($description)."',NOW(),NOW(),'".escape($link)."','".escape($_SESSION['userid'])."','".escape($slug)."','".escape($cache)."','0','0','0','".escape($kb)."')"); $query = mysql_query($sql); $questionid = mysql_insert_id(); if (!empty($_POST['tags'])) { foreach ($_POST['tags'] as $tag) { $tag = createSlug($tag); $sql = ("select * from tags where tag = '".escape($tag)."'"); $query = mysql_query($sql); $result = mysql_fetch_array($query); if ($result['id'] > 0) { $sql = ("insert into tags_questions (tagid,questionid) values ('".escape($result['id'])."','".escape($questionid)."')"); $query = mysql_query($sql); } else { $sql = ("insert into tags (tag) values ('".escape($tag)."')"); $query = mysql_query($sql); $tagid = mysql_insert_id(); $sql = ("insert into tags_questions (tagid,questionid) values ('".escape($tagid)."','".escape($questionid)."')"); $query = mysql_query($sql); } } } if (!empty($_POST['answer'])) { $description = sanitize($_POST['answer'],"markdown"); $sql = ("insert into answers (questionid,description,created,updated,userid,accepted,votes) values ('".escape($questionid)."','".escape($description)."',NOW(),NOW(),'".escape($_SESSION['userid'])."','1','0')"); $query = mysql_query($sql); } if ($kb == 1) { score('kb_posted',$questionid); } header("Location: $basePath/questions/view/$questionid/$slug"); } function update() { authenticate(1); $questionid = sanitize($_POST['id'],"int"); $title = sanitize($_POST['title'],"string"); $description = sanitize($_POST['description'],"markdown"); $link = sanitize($_POST['link'],"url"); $slug = createSlug($title); $kb = 0; if (!empty($_POST['answercheck'])) { $kb = sanitize($_POST['answercheck'],"int"); } $cache = ''; if (!empty($link)) { $cache = fetchURL($link); } $sql = ("select * from questions where id = '".escape($questionid)."'"); $query = mysql_query($sql); $result = mysql_fetch_array($query); if ($result['userid'] != $_SESSION['userid']) { $basePath = basePath(); header("Location: $basePath/questions/view/{$result['id']}/{$result['slug']}"); } $cacheup = ''; if ($result['link'] != $link) { $cacheup = ",linkcache = '".escape($cache)."'"; } if ($result['kb'] == 1 && $kb == 0) { score('kb_posted_removed',$questionid); } else if ($result['kb'] == 0 && $kb == 1) { score('kb_posted',$questionid); } $sql = ("update questions set title = '".escape($title)."', kb = '".escape($kb)."', description = '".escape($description)."' , updated = NOW(), link = '".escape($link)."', slug = '".escape($slug)."' $cacheup where userid = '".escape($_SESSION['userid'])."' and id = '".escape($questionid)."'"); $query = mysql_query($sql); echo mysql_error(); $sql = ("delete from tags_questions where questionid = '".escape($questionid)."'"); $query = mysql_query($sql); if (!empty($_POST['tags'])) { foreach ($_POST['tags'] as $tag) { $tag = createSlug($tag); $sql = ("select * from tags where tag = '".escape($tag)."'"); $query = mysql_query($sql); $result = mysql_fetch_array($query); if ($result['id'] > 0) { $sql = ("insert into tags_questions (tagid,questionid) values ('".escape($result['id'])."','".escape($questionid)."')"); $query = mysql_query($sql); } else { $sql = ("insert into tags (tag) values ('".escape($tag)."')"); $query = mysql_query($sql); $tagid = mysql_insert_id(); $sql = ("insert into tags_questions (tagid,questionid) values ('".escape($tagid)."','".escape($questionid)."')"); $query = mysql_query($sql); } } } $basePath = basePath(); header("Location: $basePath/questions/view/$questionid/$slug"); } function fetchtags() { noRender(); $tag = createSlug($_GET['tag']); header('Content-type: application/json; charset=utf-8'); $sql = ("select * from tags where tag LIKE '%".escape($tag)."%'"); $query = mysql_query($sql); $resultSet = array(); while ($result = mysql_fetch_array($query)) { $resultSet[] = array("caption" => $result['tag'], "value" => $result['tag']); } echo json_encode($resultSet); exit(); } function view() { global $path; global $template; $questionid = sanitize($path[2],"int"); $sql = ("select * from questions where id = '".escape($questionid)."'"); $query = mysql_query($sql); $result = mysql_fetch_array($query); $template->set('id',$result['id']); $template->set('userid',$result['userid']); $template->set('title',$result['title']); $template->set('created',$result['created']); $template->set('description',Markdown($result['description'])); $template->set('kb',$result['kb']); $template->set('link',$result['link']); $cache = 0; if (!empty($result['linkcache'])) { $cache = 1; } $template->set('cache',$cache); $sql = ("select tag from tags_questions, tags where questionid = '".escape($questionid)."' and tags.id = tags_questions.tagid order by tag"); $query = mysql_query($sql); $tags = array(); while ($result = mysql_fetch_array($query)) { $tags[] = $result['tag']; } $template->set('tags',$tags); $sql = ("select * from favorites where questionid = '".escape($questionid)."' and userid = '".escape($_SESSION['userid'])."'"); $query = mysql_query($sql); $result = mysql_fetch_array($query); $fave = 0; if ($result['id'] > 0) { $fave = 1; } $template->set('fave',$fave); $sql = ("select sum(vote) count from questions_votes where questionid = '".escape($questionid)."'"); $query = mysql_query($sql); $result = mysql_fetch_array($query); $votes = $result['count']; if ($votes == '') { $votes = 0; } $template->set('votes',$votes); $sql = ("select vote from questions_votes where questionid = '".escape($questionid)."' and userid = '".escape($_SESSION['userid'])."'"); $query = mysql_query($sql); $result = mysql_fetch_array($query); $nvote = 0; $pvote = 0; if ($result['vote'] == -1) { $nvote = 1; } if ($result['vote'] == 1) { $pvote = 1; } $template->set('nvote',$nvote); $template->set('pvote',$pvote); $sql = ("select comments.id,comment,comments.userid,users.name username, comments_votes.id voted, comments.votes from comments left join users on comments.userid = users.id left join comments_votes on (comments_votes.commentid = comments.id and comments_votes.userid = '".escape($_SESSION['userid'])."') where type = '0' and typeid = '".escape($questionid)."' order by comments.created asc"); $query = mysql_query($sql); $comments = array(); while ($result = mysql_fetch_array($query)) { $pos = strpos($result['username'],' '); if ($pos > 0) { $result['username'] = substr($result['username'],0,$pos); } $comments[] = array("id" => $result['id'], "comment" => $result['comment'], "userid" => $result['userid'], "username" => $result['username'], "voted" => $result['voted'], "votes" => $result['votes']); } $template->set('comments',$comments); $sql = ("select count(id) count from answers where questionid = '".escape($questionid)."'"); $query = mysql_query($sql); $result = mysql_fetch_array($query); $template->set('answerscount',$result['count']); $order = "votes desc"; $orderby = "votes"; $page = 1; if (!empty($_GET['order'])) { if ($_GET['order'] == "newest") { $order = "created desc"; $orderby = "newest"; } else if ($_GET['order'] == "oldest") { $order = "created asc"; $orderby = "oldest"; } } if (!empty($_GET['page'])) { $page = $_GET['page']; } $offset = ($page-1)*ANSWERS_PER_PAGE; $paging = new Pagination(); $paging->set('urlscheme','?order='.$orderby.'&page=%page%'); $paging->set('perpage',ANSWERS_PER_PAGE); $paging->set('page',$page); $paging->set('total',$result['count']); $paging->set('order',$orderby); $template->set('pagination',$paging->display()); $paging->set('urlscheme','?order=%label%&page=1'); $template->set('orderOptions',$paging->displayOptions()); $sqlanswer = ''; if ($page == 1) { $sqlanswer = "(select answers.*,users.name username from answers,users where questionid = '".escape($questionid)."' and answers.userid = users.id and answers.accepted = '1') UNION "; } $sql = ("$sqlanswer (select answers.*,users.name username from answers,users where questionid = '".escape($questionid)."' and answers.userid = users.id and answers.accepted = '0' order by $order, created desc LIMIT ".ANSWERS_PER_PAGE." OFFSET $offset)"); $query = mysql_query($sql); $answers = array(); while ($result = mysql_fetch_array($query)) { $sql_nest = ("select sum(vote) count from answers_votes where answerid = '".escape($result['id'])."'"); $query_nest = mysql_query($sql_nest); $result_nest = mysql_fetch_array($query_nest); $votes = $result_nest['count']; if ($votes == '') { $votes = 0; } $sql_nest = ("select vote from answers_votes where answerid = '".escape($result['id'])."' and userid = '".escape($_SESSION['userid'])."'"); $query_nest = mysql_query($sql_nest); $result_nest = mysql_fetch_array($query_nest); $nvote = 0; $pvote = 0; if ($result_nest['vote'] == -1) { $nvote = 1; } if ($result_nest['vote'] == 1) { $pvote = 1; } $sql_nest = ("select comments.id,comment,comments.userid,users.name username, comments_votes.id voted, comments.votes from comments left join users on comments.userid = users.id left join comments_votes on (comments_votes.commentid = comments.id and comments_votes.userid = '".escape($_SESSION['userid'])."') where type = '1' and typeid = '".escape($result['id'])."' order by comments.created asc"); $query_nest = mysql_query($sql_nest); $comments = array(); while ($result_nest = mysql_fetch_array($query_nest)) { $pos = strpos($result['username'],' '); if ($pos > 0) { $result['username'] = substr($result['username'],0,$pos); } $comments[] = array("id" => $result_nest['id'], "comment" => $result_nest['comment'], "userid" => $result_nest['userid'], "username" => $result['username'], "voted" => $result_nest['voted'], "votes" => $result_nest['votes']); } $answers[] = array ("description" => Markdown($result['description']), "created" => $result['created'], "updated" => $result['updated'], "userid" => $result['userid'], "username" => $result['username'], "pvote" => $pvote, "nvote" => $nvote, "votes" => $votes, "id" => $result['id'], "accepted"=> $result['accepted'], "comments" => $comments ); } $template->set('answers',$answers); $basePathNS = basePathNS(); $js = ''; if ($_SESSION['userid'] != '') { $js = << EOD; } $js .= << var basePath = "{$basePath}"; function vote(elem,type,voted) { \$this = $(elem); var id = \$this.parent().parent().parent().attr('id'); var add = 1; var minus = -1; var up = 'up'; var down = 'down'; if (voted == 'minus') { add = -1; minus = 1; up = 'down'; down = 'up'; } $.post(basePath+"/"+type+"/vote", { id: id, vote: voted }, function(data) { var result = data.substr(0,1); var message = data.substr(1); if (result == 1) { if (!$("#"+id+" .questionsview_"+up+"").hasClass("voteselected")) { $("#"+id+" .questionsview_vote").html(parseInt($("#"+id+" .questionsview_vote").html())+add); if ($("#"+id+" .questionsview_"+down+"").hasClass("voteselected")) { $("#"+id+" .questionsview_vote").html(parseInt($("#"+id+" .questionsview_vote").html())+add); $("#"+id+" .questionsview_"+down+"").removeClass("voteselected"); } } else { $("#"+id+" .questionsview_vote").html(parseInt($("#"+id+" .questionsview_vote").html())+minus); } \$this.toggleClass("voteselected"); } if (message != '') { $.fancyalert(message); } }); } $(document).ready(function() { $(".questionsview_answer .questionsview_up").click(function() { vote(this,'answers','plus'); }); $(".questionsview_answer .questionsview_down").click(function() { vote(this,'answers','minus'); }); $(".questionsview_question .questionsview_up").click(function() { vote(this,'questions','plus'); }); $(".questionsview_question .questionsview_down").click(function() { vote(this,'questions','minus'); }); $(".questionsview_question .questionsview_fave").click(function() { \$this = $(this); var id = \$this.parent().parent().parent().attr('id'); $.post(basePath+"/questions/fave", { id: id }, function(data) { var result = data.substr(0,1); var message = data.substr(1); if (result == 1) { \$this.toggleClass("voteselected"); } $.fancyalert(message); }); }); $(".commentfave").click(function() { var id = $(this).attr('id'); \$this = $(this); $.post(basePath+"/comments/vote", { id: id }, function(data) { var result = data.substr(0,1); var message = data.substr(1); if (result == "1") { if (\$this.next('div').html() == '') { \$this.next('div').html('0'); } if (!\$this.hasClass("voteselected")) { \$this.next('div').html(parseInt(\$this.next('div').html())+1); } else { \$this.next('div').html(parseInt(\$this.next('div').html())-1); } \$this.toggleClass("voteselected"); } if (message != '') { $.fancyalert(message); } }); }); $(".commentdel").click(function() { var answer = confirm("Delete this comment?") if (answer){ var id = $(this).attr('id'); \$this = $(this); $.post(basePath+"/comments/del", { id: id }, function(data) { var result = data.substr(0,1); var message = data.substr(1); if (result == "1") { \$this.parent().fadeOut(2000); } if (message != '') { $.fancyalert(message); } }); } }); var comments = $(".comments"); $.each(comments, function() { var elements = ( $('.comment:gt(4)',$(this)).size()); if (elements > 0) { $('.viewallcomments',$(this)).css('display','block'); $('.viewallcomments a',$(this)).html('View all comments ('+elements+' more)'); } allComments = $(".comment",$(this)).get(); allComments.sort(function(a,b) { a = $(".commentfavevotes",a).html(); b = $(".commentfavevotes",b).html();; if (a == '') a = 0; if (b == '') b = 0; if (a > b) { return -1; } else if (a < b) { return 1; } else { return 0; } }); $(allComments.slice(5)).hide(); }); }); function comment(id) { $("#comment_"+id).html(""); } function addcomment(id) { var comment = $("#commenttext_"+id).val(); if (comment.length < 10) { $.fancyalert('Your comment must be atleast 10 characters in length'); return; } if (comment.length > 600) { $.fancyalert('Your comment is too long, please reduce it to 600 characters'); return; } $("#commenttext_"+id).val(''); $.post(basePath+"/comments/post", { id: id, comment: comment }, function(data) { if (data == 0) { $("#commenttext_"+id).val(comment); $.fancyalert('Please login to post a comment'); } else { $("#comments_"+id).append(data); } }); } function viewallcomments(id){ $('#comments_'+id+' .comment').fadeIn(1000); $('#comment_'+id+' .viewallcomments').css('display','none'); } EOD; $template->set('js',$js); } function cache() { global $path; global $template; global $noheader; $noheader = true; $questionid = sanitize($path[2],"int"); $sql = ("select * from questions where id = '".escape($questionid)."'"); $query = mysql_query($sql); $result = mysql_fetch_array($query); $template->set('cachelink',$result['link']); $template->set('cache',$result['linkcache']); } function vote() { if ($_SESSION['userid'] == '') { echo "0Please login to vote"; exit; } $id = sanitize($_POST['id'],"int"); $vote = sanitize($_POST['vote'],"string"); if ($vote == 'plus') { $vote = '+1'; } else { $vote = '-1'; } $sql = ("select questions.userid,questions_votes.id qvid,questions_votes.vote qvvote from questions left join questions_votes on (questions.id = questions_votes.questionid and questions_votes.userid = '".escape($_SESSION['userid'])."') where questions.id = '".escape($id)."'"); $query = mysql_query($sql); $question = mysql_fetch_array($query); if ($question['userid'] == $_SESSION['userid']) { echo "0"."You cannot up/down vote your own question"; exit; } if ($question['qvid'] > 0) { if ($question['qvvote'] == 1 && $vote == '+1') { $vote = "-1"; score('q_upvoted_removed',$id,$question['userid']); } else if ($question['qvvote'] == 1 && $vote == '-1') { $vote = "-2"; score('q_upvoted_removed',$id,$question['userid']); score('q_downvoter',$id); score('q_downvoted',$id,$question['userid']); } else if ($question['qvvote'] == -1 && $vote == '-1') { $vote = "+1"; score('q_downvoter_removed',$id); score('q_downvoted_removed',$id,$question['userid']); } else if ($question['qvvote'] == -1 && $vote == '+1') { $vote = "+2"; score('q_downvoter_removed',$id); score('q_downvoted_removed',$id,$question['userid']); score('q_upvoted',$id,$question['userid']); } else if ($question['qvvote'] == 0) { if ($vote == 1) { score('q_upvoted',$id,$question['userid']); } else { score('q_downvoter',$id); score('q_downvoted',$id,$question['userid']); } } $sql = ("update questions_votes set vote = vote".escape($vote)." where id = '".$question['qvid']."'"); $query = mysql_query($sql); } else { $sql = ("insert into questions_votes (questionid,userid,vote) values ('".escape($id)."','".escape($_SESSION['userid'])."','".escape($vote)."')"); $query = mysql_query($sql); if ($vote == 1) { score('q_upvoted',$id,$question['userid']); } else { score('q_downvoter',$id); score('q_downvoted',$id,$question['userid']); } } $sql_nest = ("update questions set votes = votes".escape($vote)." where id = '".escape($id)."'"); $query_nest = mysql_query($sql_nest); echo "1Thankyou for voting"; exit; } function fave() { if ($_SESSION['userid'] == '') { echo "0Please login to add a question to your favorites"; exit; } $id = sanitize($_POST['id'],"int"); $sql = ("select * from favorites where questionid = '".escape($id)."' and userid = '".escape($_SESSION['userid'])."'"); $query = mysql_query($sql); $result = mysql_fetch_array($query); if ($result['id'] > 0) { $sql = ("delete from favorites where questionid = '".escape($id)."' and userid = '".escape($_SESSION['userid'])."'"); $query = mysql_query($sql); echo "1Question removed from your favorites"; } else { $sql = ("insert into favorites (questionid,userid) values ('".escape($id)."','".escape($_SESSION['userid'])."')"); $query = mysql_query($sql); echo "1Question added to your favorites"; } exit; } function index() { global $path; global $template; $conditionspre = ''; $conditionspost = ''; $conditionsselect = ''; $extratitle = ''; $orderby = 'newest'; $order = 'created desc'; $defaultorder = 1; $nopagination = 0; $page = 1; $searchstringoriginal = ''; if (!empty($_GET['search'])) { $searchstringoriginal = sanitize($_GET['search'],"string"); } if (!empty($_GET['order'])) { if ($_GET['order'] == "votes") { $order = "votes desc"; $orderby = "votes"; $defaultorder = 0; } else if ($_GET['order'] == "oldest") { $order = "created asc"; $orderby = "oldest"; $defaultorder = 0; } else if ($_GET['order'] == "relevance") { $order = "score desc"; $orderby = "relevance"; $defaultorder = 0; } else if ($_GET['order'] == "newest") { $defaultorder = 0; } } if (!empty($_GET['page'])) { $page = sanitize($_GET['page'],"int"); } $type = ''; if (!empty($_GET['type'])) { $type = "&type=".sanitize($_GET['type'],"string"); if (sanitize($_GET['type'],"string") == "unanswered") { // $conditionspost .= " questions.id NOT IN (select questions.id from questions,answers where questions.id = answers.questionid and answers.accepted = 1) and "; $conditionspost .= " questions.accepted = 0 and questions.kb = 0 and "; $extratitle = " not yet answered"; } else { $extratitle = " active"; $order = " updated desc "; $nopagination = 1; } } $template->set('nopagination',$nopagination); $search = ''; $searchstring = urldecode($searchstringoriginal); if (!empty($searchstringoriginal)) { $search = "&search=".urlencode($searchstring); $conditionspost .= " MATCH(title, description) AGAINST ('".escape($searchstring)."') and "; $conditionsselect .= ",MATCH(title, description) AGAINST ('".escape($searchstring)."') AS score "; $extratitle = " showing ".$searchstring; if ($defaultorder == 1) { $orderby = 'relevance'; $order = 'score desc'; } } $template->set('searchstring',$searchstring); $tag = ''; if (!empty($_GET['tag'])) { $tag = "&tag=".createSlug($_GET['tag']); $conditionspre .= ",tags_questions, tags"; $conditionspost .= " tags_questions.questionid = questions.id and tags.id = tags_questions.tagid and tags.tag LIKE '".escape(createSlug($_GET['tag']))."' and "; $extratitle = " tagged ".createSlug($_GET['tag']); } $offset = ($page-1)*QUESTIONS_PER_PAGE; $sql = ("select count(questions.id) count from questions $conditionspre WHERE $conditionspost 1"); $query = mysql_query($sql); $result = mysql_fetch_array(mysql_query($sql)); $template->set('questionscount',$result['count']); $paging = new Pagination(); $paging->set('urlscheme','?order='.$orderby.$tag.$type.$search.'&page=%page%'); $paging->set('perpage',QUESTIONS_PER_PAGE); $paging->set('page',$page); $paging->set('total',$result['count']); $paging->set('order',$orderby); $paging->set('search',$search); $template->set('pagination',$paging->display()); $paging->set('urlscheme','?order=%label%'.$tag.$type.$search.'&page=1'); $template->set('orderOptions',$paging->displayOptions()); $template->set('extratitle',$extratitle); $sql = ("select questions.* $conditionsselect from questions $conditionspre WHERE $conditionspost 1 order by $order, created desc LIMIT ".QUESTIONS_PER_PAGE." OFFSET $offset"); $query = mysql_query($sql); $questions = array(); while ($result = mysql_fetch_array($query)) { $sql_nest = ("select tag from tags_questions, tags where questionid = '".escape($result['id'])."' and tags.id = tags_questions.tagid order by tag"); $query_nest = mysql_query($sql_nest); $tags = array(); while ($result_nest = mysql_fetch_array($query_nest)) { $tags[] = $result_nest['tag']; } $description = truncate(trim(sanitize(Markdown($result['description']),"string"))); if (!empty($searchstring)) { $description = highlight(excerpt((trim(sanitize(Markdown($result['description']),"string"))),$searchstring),$searchstring); $result['title'] = highlight($result['title'],$searchstring); } $questions[] = array ("title" => $result['title'], "created" => $result['created'], "updated" => $result['updated'], "userid" => $result['userid'], "link" => $result['link'], "slug" => $result['slug'], "answers" => $result['answers'], "accepted" => $result['accepted'], "kb" => $result['kb'], "votes" => $result['votes'], "id" => $result['id'], "tags" => $tags, "description" => $description); } $template->set('questions',$questions); } ================================================ FILE: controllers/tags.php ================================================ set('count',$result['count']); $sql = ("select tag, count(tags_questions.questionid) tagcount from tags, tags_questions where tags.id = tags_questions.tagid group by tagid order by tagcount desc"); $query = mysql_query($sql); $tags = array(); while ($result = mysql_fetch_array($query)) { $tags[] = array ("tag" => $result['tag'], "count" => $result['tagcount']); } $template->set('tags',$tags); /* Add Pagination Later */ } ================================================ FILE: controllers/users.php ================================================ set('loginpage','1'); } function view() { global $path; global $template; $userid = sanitize($path[2],"int"); $sql = ("select * from users where id = '".escape($userid)."'"); $query = mysql_query($sql); $user = mysql_fetch_array($query); $basePath = basePath(); $template->set('user',$user); } function edit() { authenticate(1); global $template; $sql = ("select * from users where id = '".escape($_SESSION['userid'])."'"); $query = mysql_query($sql); $user = mysql_fetch_array($query); $basePath = basePath(); $template->set('user',$user); } function update() { authenticate(1); global $template; $name = sanitize($_POST['name'],"string"); $email = sanitize($_POST['email'],"email"); $password = sanitize($_POST['password'],"string"); $password = sha1(SALT.$password.$email); if (!empty($_POST['password'])) { $sql = ("update users set password = '".escape($password)."' where id = '".escape($_SESSION['userid'])."'"); $query = mysql_query($sql); } $sql = ("update users set name = '".escape($name)."', email = '".escape($email)."' where id = '".escape($_SESSION['userid'])."'"); $query = mysql_query($sql); $slug = createslug($name); $basePath = basePath(); header("Location: $basePath/users/view/{$_SESSION['userid']}/$slug"); } function validate() { $email = sanitize($_POST['email'],"email"); $password = sanitize($_POST['password'],"string"); $password = sha1(SALT.$password.$email); $sql = "select * from users where email = '".escape($email)."' and password = '".escape($password)."'"; $query_result = mysql_query($sql); $user = mysql_fetch_array($query_result); $basePath = basePath(); if ($user['id'] > 0) { $_SESSION['userid'] = $user['id']; $_SESSION['name'] = $user['name']; $_SESSION['email'] = $user['email']; $_SESSION['password'] = $user['password']; $_SESSION['points'] = $user['points']; if (!empty($_POST['returnurl'])) { $url = sanitize($_POST['returnurl'],"url"); header("Location: {$url}"); } else { header("Location: $basePath"); } } else { header("Location: $basePath/users/login"); } } function register() { } function create() { $name = sanitize($_POST['name'],"string"); $email = sanitize($_POST['email'],"email"); $password = sanitize($_POST['password'],"string"); $password = sha1(SALT.$password.$email); $sql = ("insert into users (name,email,password,points,moderator,created,lastactivity) values ('".escape($name)."','".escape($email)."','".escape($password)."','1','0',NOW(),NOW())"); $query = mysql_query($sql); validate(); } function logout() { session_destroy(); session_start(); $_SESSION['userid'] = ''; } function index() { global $template; $sql = ("select count(id) count from users"); $query = mysql_query($sql); $result = mysql_fetch_array($query); $template->set('count',$result['count']); $sql = ("select * from users order by points desc, name asc"); $query = mysql_query($sql); $users = array(); while ($result = mysql_fetch_array($query)) { $users[] = array ("id" => $result['id'], "name" => $result['name'], "points" => $result['points']); } $template->set('users',$users); /* Add Pagination Later */ } ================================================ FILE: css/main.css ================================================ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td { margin: 0; padding: 0; border: 0; outline: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; } /* remember to define focus styles! */ :focus { outline: 0; } body { line-height: 1; color: black; background: white; } ol, ul { line-height: 130%; } /* tables still need 'cellspacing="0"' in the markup */ table { border-collapse: separate; border-spacing: 0; } caption, th, td { text-align: left; font-weight: normal; } blockquote:before, blockquote:after, q:before, q:after { content: ""; } blockquote, q { quotes: "" ""; } body { font-family:Arial,"Bitstream Vera Sans",sans-serif; font-size:15px; color: #333; } p { color: #333333; } a { text-decoration: none; border-bottom: 1px dotted #333333; color: #333333; } pre { font-size: 11px; background-color: #D3F6FF; padding: 5px; max-height: 200px; overflow: auto; line-height: 130%; } * html pre { height: expression( this.scrollHeight > 199 ? "200px" : "auto" ); /* sets max-height for IE */ } #container { width: 960px; margin: 0 auto; } .textbox { width: 654px; border: 1px solid #d1dbbd; color: #666666; font-size: 15px; padding: 3px; } .textarea { width: 654px; border: 1px solid #d1dbbd; color: #666666; font-size: 15px; padding: 3px; height: 300px; } h1 { color: #333333; font-size: 20px; font-weight: bold; padding-left: 5px; padding-bottom: 7px; } h2 { color: #333333; font-size: 17px; font-weight: bold; padding-left: 5px; padding-top: 7px; padding-bottom: 7px; } h3 { color: #333333; font-size: 15px; font-weight: bold; padding-left: 5px; padding-top: 7px; padding-bottom: 7px; } p { line-height: 130%; } .button { border: 1px solid #ccc; color: #666666; font-size: 15px; padding: 3px; } strong { font-weight: bold; } /* questions/ask */ /* * html div#preview { height: expression( this.scrollHeight > 499 ? "500px" : "auto" ); /* sets max-height for IE } div#preview { max-height: 500px; /* sets max-height value for all standards-compliant browsers } */ .questionsview_rating { float: left; } .questionsview_box { font-weight: bold; font-size: 12px; margin-right: 20px; width: 38px; } .noborder { border: 0px !important; float:left; margin-top:10px !important; padding: 0px !important; } .nopadding { padding-right: 5px !important; } .questionsview_vote { color: #000; font-weight: bold; text-align: center; font-size: 30px; } .questionsview_down { padding-top: 3px; padding-bottom: 3px; text-align: center; font-size:25px; color: #555; cursor: pointer; } .questionsview_up { padding-top: 3px; padding-bottom: 3px; text-align: center; font-size:25px; color: #555; cursor: pointer; } .questionsview_fave { padding-top: 3px; padding-bottom: 3px; text-align: center; font-size:25px; color: #555; cursor: pointer; } .questionsview_question { padding-top: 15px; padding-bottom: 15px; margin-top: 10px; margin-bottom: 10px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; width: 658px; padding-left: 5px; padding-right: 5px; } .questionsview_answer { padding-top: 15px; padding-bottom: 15px; margin-top: 10px; margin-bottom: 0px; border-top: 1px solid #ccc; width: 658px; padding-left: 5px; padding-right: 5px; } .questionsview_form { padding-top: 15px; padding-bottom: 15px; margin-top: 10px; margin-bottom: 10px; border-top: 1px solid #ccc; width: 668px; } .questionsview_answerdescription { float:left; width: 600px; } .questionsview_questiondescription { float:left; width: 600px; } .voteselected { color: #21a5ca !important; font-weight: bold !important; } #navigation { background-color: #16bae2; margin-bottom: 30px; color: #ffffff; text-shadow: 0px 2px 3px #d3d4aa; font-size: 12px; font-weight: bold; border-bottom: 2px solid #21a5ca; } #navigation a { color: white; border-bottom: 0px; font-weight: bold; } .navcenter { width: 960px; margin: 0 auto; text-align:center; } .navcenter input { height: 20px; width: 400px; margin: 5px; font-size: 14px; padding: 2px; color: #666; -moz-border: 3px; border: 1px solid #21a5ca; margin-left: 0px !important; } #navigation ol { list-style: none; padding: 0; margin: 0 !important; } #navigation li { float: left; padding: 10px; padding-left:12px; padding-right:12px; border-right: 1px solid #13a1c9; border-left: 1px solid #45c9e9; } #navigation li:hover { background-color: #45c6e6; cursor:pointer; } body { margin-bottom: 30px; } #leftpanel { width: 658px; float: left; } #rightpanel { width: 250px; float:right; } .userdetails { border: 1px solid #d8dcdc; -moz-border-radius: 5px; padding: 10px; background-color: #eee; color: #666; font-size: 12px; font-weight: bold; line-height:1.3em; text-align:right; } .userlogin { border: 1px solid #ccc; -moz-border-radius: 5px; padding: 10px; background-color: #fff; color: #666; font-size: 12px; font-weight: bold; line-height:1.3em; margin-top: 10px; } .questionsview_userbox { float: right; } .userbox { padding: 3px; padding-right: 10px; padding-left: 10px; background-color: #eee; font-size: 10px; font-weight: bold; border: 1px solid #ccc; -moz-border-radius-bottomright:6px; -moz-border-radius-topleft:6px; color: #666; } .userbox a { border-bottom: 0px; } .questionsview_details { padding: 3px; padding-right: 10px; padding-left: 10px; background-color: #eee; font-size: 10px; font-weight: bold; border: 1px solid #ccc; -moz-border-radius-bottomright:6px; -moz-border-radius-topleft:6px; color: #666; float: right; margin-right: 10px; } .questionsview_details a { border-bottom: 0px; } .questionsview_options { padding: 3px; padding-right: 10px; padding-left: 10px; background-color: #eee; font-size: 10px; font-weight: bold; border: 1px solid #ccc; -moz-border-radius-bottomright:6px; -moz-border-radius-topleft:6px; color: #666; float: right; margin-right: 10px; } .questionsview_options a { border-bottom: 0px; } pre { margin-top: 10px; margin-bottom: 10px; font-size: 13px; } .pagination { float: left; padding-top: 7px; padding-bottom:7px; margin-left: 20px; } .pagination a { text-decoration: none; padding: 5px; border: 1px solid #eee; margin-right: 4px; } .selected { text-decoration: none; padding: 5px; background-color: #eee; border: 1px solid #eee; font-weight: bold; margin-right: 4px; cursor: pointer; } .bottompagination { border-top: 1px solid #ccc; padding-top: 10px; width: 668px; } .toppagination { border-bottom: 1px solid #ccc; padding-bottom: 10px; margin-bottom: 10px; width: 668px; } .questionsgrid_toppagination { padding-bottom: 10px; padding-top: 10px; width: 668px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; margin-top: 10px; margin-bottom: 0px; } .questionsgrid_bottompagination { padding-bottom: 10px; padding-top: 10px; width: 668px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } .questionsview_accepted { padding: 3px; padding-right: 10px; padding-left: 10px; font-size: 10px; font-weight: bold; -moz-border-radius-bottomright:6px; -moz-border-radius-topleft:6px; float: right; margin-right: 10px; border:1px solid #0d9fca; background-color:#14abd7; color:#fff; } .questionsview_accept { padding: 3px; padding-right: 10px; padding-left: 10px; font-size: 10px; font-weight: bold; -moz-border-radius-bottomright:6px; -moz-border-radius-topleft:6px; float: right; margin-right: 10px; border:1px solid #6dc900; background-color: #6dc900; color:#fff; cursor: pointer; } .questionsview_accept a { text-decoration: none; border-bottom: 0px; color: #fff; } .questionsgrid_title a { font-size: 16px; font-weight: bold; border-bottom: 0px; color: #666; } .questionsgrid_description a { font-size: 14px; color: #666; font-weight: normal; } .tags_list { line-height: 1.5em; border-top:1px solid #ccc; margin-top: 10px; padding-top: 10px; } .tags_list li { margin-top: 5px !important; margin-right: 10px !important; } .highlight { background-color: #d3f6ff; color: #333; } .comments { clear: both; padding-top: 20px; } .comments a { font-size: 12px; font-weight: bold; color: #14abd7; text-decoration: none; border-bottom: 1px dotted #14abd7; } .commentsadd { border-top:1px dotted #ccc; padding-top: 10px; width: 580px; padding-left: 5px; padding-right: 5px; } .commenttextarea { width: 570px; border: 1px solid #ccc; color: #666666; font-size: 12px; padding: 3px; height: 50px; } .smallbutton { border: 1px solid #ccc; color: #666666; font-size: 11px; padding: 2px; margin-top: 2px; } .comment { border-top:1px dotted #ccc; padding-top: 5px; padding-bottom: 5px; width: 580px; padding-left: 5px; padding-right: 5px; font-size: 12px; color: #666; line-height: 1.4em; } .comment a { color: #666; border-bottom: 1px dotted #666; } .viewallcomments { display:none; } .comment_text { float:left; width:520px; padding-right: 10px; } .commentfave { float:left; font-size:15px; font-weight:bold; padding-top:2px; width:15px; color:#999; text-align:center; cursor: pointer; } .commentdel { float:left; font-size:15px; font-weight:bold; padding-top:1px; width:15px; color:#999; text-align:center; cursor: pointer; } .commentfavevotes { float:left; font-size:15px; font-weight:bold; padding-top:2px; width:15px; color:#ccc; text-align:left; } #alert { overflow: hidden; width: 100%; text-align: center; position: fixed; top: 0; left: 0; background-color: #d3f6ff; height: 0; color: #333; font: 17px/36px arial, sans-serif; font-weight: bold; } html { overflow: -moz-scrollbars-vertical; overflow-y: scroll; } /* markdown */ .markdown { padding-top: 5px; } .markdown h1, .markdown h2, .markdown h3, .markdown h4 { padding-top:7px; padding-left:0px; } .markdown hr { color: #ccc; background-color: #ccc; height: 1px; border: 0px; } .markdown ol, .markdown li { margin-left: 15px; } .markdown blockquotes { border-left: 1px solid #ccc; } .markdown em { font-style: italic; } .questionsgrid_description { padding-top: 5px; display: block; line-height: 1.1em; } .markdown { line-height: 1.2em; } .textalert { border: 1px solid #21a5ca !important; } .q { background-color:#14abd7; border:1px solid #14abd7; color:#fff; margin-left:10px; } .kb { background-color:#d3f6ff; border:1px solid #d3f6ff; color:#333; margin-left:10px; } .footer { width: 940px; text-align: right; margin-top: 40px; padding-right:20px; padding-top: 10px; border-top: 1px dotted #ccc; display: block; font-family: verdana; font-size: 10px; padding-bottom:10px; line-height:15px; margin: 0 auto; } ================================================ FILE: css/prettify.css ================================================ /* Pretty printing styles. Used with prettify.js. */ .str { color: #080; } .kwd { color: #075c70; } .com { color: #800; } .typ { color: #606; } .lit { color: #066; } .pun { color: #16bae2; } .pln { color: #000; } .tag { color: #008; } .atn { color: #606; } .atv { color: #080; } .dec { color: #606; } pre.prettyprint { } @media print { .str { color: #060; } .kwd { color: #006; font-weight: bold; } .com { color: #600; font-style: italic; } .typ { color: #404; font-weight: bold; } .lit { color: #044; } .pun { color: #440; } .pln { color: #000; } .tag { color: #006; font-weight: bold; } .atn { color: #404; } .atv { color: #060; } } ================================================ FILE: css/tagscomplete.css ================================================ ul.holder { margin: 0; border: 1px solid #d1dbbd; overflow: hidden; height: auto !important; height: 1%; padding: 4px 5px 0; color: #333;} *:first-child+html ul.holder { padding-bottom: 2px; } * html ul.holder { padding-bottom: 2px; } /* ie7 and below */ ul.holder li { float: left; list-style-type: none; margin: 0 5px 2px 0; white-space:nowrap;} ul.holder li.bit-box, ul.holder li.bit-input input { font: 10px "Lucida Grande", "Verdana"; } ul.holder li.bit-box { -moz-border-radius-topleft: 6px;-moz-border-radius-bottomright: 6px; color:#797953; -webkit-border-radius: 6px; border-radius: 6px; border: 1px solid #d3d4aa; background: #e7e9d1; padding: 1px 5px 2px; } ul.holder li.bit-box-focus { border-color: #d3d4aa; background: #CAD8F3; color: #fff; } ul.holder li.bit-input input { width: auto; overflow:visible; margin: 0; border: 0px; outline: 0; } /* no left/right padding here please */ ul.holder li.bit-input input.smallinput { width: 20px; } #add { border: 1px solid #ccc; width: 550px; margin: 50px; padding: 20px 30px 10px; } form .holder ol li { list-style-type: none; } form .holder ol { font: 11px "Lucida Grande", "Verdana"; margin: 0; padding: 0; } form .holder ol li.input-text { margin-bottom: 10px; list-style-type: none; padding-bottom: 10px; } form .holder ol li.input-text label { font-weight: bold; cursor: pointer; display: block; font-size: 13px; margin-bottom: 10px; } form .holder ol li.input-text input { width: 588px; padding: 3px; font: 11px "Lucida Grande", "Verdana"; border: 1px solid #fff; } form ul.holder { width: 650px; } form .holder ul { margin: 0 !important } ul.holder li.bit-box, #apple-list ul.holder li.bit-box { padding-right: 15px; position: relative; z-index:1000;} #apple-list ul.holder li.bit-input { margin: 0; } #apple-list ul.holder li.bit-input input.smallinput { width: 5px; } ul.holder li.bit-hover { background: #BBCEF1; border: 1px solid #6D95E0; } ul.holder li.bit-box-focus { border-color: #598BEC; background: #ffffff; color: #fff; } ul.holder li.bit-box a.closebutton { position: absolute; right: 4px; top: 5px; display: block; width: 7px; height: 7px; font-size: 1px; background: url('/qwench/img/close.gif'); } ul.holder li.bit-box a.closebutton:hover { background-position: 7px; } ul.holder li.bit-box-focus a.closebutton, ul.holder li.bit-box-focus a.closebutton:hover { background-position: bottom; } ul a { border-bottom: 0px; } .facebook-auto { display: none; position: absolute; width: 512px; background: #fff; border-bottom: 0px; } .facebook-auto .default { padding: 5px 7px; border: 1px solid #ccc; border-width: 0 1px 1px;font-family:"Lucida Grande","Verdana"; font-size:11px; } .facebook-auto ul { display: none; margin: 0; padding: 0; overflow: auto; position:absolute; z-index:9999} .facebook-auto ul li { padding: 5px 12px; z-index: 1000; cursor: pointer; margin: 0; list-style-type: none; border: 1px solid #ccc; border-width: 0 1px 1px; font: 11px "Lucida Grande", "Verdana"; background-color: #fff } .facebook-auto ul li em { font-weight: bold; font-style: normal; background: #ccc; } .facebook-auto ul li.auto-focus { background: #e7e9d1; color: #333; } .facebook-auto ul li.auto-focus em { background: none; } .deleted { background-color:#d3d4aa !important; } .hidden { display:none;} #demo ul.holder li.bit-input input { padding: 2px 0 1px; border: 1px solid #ccc; } .ie6fix {height:1px;width:1px; position:absolute;top:0px;left:0px;z-index:1;} ================================================ FILE: css/wmd.css ================================================ .wmd-panel { } #wmd-editor { } #wmd-button-bar { width: 100%; padding-top: 5px; padding-bottom: 5px; } #wmd-input { width: 654px; border: 1px solid #d1dbbd; color: #666666; font-size: 15px; padding: 3px; height: 300px; } #wmd-preview { overflow: auto; background-color: #EFFCFF; width: 633px; padding: 15px; } #wmd-output { background-color: Pink; } #wmd-button-row { position: relative; margin-left: 5px; margin-right: 5px; margin-bottom: 5px; margin-top: 10px; padding: 0px; height: 20px; } .wmd-spacer { width: 1px; height: 20px; margin-left: 14px; position: absolute; background-color: #fff; display: inline-block; list-style: none; } .wmd-button { width: 20px; height: 20px; margin-left: 5px; margin-right: 5px; position: absolute; background-image: url(../img/wmd-buttons.png); background-repeat: no-repeat; background-position: 0px 0px; display: inline-block; list-style: none; } .wmd-button > a { width: 20px; height: 20px; margin-left: 5px; margin-right: 5px; position: absolute; display: inline-block; } /* sprite button slicing style information */ #wmd-button-bar #wmd-bold-button {left: 0px; background-position: 0px 0;} #wmd-button-bar #wmd-italic-button {left: 25px; background-position: -20px 0;} #wmd-button-bar #wmd-spacer1 {left: 50px;} #wmd-button-bar #wmd-link-button {left: 75px; background-position: -40px 0;} #wmd-button-bar #wmd-quote-button {left: 100px; background-position: -60px 0;} #wmd-button-bar #wmd-code-button {left: 125px; background-position: -80px 0;} #wmd-button-bar #wmd-image-button {left: 150px; background-position: -100px 0;} #wmd-button-bar #wmd-spacer2 {left: 175px;} #wmd-button-bar #wmd-olist-button {left: 200px; background-position: -120px 0;} #wmd-button-bar #wmd-ulist-button {left: 225px; background-position: -140px 0;} #wmd-button-bar #wmd-heading-button {left: 250px; background-position: -160px 0;} #wmd-button-bar #wmd-hr-button {left: 275px; background-position: -180px 0;} #wmd-button-bar #wmd-spacer3 {left: 300px;} #wmd-button-bar #wmd-undo-button {left: 325px; background-position: -200px 0;} #wmd-button-bar #wmd-redo-button {left: 350px; background-position: -220px 0;} #wmd-button-bar #wmd-help-button {right: 0px; background-position: -240px 0;} .wmd-prompt-background { background-color: Black; } .wmd-prompt-dialog { border: 1px solid #999999; background-color: #F5F5F5; } .wmd-prompt-dialog > div { font-size: 0.8em; font-family: arial, helvetica, sans-serif; } .wmd-prompt-dialog > form > input[type="text"] { border: 1px solid #999999; color: black; } .wmd-prompt-dialog > form > input[type="button"]{ border: 1px solid #888888; font-family: trebuchet MS, helvetica, sans-serif; font-size: 0.8em; font-weight: bold; } ================================================ FILE: db/1.txt ================================================ -- MySQL dump 10.11 -- -- Host: localhost Database: qwench -- ------------------------------------------------------ -- Server version 5.0.51b-community-nt /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- -- Table structure for table `answers` -- DROP TABLE IF EXISTS `answers`; SET @saved_cs_client = @@character_set_client; SET character_set_client = utf8; CREATE TABLE `answers` ( `id` int(10) unsigned NOT NULL auto_increment, `questionid` int(10) unsigned NOT NULL, `description` text character set latin1 NOT NULL, `created` timestamp NOT NULL default '0000-00-00 00:00:00', `updated` timestamp NOT NULL default '0000-00-00 00:00:00', `userid` int(10) unsigned NOT NULL, `accepted` int(10) unsigned NOT NULL, `votes` int(11) NOT NULL default '0', PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=26 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; SET character_set_client = @saved_cs_client; -- -- Table structure for table `answers_votes` -- DROP TABLE IF EXISTS `answers_votes`; SET @saved_cs_client = @@character_set_client; SET character_set_client = utf8; CREATE TABLE `answers_votes` ( `id` int(10) unsigned NOT NULL auto_increment, `answerid` int(10) unsigned NOT NULL, `userid` int(10) unsigned NOT NULL, `vote` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=72 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; SET character_set_client = @saved_cs_client; -- -- Table structure for table `comments` -- DROP TABLE IF EXISTS `comments`; SET @saved_cs_client = @@character_set_client; SET character_set_client = utf8; CREATE TABLE `comments` ( `id` int(10) unsigned NOT NULL auto_increment, `type` int(10) unsigned NOT NULL, `comment` text character set latin1 NOT NULL, `votes` int(10) unsigned NOT NULL default '0', `created` timestamp NOT NULL default '0000-00-00 00:00:00', `userid` int(10) unsigned NOT NULL, `typeid` int(10) unsigned NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=49 DEFAULT CHARSET=utf8; SET character_set_client = @saved_cs_client; -- -- Table structure for table `comments_votes` -- DROP TABLE IF EXISTS `comments_votes`; SET @saved_cs_client = @@character_set_client; SET character_set_client = utf8; CREATE TABLE `comments_votes` ( `id` int(10) unsigned NOT NULL auto_increment, `commentid` int(10) unsigned NOT NULL, `userid` int(10) unsigned NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=18 DEFAULT CHARSET=utf8; SET character_set_client = @saved_cs_client; -- -- Table structure for table `favorites` -- DROP TABLE IF EXISTS `favorites`; SET @saved_cs_client = @@character_set_client; SET character_set_client = utf8; CREATE TABLE `favorites` ( `id` int(10) unsigned NOT NULL auto_increment, `questionid` int(10) unsigned NOT NULL, `userid` int(10) unsigned NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; SET character_set_client = @saved_cs_client; -- -- Table structure for table `questions` -- DROP TABLE IF EXISTS `questions`; SET @saved_cs_client = @@character_set_client; SET character_set_client = utf8; CREATE TABLE `questions` ( `id` int(10) unsigned NOT NULL auto_increment, `title` text character set latin1 NOT NULL, `description` text character set latin1 NOT NULL, `created` timestamp NOT NULL default '0000-00-00 00:00:00', `updated` timestamp NOT NULL default '0000-00-00 00:00:00', `link` text character set latin1 NOT NULL, `userid` int(10) unsigned NOT NULL, `slug` text character set latin1 NOT NULL, `linkcache` longtext character set latin1 NOT NULL, `votes` int(11) NOT NULL default '0', PRIMARY KEY (`id`), FULLTEXT KEY `title` (`title`,`description`) ) ENGINE=MyISAM AUTO_INCREMENT=28 DEFAULT CHARSET=utf8; SET character_set_client = @saved_cs_client; -- -- Table structure for table `questions_votes` -- DROP TABLE IF EXISTS `questions_votes`; SET @saved_cs_client = @@character_set_client; SET character_set_client = utf8; CREATE TABLE `questions_votes` ( `id` int(10) unsigned NOT NULL auto_increment, `questionid` int(10) unsigned NOT NULL, `userid` int(10) unsigned NOT NULL, `vote` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=38 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; SET character_set_client = @saved_cs_client; -- -- Table structure for table `tags` -- DROP TABLE IF EXISTS `tags`; SET @saved_cs_client = @@character_set_client; SET character_set_client = utf8; CREATE TABLE `tags` ( `id` int(10) unsigned NOT NULL auto_increment, `tag` varchar(255) character set latin1 NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=27 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; SET character_set_client = @saved_cs_client; -- -- Table structure for table `tags_questions` -- DROP TABLE IF EXISTS `tags_questions`; SET @saved_cs_client = @@character_set_client; SET character_set_client = utf8; CREATE TABLE `tags_questions` ( `id` int(10) unsigned NOT NULL auto_increment, `tagid` int(10) unsigned NOT NULL, `questionid` int(10) unsigned NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=93 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; SET character_set_client = @saved_cs_client; -- -- Table structure for table `users` -- DROP TABLE IF EXISTS `users`; SET @saved_cs_client = @@character_set_client; SET character_set_client = utf8; CREATE TABLE `users` ( `id` int(10) unsigned NOT NULL auto_increment, `username` varchar(255) character set latin1 NOT NULL, `password` varchar(255) character set latin1 NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; SET character_set_client = @saved_cs_client; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2009-09-11 7:03:59 ================================================ FILE: db/2.txt ================================================ ALTER TABLE `users` ADD COLUMN `name` VARCHAR(255) NOT NULL AFTER `password`, ADD COLUMN `email` VARCHAR(255) NOT NULL AFTER `name`, ADD COLUMN `points` INTEGER UNSIGNED NOT NULL AFTER `email`, ADD COLUMN `moderator` INTEGER UNSIGNED NOT NULL AFTER `points`; ALTER TABLE `users` DROP COLUMN `username`; ALTER TABLE `users` ADD COLUMN `created` DATETIME NOT NULL AFTER `moderator`, ADD COLUMN `lastactivity` DATETIME NOT NULL AFTER `created`; ================================================ FILE: db/3.txt ================================================ CREATE TABLE `activities` ( `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, `userid` INTEGER UNSIGNED NOT NULL, `activity` INTEGER UNSIGNED NOT NULL, `points` INTEGER NOT NULL, PRIMARY KEY (`id`) ) ENGINE = MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE `questions` ADD COLUMN `accepted` INTEGER UNSIGNED NOT NULL DEFAULT 0 AFTER `votes`, ADD COLUMN `answers` INTEGER UNSIGNED NOT NULL DEFAULT 0 AFTER `accepted`, ADD COLUMN `kb` INTEGER UNSIGNED NOT NULL DEFAULT 0 AFTER `answers`; ALTER TABLE `activities` MODIFY COLUMN `activity` VARCHAR(255) NOT NULL DEFAULT ''; ALTER TABLE `activities` ADD COLUMN `created` DATETIME NOT NULL AFTER `points`; ALTER TABLE `activities` ADD COLUMN `activityid` INTEGER UNSIGNED NOT NULL AFTER `created`; ALTER TABLE `users` MODIFY COLUMN `points` INTEGER NOT NULL DEFAULT 0; ================================================ FILE: index.php ================================================ render($noheader); } ================================================ FILE: install.php ================================================ Please edit the config.php file to match you database settings.'; } else { $body = ''; $path = ''; $rollback = 0; $errors = ''; $content = << 4) { $result = mysql_query($query); if (!$result) { $rollback = 1; $errors .= mysql_error()."
\n"; } } } } ?> Install

Security Alert!!!
Delete the install.php file from your server.

Try out your shiny new server. click here
================================================ FILE: js/fancyalert.js ================================================ /* jQuery Fancy Alert Copyright (c) 2009 Anant Garg (http://anantgarg.com) Version: 1.0 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ (function($){ $.fancyalert = function(message){ if ($("#alert").length > 0) { $("#alert").html(message); } else { var html = '
'+message+'
'; $('body').append(html); } $alert = $('#alert'); if($alert.length) { var alerttimer = window.setTimeout(function () { $alert.trigger('click'); }, 3000); $alert.css('border-bottom','2px solid #76e3ff'); $alert.animate({height: $alert.css('line-height') || '50px'}, 200) .click(function () { window.clearTimeout(alerttimer); $alert.animate({height: '0'}, 200); $alert.css('border-bottom','0px solid #333333'); }); } }; })(jQuery); ================================================ FILE: js/jquery.js ================================================ /* * jQuery JavaScript Library v1.3.2 * http://jquery.com/ * * Copyright (c) 2009 John Resig * Dual licensed under the MIT and GPL licenses. * http://docs.jquery.com/License * * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) * Revision: 6246 */ (function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
","
"]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); /* * Sizzle CSS Selector Engine - v0.9.3 * Copyright 2009, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ (function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); ================================================ FILE: js/prettify/lang-css.js ================================================ PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[ \t\r\n\f]+/,null," \t\r\n\u000c"]],[["str",/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],["str",/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],["kwd",/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],["com", /^(?: ) [ ]* (?=\n{2,}|\Z) # followed by a blank line or end of document | # PHP and ASP-style processor instructions ( ) [ ]* (?=\n{2,}|\Z) # followed by a blank line or end of document ) )}Sxmi', array(&$this, '_hashHTMLBlocks_callback'), $text); return $text; } function _hashHTMLBlocks_callback($matches) { $text = $matches[1]; $key = $this->hashBlock($text); return "\n\n$key\n\n"; } function hashPart($text, $boundary = 'X') { # # Called whenever a tag must be hashed when a function insert an atomic # element in the text stream. Passing $text to through this function gives # a unique text-token which will be reverted back when calling unhash. # # The $boundary argument specify what character should be used to surround # the token. By convension, "B" is used for block elements that needs not # to be wrapped into paragraph tags at the end, ":" is used for elements # that are word separators and "X" is used in the general case. # # Swap back any tag hash found in $text so we do not have to `unhash` # multiple times at the end. $text = $this->unhash($text); # Then hash the block. static $i = 0; $key = "$boundary\x1A" . ++$i . $boundary; $this->html_hashes[$key] = $text; return $key; # String that will replace the tag. } function hashBlock($text) { # # Shortcut function for hashPart with block-level boundaries. # return $this->hashPart($text, 'B'); } var $block_gamut = array( # # These are all the transformations that form block-level # tags like paragraphs, headers, and list items. # "doHeaders" => 10, "doHorizontalRules" => 20, "doLists" => 40, "doCodeBlocks" => 50, "doBlockQuotes" => 60, ); function runBlockGamut($text) { # # Run block gamut tranformations. # # We need to escape raw HTML in Markdown source before doing anything # else. This need to be done for each block, and not only at the # begining in the Markdown function since hashed blocks can be part of # list items and could have been indented. Indented blocks would have # been seen as a code block in a previous pass of hashHTMLBlocks. $text = $this->hashHTMLBlocks($text); return $this->runBasicBlockGamut($text); } function runBasicBlockGamut($text) { # # Run block gamut tranformations, without hashing HTML blocks. This is # useful when HTML blocks are known to be already hashed, like in the first # whole-document pass. # foreach ($this->block_gamut as $method => $priority) { $text = $this->$method($text); } # Finally form paragraph and restore hashed blocks. $text = $this->formParagraphs($text); return $text; } function doHorizontalRules($text) { # Do Horizontal Rules: return preg_replace( '{ ^[ ]{0,3} # Leading space ([-*_]) # $1: First marker (?> # Repeated marker group [ ]{0,2} # Zero, one, or two spaces. \1 # Marker character ){2,} # Group repeated at least twice [ ]* # Tailing spaces $ # End of line. }mx', "\n".$this->hashBlock("empty_element_suffix")."\n", $text); } var $span_gamut = array( # # These are all the transformations that occur *within* block-level # tags like paragraphs, headers, and list items. # # Process character escapes, code spans, and inline HTML # in one shot. "parseSpan" => -30, # Process anchor and image tags. Images must come first, # because ![foo][f] looks like an anchor. "doImages" => 10, "doAnchors" => 20, # Make links out of things like `` # Must come after doAnchors, because you can use < and > # delimiters in inline links like [this](). "doAutoLinks" => 30, "encodeAmpsAndAngles" => 40, "doItalicsAndBold" => 50, "doHardBreaks" => 60, ); function runSpanGamut($text) { # # Run span gamut tranformations. # foreach ($this->span_gamut as $method => $priority) { $text = $this->$method($text); } return $text; } function doHardBreaks($text) { # Do hard breaks: return preg_replace_callback('/ {2,}\n/', array(&$this, '_doHardBreaks_callback'), $text); } function _doHardBreaks_callback($matches) { return $this->hashPart("empty_element_suffix\n"); } function doAnchors($text) { # # Turn Markdown link shortcuts into XHTML tags. # if ($this->in_anchor) return $text; $this->in_anchor = true; # # First, handle reference-style links: [link text] [id] # $text = preg_replace_callback('{ ( # wrap whole match in $1 \[ ('.$this->nested_brackets_re.') # link text = $2 \] [ ]? # one optional space (?:\n[ ]*)? # one optional newline followed by spaces \[ (.*?) # id = $3 \] ) }xs', array(&$this, '_doAnchors_reference_callback'), $text); # # Next, inline-style links: [link text](url "optional title") # $text = preg_replace_callback('{ ( # wrap whole match in $1 \[ ('.$this->nested_brackets_re.') # link text = $2 \] \( # literal paren [ ]* (?: <(\S*)> # href = $3 | ('.$this->nested_url_parenthesis_re.') # href = $4 ) [ ]* ( # $5 ([\'"]) # quote char = $6 (.*?) # Title = $7 \6 # matching quote [ ]* # ignore any spaces/tabs between closing quote and ) )? # title is optional \) ) }xs', array(&$this, '_DoAnchors_inline_callback'), $text); # # Last, handle reference-style shortcuts: [link text] # These must come last in case you've also got [link test][1] # or [link test](/foo) # // $text = preg_replace_callback('{ // ( # wrap whole match in $1 // \[ // ([^\[\]]+) # link text = $2; can\'t contain [ or ] // \] // ) // }xs', // array(&$this, '_doAnchors_reference_callback'), $text); $this->in_anchor = false; return $text; } function _doAnchors_reference_callback($matches) { $whole_match = $matches[1]; $link_text = $matches[2]; $link_id =& $matches[3]; if ($link_id == "") { # for shortcut links like [this][] or [this]. $link_id = $link_text; } # lower-case and turn embedded newlines into spaces $link_id = strtolower($link_id); $link_id = preg_replace('{[ ]?\n}', ' ', $link_id); if (isset($this->urls[$link_id])) { $url = $this->urls[$link_id]; $url = $this->encodeAttribute($url); $result = "titles[$link_id] ) ) { $title = $this->titles[$link_id]; $title = $this->encodeAttribute($title); $result .= " title=\"$title\""; } $link_text = $this->runSpanGamut($link_text); $result .= ">$link_text"; $result = $this->hashPart($result); } else { $result = $whole_match; } return $result; } function _doAnchors_inline_callback($matches) { $whole_match = $matches[1]; $link_text = $this->runSpanGamut($matches[2]); $url = $matches[3] == '' ? $matches[4] : $matches[3]; $title =& $matches[7]; $url = $this->encodeAttribute($url); $result = "encodeAttribute($title); $result .= " title=\"$title\""; } $link_text = $this->runSpanGamut($link_text); $result .= ">$link_text"; return $this->hashPart($result); } function doImages($text) { # # Turn Markdown image shortcuts into tags. # # # First, handle reference-style labeled images: ![alt text][id] # $text = preg_replace_callback('{ ( # wrap whole match in $1 !\[ ('.$this->nested_brackets_re.') # alt text = $2 \] [ ]? # one optional space (?:\n[ ]*)? # one optional newline followed by spaces \[ (.*?) # id = $3 \] ) }xs', array(&$this, '_doImages_reference_callback'), $text); # # Next, handle inline images: ![alt text](url "optional title") # Don't forget: encode * and _ # $text = preg_replace_callback('{ ( # wrap whole match in $1 !\[ ('.$this->nested_brackets_re.') # alt text = $2 \] \s? # One optional whitespace character \( # literal paren [ ]* (?: <(\S*)> # src url = $3 | ('.$this->nested_url_parenthesis_re.') # src url = $4 ) [ ]* ( # $5 ([\'"]) # quote char = $6 (.*?) # title = $7 \6 # matching quote [ ]* )? # title is optional \) ) }xs', array(&$this, '_doImages_inline_callback'), $text); return $text; } function _doImages_reference_callback($matches) { $whole_match = $matches[1]; $alt_text = $matches[2]; $link_id = strtolower($matches[3]); if ($link_id == "") { $link_id = strtolower($alt_text); # for shortcut links like ![this][]. } $alt_text = $this->encodeAttribute($alt_text); if (isset($this->urls[$link_id])) { $url = $this->encodeAttribute($this->urls[$link_id]); $result = "\"$alt_text\"";titles[$link_id])) { $title = $this->titles[$link_id]; $title = $this->encodeAttribute($title); $result .= " title=\"$title\""; } $result .= $this->empty_element_suffix; $result = $this->hashPart($result); } else { # If there's no such link ID, leave intact: $result = $whole_match; } return $result; } function _doImages_inline_callback($matches) { $whole_match = $matches[1]; $alt_text = $matches[2]; $url = $matches[3] == '' ? $matches[4] : $matches[3]; $title =& $matches[7]; $alt_text = $this->encodeAttribute($alt_text); $url = $this->encodeAttribute($url); $result = "\"$alt_text\"";encodeAttribute($title); $result .= " title=\"$title\""; # $title already quoted } $result .= $this->empty_element_suffix; return $this->hashPart($result); } function doHeaders($text) { # Setext-style headers: # Header 1 # ======== # # Header 2 # -------- # $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx', array(&$this, '_doHeaders_callback_setext'), $text); # atx-style headers: # # Header 1 # ## Header 2 # ## Header 2 with closing hashes ## # ... # ###### Header 6 # $text = preg_replace_callback('{ ^(\#{1,6}) # $1 = string of #\'s [ ]* (.+?) # $2 = Header text [ ]* \#* # optional closing #\'s (not counted) \n+ }xm', array(&$this, '_doHeaders_callback_atx'), $text); return $text; } function _doHeaders_callback_setext($matches) { # Terrible hack to check we haven't found an empty list item. if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1])) return $matches[0]; $level = $matches[2]{0} == '=' ? 1 : 2; $block = "".$this->runSpanGamut($matches[1]).""; return "\n" . $this->hashBlock($block) . "\n\n"; } function _doHeaders_callback_atx($matches) { $level = strlen($matches[1]); $block = "".$this->runSpanGamut($matches[2]).""; return "\n" . $this->hashBlock($block) . "\n\n"; } function doLists($text) { # # Form HTML ordered (numbered) and unordered (bulleted) lists. # $less_than_tab = $this->tab_width - 1; # Re-usable patterns to match list item bullets and number markers: $marker_ul_re = '[*+-]'; $marker_ol_re = '\d+[.]'; $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; $markers_relist = array($marker_ul_re, $marker_ol_re); foreach ($markers_relist as $marker_re) { # Re-usable pattern to match any entirel ul or ol list: $whole_list_re = ' ( # $1 = whole list ( # $2 [ ]{0,'.$less_than_tab.'} ('.$marker_re.') # $3 = first list item marker [ ]+ ) (?s:.+?) ( # $4 \z | \n{2,} (?=\S) (?! # Negative lookahead for another list item marker [ ]* '.$marker_re.'[ ]+ ) ) ) '; // mx # We use a different prefix before nested lists than top-level lists. # See extended comment in _ProcessListItems(). if ($this->list_level) { $text = preg_replace_callback('{ ^ '.$whole_list_re.' }mx', array(&$this, '_doLists_callback'), $text); } else { $text = preg_replace_callback('{ (?:(?<=\n)\n|\A\n?) # Must eat the newline '.$whole_list_re.' }mx', array(&$this, '_doLists_callback'), $text); } } return $text; } function _doLists_callback($matches) { # Re-usable patterns to match list item bullets and number markers: $marker_ul_re = '[*+-]'; $marker_ol_re = '\d+[.]'; $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; $list = $matches[1]; $list_type = preg_match("/$marker_ul_re/", $matches[3]) ? "ul" : "ol"; $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re ); $list .= "\n"; $result = $this->processListItems($list, $marker_any_re); $result = $this->hashBlock("<$list_type>\n" . $result . ""); return "\n". $result ."\n\n"; } var $list_level = 0; function processListItems($list_str, $marker_any_re) { # # Process the contents of a single ordered or unordered list, splitting it # into individual list items. # # The $this->list_level global keeps track of when we're inside a list. # Each time we enter a list, we increment it; when we leave a list, # we decrement. If it's zero, we're not in a list anymore. # # We do this because when we're not inside a list, we want to treat # something like this: # # I recommend upgrading to version # 8. Oops, now this line is treated # as a sub-list. # # As a single paragraph, despite the fact that the second line starts # with a digit-period-space sequence. # # Whereas when we're inside a list (or sub-list), that line will be # treated as the start of a sub-list. What a kludge, huh? This is # an aspect of Markdown's syntax that's hard to parse perfectly # without resorting to mind-reading. Perhaps the solution is to # change the syntax rules such that sub-lists must start with a # starting cardinal number; e.g. "1." or "a.". $this->list_level++; # trim trailing blank lines: $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); $list_str = preg_replace_callback('{ (\n)? # leading line = $1 (^[ ]*) # leading whitespace = $2 ('.$marker_any_re.' # list marker and space = $3 (?:[ ]+|(?=\n)) # space only required if item is not empty ) ((?s:.*?)) # list item text = $4 (?:(\n+(?=\n))|\n) # tailing blank line = $5 (?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n)))) }xm', array(&$this, '_processListItems_callback'), $list_str); $this->list_level--; return $list_str; } function _processListItems_callback($matches) { $item = $matches[4]; $leading_line =& $matches[1]; $leading_space =& $matches[2]; $marker_space = $matches[3]; $tailing_blank_line =& $matches[5]; if ($leading_line || $tailing_blank_line || preg_match('/\n{2,}/', $item)) { # Replace marker with the appropriate whitespace indentation $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item; $item = $this->runBlockGamut($this->outdent($item)."\n"); } else { # Recursion for sub-lists: $item = $this->doLists($this->outdent($item)); $item = preg_replace('/\n+$/', '', $item); $item = $this->runSpanGamut($item); } return "
  • " . $item . "
  • \n"; } function doCodeBlocks($text) { # # Process Markdown `
    ` blocks.
    	#
    		$text = preg_replace_callback('{
    				(?:\n\n|\A\n?)
    				(	            # $1 = the code block -- one or more lines, starting with a space/tab
    				  (?>
    					[ ]{'.$this->tab_width.'}  # Lines must start with a tab or a tab-width of spaces
    					.*\n+
    				  )+
    				)
    				((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
    			}xm',
    			array(&$this, '_doCodeBlocks_callback'), $text);
    
    		return $text;
    	}
    	function _doCodeBlocks_callback($matches) {
    		$codeblock = $matches[1];
    
    		$codeblock = $this->outdent($codeblock);
    		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
    
    		# trim leading newlines and trailing newlines
    		$codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
    
    		$codeblock = "
    $codeblock\n
    "; return "\n\n".$this->hashBlock($codeblock)."\n\n"; } function makeCodeSpan($code) { # # Create a code span markup for $code. Called from handleSpanToken. # $code = htmlspecialchars(trim($code), ENT_NOQUOTES); return $this->hashPart("$code"); } var $em_relist = array( '' => '(?:(? '(?<=\S)(? '(?<=\S)(? '(?:(? '(?<=\S)(? '(?<=\S)(? '(?:(? '(?<=\S)(? '(?<=\S)(?em_relist as $em => $em_re) { foreach ($this->strong_relist as $strong => $strong_re) { # Construct list of allowed token expressions. $token_relist = array(); if (isset($this->em_strong_relist["$em$strong"])) { $token_relist[] = $this->em_strong_relist["$em$strong"]; } $token_relist[] = $em_re; $token_relist[] = $strong_re; # Construct master expression from list. $token_re = '{('. implode('|', $token_relist) .')}'; $this->em_strong_prepared_relist["$em$strong"] = $token_re; } } } function doItalicsAndBold($text) { $token_stack = array(''); $text_stack = array(''); $em = ''; $strong = ''; $tree_char_em = false; while (1) { # # Get prepared regular expression for seraching emphasis tokens # in current context. # $token_re = $this->em_strong_prepared_relist["$em$strong"]; # # Each loop iteration seach for the next emphasis token. # Each token is then passed to handleSpanToken. # $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE); $text_stack[0] .= $parts[0]; $token =& $parts[1]; $text =& $parts[2]; if (empty($token)) { # Reached end of text span: empty stack without emitting. # any more emphasis. while ($token_stack[0]) { $text_stack[1] .= array_shift($token_stack); $text_stack[0] .= array_shift($text_stack); } break; } $token_len = strlen($token); if ($tree_char_em) { # Reached closing marker while inside a three-char emphasis. if ($token_len == 3) { # Three-char closing marker, close em and strong. array_shift($token_stack); $span = array_shift($text_stack); $span = $this->runSpanGamut($span); $span = "$span"; $text_stack[0] .= $this->hashPart($span); $em = ''; $strong = ''; } else { # Other closing marker: close one em or strong and # change current token state to match the other $token_stack[0] = str_repeat($token{0}, 3-$token_len); $tag = $token_len == 2 ? "strong" : "em"; $span = $text_stack[0]; $span = $this->runSpanGamut($span); $span = "<$tag>$span"; $text_stack[0] = $this->hashPart($span); $$tag = ''; # $$tag stands for $em or $strong } $tree_char_em = false; } else if ($token_len == 3) { if ($em) { # Reached closing marker for both em and strong. # Closing strong marker: for ($i = 0; $i < 2; ++$i) { $shifted_token = array_shift($token_stack); $tag = strlen($shifted_token) == 2 ? "strong" : "em"; $span = array_shift($text_stack); $span = $this->runSpanGamut($span); $span = "<$tag>$span"; $text_stack[0] .= $this->hashPart($span); $$tag = ''; # $$tag stands for $em or $strong } } else { # Reached opening three-char emphasis marker. Push on token # stack; will be handled by the special condition above. $em = $token{0}; $strong = "$em$em"; array_unshift($token_stack, $token); array_unshift($text_stack, ''); $tree_char_em = true; } } else if ($token_len == 2) { if ($strong) { # Unwind any dangling emphasis marker: if (strlen($token_stack[0]) == 1) { $text_stack[1] .= array_shift($token_stack); $text_stack[0] .= array_shift($text_stack); } # Closing strong marker: array_shift($token_stack); $span = array_shift($text_stack); $span = $this->runSpanGamut($span); $span = "$span"; $text_stack[0] .= $this->hashPart($span); $strong = ''; } else { array_unshift($token_stack, $token); array_unshift($text_stack, ''); $strong = $token; } } else { # Here $token_len == 1 if ($em) { if (strlen($token_stack[0]) == 1) { # Closing emphasis marker: array_shift($token_stack); $span = array_shift($text_stack); $span = $this->runSpanGamut($span); $span = "$span"; $text_stack[0] .= $this->hashPart($span); $em = ''; } else { $text_stack[0] .= $token; } } else { array_unshift($token_stack, $token); array_unshift($text_stack, ''); $em = $token; } } } return $text_stack[0]; } function doBlockQuotes($text) { $text = preg_replace_callback('/ ( # Wrap whole match in $1 (?> ^[ ]*>[ ]? # ">" at the start of a line .+\n # rest of the first line (.+\n)* # subsequent consecutive lines \n* # blanks )+ ) /xm', array(&$this, '_doBlockQuotes_callback'), $text); return $text; } function _doBlockQuotes_callback($matches) { $bq = $matches[1]; # trim one level of quoting - trim whitespace-only lines $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq); $bq = $this->runBlockGamut($bq); # recurse $bq = preg_replace('/^/m', " ", $bq); # These leading spaces cause problem with
     content, 
    		# so we need to fix that:
    		$bq = preg_replace_callback('{(\s*
    .+?
    )}sx', array(&$this, '_DoBlockQuotes_callback2'), $bq); return "\n". $this->hashBlock("
    \n$bq\n
    ")."\n\n"; } function _doBlockQuotes_callback2($matches) { $pre = $matches[1]; $pre = preg_replace('/^ /m', '', $pre); return $pre; } function formParagraphs($text) { # # Params: # $text - string to process with html

    tags # # Strip leading and trailing lines: $text = preg_replace('/\A\n+|\n+\z/', '', $text); $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); # # Wrap

    tags and unhashify HTML blocks # foreach ($grafs as $key => $value) { if (!preg_match('/^B\x1A[0-9]+B$/', $value)) { # Is a paragraph. $value = $this->runSpanGamut($value); $value = preg_replace('/^([ ]*)/', "

    ", $value); $value .= "

    "; $grafs[$key] = $this->unhash($value); } else { # Is a block. # Modify elements of @grafs in-place... $graf = $value; $block = $this->html_hashes[$graf]; $graf = $block; // if (preg_match('{ // \A // ( # $1 =
    tag //
    ]* // \b // markdown\s*=\s* ([\'"]) # $2 = attr quote char // 1 // \2 // [^>]* // > // ) // ( # $3 = contents // .* // ) // (
    ) # $4 = closing tag // \z // }xs', $block, $matches)) // { // list(, $div_open, , $div_content, $div_close) = $matches; // // # We can't call Markdown(), because that resets the hash; // # that initialization code should be pulled into its own sub, though. // $div_content = $this->hashHTMLBlocks($div_content); // // # Run document gamut methods on the content. // foreach ($this->document_gamut as $method => $priority) { // $div_content = $this->$method($div_content); // } // // $div_open = preg_replace( // '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open); // // $graf = $div_open . "\n" . $div_content . "\n" . $div_close; // } $grafs[$key] = $graf; } } return implode("\n\n", $grafs); } function encodeAttribute($text) { # # Encode text for a double-quoted HTML attribute. This function # is *not* suitable for attributes enclosed in single quotes. # $text = $this->encodeAmpsAndAngles($text); $text = str_replace('"', '"', $text); return $text; } function encodeAmpsAndAngles($text) { # # Smart processing for ampersands and angle brackets that need to # be encoded. Valid character entities are left alone unless the # no-entities mode is set. # if ($this->no_entities) { $text = str_replace('&', '&', $text); } else { # Ampersand-encoding based entirely on Nat Irons's Amputator # MT plugin: $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', '&', $text);; } # Encode remaining <'s $text = str_replace('<', '<', $text); return $text; } function doAutoLinks($text) { $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i', array(&$this, '_doAutoLinks_url_callback'), $text); # Email addresses: $text = preg_replace_callback('{ < (?:mailto:)? ( [-.\w\x80-\xFF]+ \@ [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+ ) > }xi', array(&$this, '_doAutoLinks_email_callback'), $text); return $text; } function _doAutoLinks_url_callback($matches) { $url = $this->encodeAttribute($matches[1]); $link = "$url"; return $this->hashPart($link); } function _doAutoLinks_email_callback($matches) { $address = $matches[1]; $link = $this->encodeEmailAddress($address); return $this->hashPart($link); } function encodeEmailAddress($addr) { # # Input: an email address, e.g. "foo@example.com" # # Output: the email address as a mailto link, with each character # of the address encoded as either a decimal or hex entity, in # the hopes of foiling most address harvesting spam bots. E.g.: # #

    foo@exampl # e.com

    # # Based by a filter by Matthew Wickline, posted to BBEdit-Talk. # With some optimizations by Milian Wolff. # $addr = "mailto:" . $addr; $chars = preg_split('/(? $char) { $ord = ord($char); # Ignore non-ascii chars. if ($ord < 128) { $r = ($seed * (1 + $key)) % 100; # Pseudo-random function. # roughly 10% raw, 45% hex, 45% dec # '@' *must* be encoded. I insist. if ($r > 90 && $char != '@') /* do nothing */; else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';'; else $chars[$key] = '&#'.$ord.';'; } } $addr = implode('', $chars); $text = implode('', array_slice($chars, 7)); # text without `mailto:` $addr = "$text"; return $addr; } function parseSpan($str) { # # Take the string $str and parse it into tokens, hashing embeded HTML, # escaped characters and handling code spans. # $output = ''; $span_re = '{ ( \\\\'.$this->escape_chars_re.' | (?no_markup ? '' : ' | # comment | <\?.*?\?> | <%.*?%> # processing instruction | <[/!$]?[-a-zA-Z0-9:]+ # regular tags (?> \s (?>[^"\'>]+|"[^"]*"|\'[^\']*\')* )? > ').' ) }xs'; while (1) { # # Each loop iteration seach for either the next tag, the next # openning code span marker, or the next escaped character. # Each token is then passed to handleSpanToken. # $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE); # Create token from text preceding tag. if ($parts[0] != "") { $output .= $parts[0]; } # Check if we reach the end. if (isset($parts[1])) { $output .= $this->handleSpanToken($parts[1], $parts[2]); $str = $parts[2]; } else { break; } } return $output; } function handleSpanToken($token, &$str) { # # Handle $token provided by parseSpan by determining its nature and # returning the corresponding value that should replace it. # switch ($token{0}) { case "\\": return $this->hashPart("&#". ord($token{1}). ";"); case "`": # Search for end marker in remaining text. if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm', $str, $matches)) { $str = $matches[2]; $codespan = $this->makeCodeSpan($matches[1]); return $this->hashPart($codespan); } return $token; // return as text since no ending marker found. default: return $this->hashPart($token); } } function outdent($text) { # # Remove one level of line-leading tabs or spaces # return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text); } # String length function for detab. `_initDetab` will create a function to # hanlde UTF-8 if the default function does not exist. var $utf8_strlen = 'mb_strlen'; function detab($text) { # # Replace tabs with the appropriate amount of space. # # For each line we separate the line in blocks delemited by # tab characters. Then we reconstruct every line by adding the # appropriate number of space between each blocks. $text = preg_replace_callback('/^.*\t.*$/m', array(&$this, '_detab_callback'), $text); return $text; } function _detab_callback($matches) { $line = $matches[0]; $strlen = $this->utf8_strlen; # strlen function for UTF-8. # Split in blocks. $blocks = explode("\t", $line); # Add each blocks to the line. $line = $blocks[0]; unset($blocks[0]); # Do not add first block twice. foreach ($blocks as $block) { # Calculate amount of space, insert spaces, insert block. $amount = $this->tab_width - $strlen($line, 'UTF-8') % $this->tab_width; $line .= str_repeat(" ", $amount) . $block; } return $line; } function _initDetab() { # # Check for the availability of the function in the `utf8_strlen` property # (initially `mb_strlen`). If the function is not available, create a # function that will loosely count the number of UTF-8 characters with a # regular expression. # if (function_exists($this->utf8_strlen)) return; $this->utf8_strlen = create_function('$text', 'return preg_match_all( "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/", $text, $m);'); } function unhash($text) { # # Swap back in all the tags hashed by _HashHTMLBlocks. # return preg_replace_callback('/(.)\x1A[0-9]+\1/', array(&$this, '_unhash_callback'), $text); } function _unhash_callback($matches) { return $this->html_hashes[$matches[0]]; } } /* PHP Markdown ============ Description ----------- This is a PHP translation of the original Markdown formatter written in Perl by John Gruber. Markdown is a text-to-HTML filter; it translates an easy-to-read / easy-to-write structured text format into HTML. Markdown's text format is most similar to that of plain text email, and supports features such as headers, *emphasis*, code blocks, blockquotes, and links. Markdown's syntax is designed not as a generic markup language, but specifically to serve as a front-end to (X)HTML. You can use span-level HTML tags anywhere in a Markdown document, and you can use block level HTML tags (like
    and as well). For more information about Markdown's syntax, see: Bugs ---- To file bug reports please send email to: Please include with your report: (1) the example input; (2) the output you expected; (3) the output Markdown actually produced. Version History --------------- See the readme file for detailed release notes for this version. Copyright and License --------------------- PHP Markdown Copyright (c) 2004-2008 Michel Fortin All rights reserved. Based on Markdown Copyright (c) 2003-2006 John Gruber All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name "Markdown" nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. This software is provided by the copyright holders and contributors "as is" and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright owner or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage. */ ?> ================================================ FILE: libraries/pagination.class.php ================================================ '', 'perpage' => '', 'page' => '', 'total' => '', 'numlinks' => '5', 'nexttext' => '>', 'prevtext' => '<', 'focusedclass' => 'selected', 'delimiter' => '', 'lasttext' => '>>', 'firsttext' => '<<', 'order' => 'votes', 'search' => '' ); function set($who,$what){ $this->output = ''; $this->options[$who] = $what; } function checkValues(){ $errors = array(); if($this->options['perpage']=='') $errors[] = 'Invalid perpage value'; if($this->options['page']=='') $errors[] = 'Invalid page value'; if($this->options['total']=='') $errors[] = 'Invalid total value'; if($this->options['numlinks']=='') $errors[] = 'Invalid numlinks value'; } function display($return = true){ $this->checkValues(); if($this->output=='') $this->generateOutput(); if(!$return) echo $this->output; else return $this->output; } function generateOutput(){ $elements = array(); $num_pages = ceil($this->options['total']/$this->options['perpage']); $front_links = ceil($this->options['numlinks']/2); $end_links = floor($this->options['numlinks']/2); if($this->options['page'] > $num_pages){ $this->set('page',1); } $start_page = max(1,($this->options['page']-$front_links+1)); $end_page = min($this->options['numlinks'] + $start_page-1,$num_pages); if($this->options['page'] > 1){ if ($num_pages > 10) { $elements[] = $this->generate_link(1,$this->options['firsttext']); } $elements[] = $this->generate_link($this->options['page']-1,$this->options['prevtext']); } for($i=$start_page;$i<=$end_page;$i++){ $elements[] = $this->generate_link($i); } if($this->options['page'] < $num_pages){ $elements[] = $this->generate_link($this->options['page']+1,$this->options['nexttext']); if ($num_pages > 10) { $elements[] = $this->generate_link($num_pages,$this->options['lasttext']); } } $this->output = implode($this->options['delimiter'],$elements); } function generate_link($page,$label=''){ $url = str_replace('%page%',$page,$this->options['urlscheme']); if($label=='') $label=$page; $html = "options['focusedclass']!='' && $page == $this->options['page'])?"class=\"{$this->options['focusedclass']}\" ":"")."href=\"{$url}\">{$label}"; return $html; } function displayOptions(){ if ($this->options['search']!='') { $elements[] = $this->generate_seclink('relevance'); } $elements[] = $this->generate_seclink('votes'); $elements[] = $this->generate_seclink('newest'); $elements[] = $this->generate_seclink('oldest'); return implode($this->options['delimiter'],$elements); } function generate_seclink($label){ $url = str_replace('%label%',$label,$this->options['urlscheme']); $html = "options['focusedclass']!='' && $label == $this->options['order'])?"class=\"{$this->options['focusedclass']}\" ":"")."href=\"{$url}\">{$label}"; return $html; } } ================================================ FILE: libraries/purifier/HTMLPurifier/AttrCollections.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/AlphaValue.php ================================================ 1.0) $result = '1'; return $result; } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/Background.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php ================================================ | | left | center | right ] [ | | 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['c'] = false; // center $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]; $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 (count($measures)) $ret[] = array_shift($measures); elseif ($keywords['c']) { $ret[] = $keywords['c']; $keywords['c'] = false; // prevent re-use: center = center center } if ($keywords['v']) $ret[] = $keywords['v']; elseif (count($measures)) $ret[] = array_shift($measures); elseif ($keywords['c']) $ret[] = $keywords['c']; if (empty($ret)) return false; return implode(' ', $ret); } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/Border.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/Color.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/Composite.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/Filter.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/Font.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/FontFamily.php ================================================ 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); $new_font = ''; for ($i = 0, $c = strlen($font); $i < $c; $i++) { if ($font[$i] === '\\') { $i++; if ($i >= $c) { $new_font .= '\\'; break; } if (ctype_xdigit($font[$i])) { $code = $font[$i]; for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) { if (!ctype_xdigit($font[$i])) break; $code .= $font[$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; $new_font .= $char; if ($i < $c && trim($font[$i]) !== '') $i--; continue; } if ($font[$i] === "\n") continue; } $new_font .= $font[$i]; } $font = $new_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; } // complicated font, requires quoting // armor single quotes and new lines $font = str_replace("\\", "\\\\", $font); $font = str_replace("'", "\\'", $font); $final .= "'$font', "; } $final = rtrim($final, ', '); if ($final === '') return false; return $final; } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/Length.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/ListStyle.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/Multiple.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/Number.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/Percentage.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/TextDecoration.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS/URI.php ================================================ 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); } $keys = array( '(', ')', ',', ' ', '"', "'"); $values = array('\\(', '\\)', '\\,', '\\ ', '\\"', "\\'"); $uri = str_replace($values, $keys, $uri); $result = parent::validate($uri, $config, $context); if ($result === false) return false; // escape necessary characters according to CSS spec // except for the comma, none of these should appear in the // URI at all $result = str_replace($keys, $values, $result); return "url($result)"; } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/CSS.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/Enum.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/HTML/Bool.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/HTML/Class.php ================================================ 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; } } ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/HTML/Color.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/HTML/FrameTarget.php ================================================ valid_values === false) $this->valid_values = $config->get('Attr.AllowedFrameTargets'); return parent::validate($string, $config, $context); } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/HTML/ID.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/HTML/Length.php ================================================ 100) return '100%'; return ((string) $points) . '%'; } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/HTML/LinkTypes.php ================================================ '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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/HTML/MultiLength.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/HTML/Pixels.php ================================================ 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 // 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/Integer.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/Lang.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/Switch.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/Text.php ================================================ parseCDATA($string); } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php ================================================ " // 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/URI/Email.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/URI/IPv4.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/URI/IPv6.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef/URI.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrDef.php ================================================ 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); } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrTransform/Background.php ================================================ confiscateAttr($attr, 'background'); // some validation should happen here $this->prependCSS($attr, "background-image:url($background);"); return $attr; } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrTransform/BdoDir.php ================================================ get('Attr.DefaultTextDir'); return $attr; } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrTransform/BgColor.php ================================================ confiscateAttr($attr, 'bgcolor'); // some validation should happen here $this->prependCSS($attr, "background-color:$bgcolor;"); return $attr; } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrTransform/BoolToCSS.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrTransform/Border.php ================================================ confiscateAttr($attr, 'border'); // some validation should happen here $this->prependCSS($attr, "border:{$border_width}px solid;"); return $attr; } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrTransform/EnumToCSS.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrTransform/ImgRequired.php ================================================ 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) { $attr['alt'] = basename($attr['src']); } else { $attr['alt'] = $alt; } } else { $attr['alt'] = $config->get('Attr.DefaultInvalidImageAlt'); } } return $attr; } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrTransform/ImgSpace.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrTransform/Input.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrTransform/Lang.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrTransform/Name.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrTransform/NameSync.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrTransform/SafeEmbed.php ================================================ 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': $attr['value'] = $this->uri->validate($attr['value'], $config, $context); 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrTransform/ScriptRequired.php ================================================ */ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrTransform/Textarea.php ================================================ */ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrTransform.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/AttrValidator.php ================================================ 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.

    to

    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 ================================================ FILE: libraries/purifier/HTMLPurifier/Bootstrap.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/CSSDefinition.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ChildDef/Chameleon.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ChildDef/Custom.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ChildDef/Empty.php ================================================ whitespace) return $tokens_of_children; else return array(); } return $result; } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ChildDef/Required.php ================================================ $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 ================================================ FILE: libraries/purifier/HTMLPurifier/ChildDef/StrictBlockquote.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ChildDef/Table.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ChildDef.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/Config.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/Builder/Xml.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/Exception.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/Interchange/Id.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/Interchange.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/InterchangeBuilder.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/Validator.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/ValidatorAtom.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt ================================================ Attr.IDBlacklist TYPE: list DEFAULT: array() DESCRIPTION: Array of IDs not allowed in the document. --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt ================================================ AutoFormat.AutoParagraph TYPE: bool VERSION: 2.0.1 DEFAULT: false --DESCRIPTION--

    This directive turns on auto-paragraphing, where double newlines are converted in to paragraphs whenever possible. Auto-paragraphing:

    • Always applies to inline elements or text in the root node,
    • Applies to inline elements or text with double newlines in nodes that allow paragraph tags,
    • Applies to double newlines in paragraph tags

    p tags must be allowed for this directive to take effect. We do not use br tags for paragraphing, as that is semantically incorrect.

    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 pre, so this should not be difficult.) To prevent the paragraphing of inline text adjacent to block elements, wrap them in div tags (the behavior is slightly different outside of the root node.)

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt ================================================ AutoFormat.Custom TYPE: list VERSION: 2.0.1 DEFAULT: array() --DESCRIPTION--

    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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt ================================================ AutoFormat.DisplayLinkURI TYPE: bool VERSION: 3.2.0 DEFAULT: false --DESCRIPTION--

    This directive turns on the in-text display of URIs in <a> tags, and disables those links. For example, example becomes example (http://example.com).

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt ================================================ AutoFormat.Linkify TYPE: bool VERSION: 2.0.1 DEFAULT: false --DESCRIPTION--

    This directive turns on linkification, auto-linking http, ftp and https URLs. a tags with the href attribute must be allowed.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt ================================================ AutoFormat.PurifierLinkify.DocURL TYPE: string VERSION: 2.0.1 DEFAULT: '#%s' ALIASES: AutoFormatParam.PurifierLinkifyDocURL --DESCRIPTION--

    Location of configuration documentation to link to, let %s substitute into the configuration's namespace and directive names sans the percent sign.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt ================================================ AutoFormat.PurifierLinkify TYPE: bool VERSION: 2.0.1 DEFAULT: false --DESCRIPTION--

    Internal auto-formatter that converts configuration directives in syntax %Namespace.Directive to links. a tags with the href attribute must be allowed.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt ================================================ AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions TYPE: lookup VERSION: 4.0.0 DEFAULT: array('td' => true, 'th' => true) --DESCRIPTION--

    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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt ================================================ AutoFormat.RemoveEmpty.RemoveNbsp TYPE: bool VERSION: 4.0.0 DEFAULT: false --DESCRIPTION--

    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.

    See %AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions for a list of elements that don't have this behavior applied to them.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt ================================================ AutoFormat.RemoveEmpty TYPE: bool VERSION: 3.2.0 DEFAULT: false --DESCRIPTION--

    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:

    • Tags with no attributes and no content, and that are not empty elements (remove <a></a> but not <br />), and
    • Tags with no content, except for:
      • The colgroup element, or
      • Elements with the id or name attribute, when those attributes are permitted on those elements.

    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.

    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.

    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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt ================================================ 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, display:none; is considered a tricky property that will only be allowed if this directive is set to true. --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt ================================================ CSS.AllowedProperties TYPE: lookup/null VERSION: 3.1.0 DEFAULT: NULL --DESCRIPTION--

    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.

    Warning: If another directive conflicts with the elements here, that directive will win and override.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt ================================================ CSS.DefinitionRev TYPE: int VERSION: 2.0.0 DEFAULT: 1 --DESCRIPTION--

    Revision identifier for your custom definition. See %HTML.DefinitionRev for details.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt ================================================ CSS.MaxImgLength TYPE: string/null DEFAULT: '1200px' VERSION: 3.1.1 --DESCRIPTION--

    This parameter sets the maximum allowed length on img tags, effectively the width and height 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).

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt ================================================ CSS.Proprietary TYPE: bool VERSION: 3.0.0 DEFAULT: false --DESCRIPTION--

    Whether or not to allow safe, proprietary CSS values.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt ================================================ Cache.SerializerPath TYPE: string/null VERSION: 2.0.0 DEFAULT: NULL --DESCRIPTION--

    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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt ================================================ Core.AggressivelyFixLt TYPE: bool VERSION: 2.1.0 DEFAULT: true --DESCRIPTION--

    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.

    Notice: This directive's default turned from false to true in HTML Purifier 3.2.0.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt ================================================ 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. Warning: 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt ================================================ Core.DirectLexLineNumberSyncInterval TYPE: int VERSION: 2.0.0 DEFAULT: 0 --DESCRIPTION--

    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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt ================================================ 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 feature 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt ================================================ Core.HiddenElements TYPE: lookup --DEFAULT-- array ( 'script' => true, 'style' => true, ) --DESCRIPTION--

    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 script 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 b tag, which defines some presentational changes but does not hide its contents.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Core.Language.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt ================================================ Core.LexerImpl TYPE: mixed/null VERSION: 2.0.0 DEFAULT: NULL --DESCRIPTION--

    This parameter determines what lexer implementation can be used. The valid values are:

    null
    Recommended, the lexer implementation will be auto-detected based on your PHP-version and configuration.
    string lexer identifier
    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.
    object lexer instance
    Super-advanced: you can specify your own, custom, implementation that implements the interface defined by HTMLPurifier_Lexer. I may remove this option simply because I don't expect anyone to use it.
    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt ================================================ Core.MaintainLineNumbers TYPE: bool/null VERSION: 2.0.0 DEFAULT: NULL --DESCRIPTION--

    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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt ================================================ Core.RemoveInvalidImg TYPE: bool DEFAULT: true VERSION: 1.3.0 --DESCRIPTION--

    This directive enables pre-emptive URI checking in img 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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt ================================================ Core.RemoveScriptContents TYPE: bool/null DEFAULT: NULL VERSION: 2.0.0 DEPRECATED-VERSION: 2.1.0 DEPRECATED-USE: Core.HiddenElements --DESCRIPTION--

    This directive enables HTML Purifier to remove not only script tags but all of their contents.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt ================================================ Filter.Custom TYPE: list VERSION: 3.1.0 DEFAULT: array() --DESCRIPTION--

    This directive can be used to add custom filters; it is nearly the equivalent of the now deprecated HTMLPurifier->addFilter() method. Specify an array of concrete implementations.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt ================================================ Filter.ExtractStyleBlocks.Escaping TYPE: bool VERSION: 3.0.0 DEFAULT: true ALIASES: Filter.ExtractStyleBlocksEscaping, FilterParam.ExtractStyleBlocksEscaping --DESCRIPTION--

    Whether or not to escape the dangerous characters <, > and & as \3C, \3E and \26, respectively. This is can be safely set to false if the contents of StyleBlocks will be placed in an external stylesheet, where there is no risk of it being interpreted as HTML.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt ================================================ Filter.ExtractStyleBlocks.Scope TYPE: string/null VERSION: 3.0.0 DEFAULT: NULL ALIASES: Filter.ExtractStyleBlocksScope, FilterParam.ExtractStyleBlocksScope --DESCRIPTION--

    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 #user-content and a user uses the selector a:hover, the final selector will be #user-content a:hover.

    The comma shorthand may be used; consider the above example, with #user-content, #user-content2, the final selector will be #user-content a:hover, #user-content2 a:hover.

    Warning: 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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt ================================================ Filter.ExtractStyleBlocks.TidyImpl TYPE: mixed/null VERSION: 3.1.0 DEFAULT: NULL ALIASES: FilterParam.ExtractStyleBlocksTidyImpl --DESCRIPTION--

    If left NULL, HTML Purifier will attempt to instantiate a csstidy class to use for internal cleaning. This will usually be good enough.

    However, for trusted user input, you can set this to false 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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt ================================================ Filter.ExtractStyleBlocks TYPE: bool VERSION: 3.1.0 DEFAULT: false EXTERNAL: CSSTidy --DESCRIPTION--

    This directive turns on the style block extraction filter, which removes style blocks from input HTML, cleans them up with CSSTidy, and places them in the StyleBlocks context variable, for further use by you, usually to be placed in an external stylesheet, or a style block in the head of your document.

    Sample usage:

    ';
    ?>
    
    
    
      Filter.ExtractStyleBlocks
    body {color:#F00;} 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 '';
        }
    ?>
    
    
      
    ]]>

    Warning: 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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt ================================================ Filter.YouTube TYPE: bool VERSION: 3.1.0 DEFAULT: false --DESCRIPTION--

    This directive enables YouTube video embedding in HTML Purifier. Check this document on embedding videos for more information on what this filter does.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt ================================================ HTML.Allowed TYPE: itext/null VERSION: 2.0.0 DEFAULT: NULL --DESCRIPTION--

    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: element1[attr1|attr2],element2.... You can also use newlines instead of commas to separate elements.

    Warning: All of the constraints on the component directives are still enforced. The syntax is a subset of TinyMCE's valid_elements 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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt ================================================ HTML.AllowedAttributes TYPE: lookup/null VERSION: 1.3.0 DEFAULT: NULL --DESCRIPTION--

    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).

    Warning: If another directive conflicts with the elements here, that 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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt ================================================ HTML.AllowedElements TYPE: lookup/null VERSION: 1.3.0 DEFAULT: NULL --DESCRIPTION--

    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.

    Warning: If another directive conflicts with the elements here, that directive will win and override.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt ================================================ HTML.AllowedModules TYPE: lookup/null VERSION: 2.0.0 DEFAULT: NULL --DESCRIPTION--

    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.

    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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt ================================================ HTML.BlockWrapper TYPE: string VERSION: 1.3.0 DEFAULT: 'p' --DESCRIPTION--

    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.

    Example: by default value, <blockquote>Foo</blockquote> would become <blockquote><p>Foo</p></blockquote>. The <p> tags can be replaced with whatever you desire, as long as it is a block level element.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt ================================================ 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--

    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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt ================================================ HTML.DefinitionID TYPE: string/null DEFAULT: NULL VERSION: 2.0.0 --DESCRIPTION--

    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:

    $config = HTMLPurifier_Config::createDefault();
    $config->set('HTML', 'DefinitionID', '1');
    $def = $config->getHTMLDefinition();
    $def->addAttribute('a', 'tabindex', 'Number');
    

    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.

    You must specify a value to this directive to use the advanced API features.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt ================================================ HTML.DefinitionRev TYPE: int VERSION: 2.0.0 DEFAULT: 1 --DESCRIPTION--

    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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt ================================================ HTML.ForbiddenAttributes TYPE: lookup VERSION: 3.1.0 DEFAULT: array() --DESCRIPTION--

    While this directive is similar to %HTML.AllowedAttributes, for forwards-compatibility with XML, this attribute has a different syntax. Instead of tag.attr, use tag@attr. To disallow href attributes in a tags, set this directive to a@href. You can also disallow an attribute globally with attr or *@attr (either syntax is fine; the latter is provided for consistency with %HTML.AllowedAttributes).

    Warning: This directive complements %HTML.ForbiddenElements, accordingly, check out that directive for a discussion of why you should think twice before using this directive.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt ================================================ HTML.ForbiddenElements TYPE: lookup VERSION: 3.1.0 DEFAULT: array() --DESCRIPTION--

    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.

    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 img with the expectation of preventing images on your site, you'll be in for a nasty surprise when people start using the background-image CSS property.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt ================================================ HTML.MaxImgLength TYPE: int/null DEFAULT: 1200 VERSION: 3.1.1 --DESCRIPTION--

    This directive controls the maximum number of pixels in the width and height attributes in img 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).

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt ================================================ HTML.Parent TYPE: string VERSION: 1.3.0 DEFAULT: 'div' --DESCRIPTION--

    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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt ================================================ HTML.Proprietary TYPE: bool VERSION: 3.1.0 DEFAULT: false --DESCRIPTION--

    Whether or not to allow proprietary elements and attributes in your documents, as per HTMLPurifier_HTMLModule_Proprietary. Warning: This can cause your documents to stop validating!

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt ================================================ HTML.SafeEmbed TYPE: bool VERSION: 3.1.1 DEFAULT: false --DESCRIPTION--

    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 probably want to enable this with %HTML.SafeObject. Highly experimental.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt ================================================ HTML.SafeObject TYPE: bool VERSION: 3.1.1 DEFAULT: false --DESCRIPTION--

    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 may also want to enable %HTML.SafeEmbed for maximum interoperability with Internet Explorer, although embed tags will cause your website to stop validating. Highly experimental.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt ================================================ HTML.TidyLevel TYPE: string VERSION: 2.0.0 DEFAULT: 'medium' --DESCRIPTION--

    General level of cleanliness the Tidy module should enforce. There are four allowed values:

    none
    No extra tidying should be done
    light
    Only fix elements that would be discarded otherwise due to lack of support in doctype
    medium
    Enforce best practices
    heavy
    Transform all deprecated elements and attributes to standards compliant equivalents
    --ALLOWED-- 'none', 'light', 'medium', 'heavy' --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt ================================================ Output.Newline TYPE: string/null VERSION: 2.0.1 DEFAULT: NULL --DESCRIPTION--

    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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt ================================================ Output.SortAttr TYPE: bool VERSION: 3.2.0 DEFAULT: false --DESCRIPTION--

    If true, HTML Purifier will sort attributes by name before writing them back to the document, converting a tag like: <el b="" a="" c="" /> to <el a="" b="" c="" />. 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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt ================================================ Output.TidyFormat TYPE: bool VERSION: 1.1.1 DEFAULT: false --DESCRIPTION--

    Determines whether or not to run Tidy on the final output for pretty formatting reasons, such as indentation and wrap.

    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.

    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).

    --ALIASES-- Core.TidyFormat --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt ================================================ 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. --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/URI.Base.txt ================================================ URI.Base TYPE: string/null VERSION: 2.1.0 DEFAULT: NULL --DESCRIPTION--

    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). Warning: If set, the scheme on this URI overrides the one specified by %URI.DefaultScheme.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt ================================================ URI.DefaultScheme TYPE: string DEFAULT: 'http' --DESCRIPTION--

    Defines through what scheme the output will be served, in order to select the proper object validator when no scheme information is present.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt ================================================ URI.DefinitionID TYPE: string/null VERSION: 2.1.0 DEFAULT: NULL --DESCRIPTION--

    Unique identifier for a custom-built URI definition. If you want to add custom URIFilters, you must specify this value.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt ================================================ URI.DefinitionRev TYPE: int VERSION: 2.1.0 DEFAULT: 1 --DESCRIPTION--

    Revision identifier for your custom definition. See %HTML.DefinitionRev for details.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt ================================================ URI.Disable TYPE: bool VERSION: 1.3.0 DEFAULT: false --DESCRIPTION--

    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).

    --ALIASES-- Attr.DisableURI --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt ================================================ URI.DisableResources TYPE: bool VERSION: 1.3.0 DEFAULT: false --DESCRIPTION--

    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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/URI.Host.txt ================================================ URI.Host TYPE: string/null VERSION: 1.2.0 DEFAULT: NULL --DESCRIPTION--

    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. Note: 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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt ================================================ 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 moo.com will catch moo.com.example.com. --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt ================================================ URI.MakeAbsolute TYPE: bool VERSION: 2.1.0 DEFAULT: false --DESCRIPTION--

    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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt ================================================ URI.Munge TYPE: string/null VERSION: 1.3.0 DEFAULT: NULL --DESCRIPTION--

    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 %s where the url-encoded original URI should be inserted (sample: http://www.google.com/url?q=%s).

    Uses for this directive:

    • 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). Notice: Many security experts believe that this form of protection does not deter spam-bots.
    • 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.

    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 meta tags. To revert to previous behavior, please use %URI.MungeResources.

    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.

    Starting with HTML Purifier 3.1.1, there is also these substitutions:

    Key Description Example <a href="">
    %r 1 - The URI embeds a resource
    (blank) - The URI is merely a link
    %n The name of the tag this URI came from a
    %m The name of the attribute this URI came from href
    %p The name of the CSS property this URI came from, or blank if irrelevant

    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.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt ================================================ URI.MungeResources TYPE: bool VERSION: 3.1.1 DEFAULT: false --DESCRIPTION--

    If true, any URI munging directives like %URI.Munge will also apply to embedded resources, such as <img src="">. Be careful enabling this directive if you have a redirector script that does not use the Location HTTP header; all of your images and other embedded resources will break.

    Warning: It is strongly advised you use this in conjunction %URI.MungeSecretKey to mitigate the security risk of an open redirector.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt ================================================ URI.MungeSecretKey TYPE: string/null VERSION: 3.1.1 DEFAULT: NULL --DESCRIPTION--

    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:

    $checksum === sha1($secret_key . ':' . $url)

    If the output is TRUE, the redirector script should accept the URI.

    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.

    Remember this has no effect if %URI.Munge is not on.

    --# vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema/schema/info.ini ================================================ name = "HTML Purifier" ; vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/ConfigSchema.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ContentSets.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/Context.php ================================================ _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 ================================================ FILE: libraries/purifier/HTMLPurifier/Definition.php ================================================ setup) return; $this->setup = true; $this->doSetup($config); } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/DefinitionCache/Decorator/Template.php.in ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/DefinitionCache/Null.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/DefinitionCache.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/DefinitionCacheFactory.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/Doctype.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/DoctypeRegistry.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ElementDef.php ================================================ 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(); /** * 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 ================================================ FILE: libraries/purifier/HTMLPurifier/Encoder.php ================================================ 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 * , * 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 ================================================ FILE: libraries/purifier/HTMLPurifier/EntityLookup/entities.ser ================================================ 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:"♦";} ================================================ FILE: libraries/purifier/HTMLPurifier/EntityLookup.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/EntityParser.php ================================================ '"', 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 ================================================ FILE: libraries/purifier/HTMLPurifier/ErrorCollector.php ================================================ 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 '

    ' . $this->locale->getMessage('ErrorCollector: No errors') . '

    '; } else { return '
    • ' . implode('
    • ', $ret) . '
    '; } } 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 .= '
    '; // W3C uses an icon to indicate the severity of the error. $error = $this->locale->getErrorName($severity); $string .= "$error "; if (!is_null($line) && !is_null($col)) { $string .= "Line $line, Column $col: "; } else { $string .= 'End of Document: '; } $string .= '' . $this->generator->escape($msg) . ' '; $string .= '
    '; // Here, have a marker for the character on the column appropriate. // Be sure to clip extremely long lines. //$string .= '
    ';
                    //$string .= '';
                    //$string .= '
    '; $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 ================================================ FILE: libraries/purifier/HTMLPurifier/ErrorStruct.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/Exception.php ================================================ 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 #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 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 ================================================ FILE: libraries/purifier/HTMLPurifier/Filter/YouTube.php ================================================ ]+>.+?'. 'http://www.youtube.com/v/([A-Za-z0-9\-_]+).+?#s'; $pre_replace = '\1'; return preg_replace($pre_regex, $pre_replace, $html); } public function postFilter($html, $config, $context) { $post_regex = '#([A-Za-z0-9\-_]+)#'; return preg_replace_callback($post_regex, array($this, 'postFilterCallback'), $html); } protected function armorUrl($url) { return str_replace('--', '--', $url); } protected function postFilterCallback($matches) { $url = $this->armorUrl($matches[1]); return ''. ''. ''. ''; } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/Filter.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/Generator.php ================================================ 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; /** * 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->_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); return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>'; } elseif ($token instanceof HTMLPurifier_Token_End) { return 'name . '>'; } elseif ($token instanceof HTMLPurifier_Token_Empty) { $attr = $this->generateAttributes($token->attr, $token->name); return '<' . $token->name . ($attr ? ' ' : '') . $attr . ( $this->_xhtml ? ' /': '' ) //
    v.
    . '>'; } elseif ($token instanceof HTMLPurifier_Token_Text) { return $this->escape($token->data, ENT_NOQUOTES); } elseif ($token instanceof HTMLPurifier_Token_Comment) { return ''; } 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 $data = preg_replace('#//\s*$#', '', $token->data); return ''; } /** * 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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLDefinition.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Bdo.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/CommonAttributes.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Edit.php ================================================ '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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Forms.php ================================================ '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 . This one's a little complex // because it maps to multiple elements. } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Hypertext.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Image.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Legacy.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/List.php ================================================ 'List'); public function setup($config) { $this->addElement('ol', 'List', 'Required: li', 'Common'); $this->addElement('ul', 'List', 'Required: li', 'Common'); $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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Name.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php ================================================ array( 'lang' => 'LanguageCode', ) ); } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Object.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Presentation.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Proprietary.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Ruby.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/SafeEmbed.php ================================================ 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', 'wmode' => 'Enum#window', 'name' => 'ID', ) ); $embed->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeEmbed(); } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/SafeObject.php ================================================ 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' ) ); $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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Scripting.php ================================================ '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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/StyleAttribute.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Tables.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Target.php ================================================ addBlankElement($name); $e->attr = array( 'target' => new HTMLPurifier_AttrDef_HTML_FrameTarget() ); } } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Text.php ================================================ '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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Tidy/Name.php ================================================ content_model_type != 'strictblockquote') return parent::getChildDef($def); return new HTMLPurifier_ChildDef_StrictBlockquote($def->content_model); } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Tidy/Transitional.php ================================================ '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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/Tidy.php ================================================ '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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule/XMLCommonAttributes.php ================================================ array( 'xml:lang' => 'LanguageCode', ) ); } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModule.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/HTMLModuleManager.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/IDAccumulator.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/Injector/AutoParagraph.php ================================================ armor['MakeWellFormed_TagClosedError'] = true; return $par; } public function handleText(&$token) { $text = $token->data; // Does the current parent allow

    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 { // State 1.2: PAR1 // ---- // State 1.3: PAR1\n\nPAR2 // ------------ // State 1.4:

    PAR1\n\nPAR2 (see State 2) // ------------ $token = array($this->_pStart()); $this->_splitText($text, $token); } } else { // State 2:
    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:
    PAR1PAR1\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:
    PAR1
    // ---- // State 2.2.2:
    PAR1PAR1
    // ---- } } // Is the current parent a

    tag? } elseif ( !empty($this->currentNesting) && $this->currentNesting[count($this->currentNesting)-1]->name == 'p' ) { // State 3.1: ...

    PAR1 // ---- // State 3.2: ...

    PAR1\n\nPAR2 // ------------ $token = array(); $this->_splitText($text, $token); // Abort! } else { // State 4.1: ...PAR1 // ---- // State 4.2: ...PAR1\n\nPAR2 // ------------ } } public function handleElement(&$token) { // We don't have to check if we're already in a

    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:

    ... // --- // 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:

    PAR1

    \n\n // --- // Quite frankly, this should be handled by splitText $token = array($this->_pStart(), $token); } else { // State 1.1.1:

    PAR1

    // --- // State 1.1.2:

    // --- // State 1.1.3:
    PAR // --- } } else { // State 1.2.1:
    // --- // Lookahead to see if

    is needed. if ($this->_pLookAhead()) { // State 1.3.1:

    PAR1\n\nPAR2 // --- $token = array($this->_pStart(), $token); } else { // State 1.3.2:
    PAR1
    // --- // State 1.3.3:
    PAR1
    \n\n
    // --- } } } else { // State 2.3: ...
    // ----- } } else { if ($this->_isInline($token)) { // State 3.1: // --- // This is where the {p} tag is inserted, not reflected in // inputTokens yet, however. $token = array($this->_pStart(), $token); } else { // State 3.2:
    // ----- } $i = null; if ($this->backward($i, $prev)) { if ( !$prev instanceof HTMLPurifier_Token_Text ) { // State 3.1.1: ...

    {p} // --- // State 3.2.1: ...

    // ----- if (!is_array($token)) $token = array($token); array_unshift($token, new HTMLPurifier_Token_Text("\n\n")); } else { // State 3.1.2: ...

    \n\n{p} // --- // State 3.2.2: ...

    \n\n
    // ----- // Note: PAR cannot occur because PAR would have been // wrapped in

    tags. } } } } else { // State 2.2:

    • // ---- // State 2.4:

      // --- } } /** * 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

      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

      } } /** * 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

      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)) { //

      PAR1
      // ---- // Terminate early, since we hit a block element return false; } } elseif ($current instanceof HTMLPurifier_Token_Text) { if (strpos($current->data, "\n\n") !== false) { //
      PAR1PAR1\n\nPAR2 // ---- return true; } else { //
      PAR1PAR1... // ---- } } return null; } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/Injector/DisplayLinkURI.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/Injector/Linkify.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/Injector/PurifierLinkify.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/Injector/RemoveEmpty.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/Injector/SafeObject.php ================================================ 'never', 'allowNetworking' => 'internal', ); protected $allowedParam = array( 'wmode' => true, 'movie' => 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') { $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 ================================================ FILE: libraries/purifier/HTMLPurifier/Injector.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/Language/classes/en-x-test.php ================================================ 'HTML Purifier X' ); // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/Language/messages/en-x-testmini.php ================================================ 'HTML Purifier XNone' ); // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/Language/messages/en.php ================================================ 'HTML Purifier', // for unit testing purposes 'LanguageFactoryTest: Pizza' => 'Pizza', 'LanguageTest: List' => '$1', 'LanguageTest: Hash' => '$1.Keys; $1.Values', 'Item separator' => ', ', 'Item separator last' => ' and ', // non-Harvard style 'ErrorCollector: No errors' => 'No errors detected. However, because error reporting is still incomplete, there may have been errors that the error collector was not notified of; please inspect the output HTML carefully.', 'ErrorCollector: At line' => ' at line $line', 'ErrorCollector: Incidental errors' => 'Incidental errors', 'Lexer: Unclosed comment' => 'Unclosed comment', 'Lexer: Unescaped lt' => 'Unescaped less-than sign (<) should be <', 'Lexer: Missing gt' => 'Missing greater-than sign (>), previous less-than sign (<) should be escaped', 'Lexer: Missing attribute key' => 'Attribute declaration has no key', 'Lexer: Missing end quote' => 'Attribute declaration has no end quote', '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 ================================================ FILE: libraries/purifier/HTMLPurifier/Language.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/LanguageFactory.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/Length.php ================================================ 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 ================================================ FILE: libraries/purifier/HTMLPurifier/Lexer/DOMLex.php ================================================ factory = new HTMLPurifier_TokenFactory(); } public function tokenizeHTML($html, $config, $context) { $html = $this->normalize($html, $config, $context); // attempt to armor stray angled brackets that cannot possibly // form tags and thus are probably being used as emoticons if ($config->get('Core.AggressivelyFixLt')) { $char = '[^a-z!\/]'; $comment = "/|\z)/is"; $html = preg_replace_callback($comment, array($this, 'callbackArmorCommentEntities'), $html); do { $old = $html; $html = preg_replace("/<($char)/i", '<\\1', $html); } while ($html !== $old); $html = preg_replace_callback($comment, array($this, 'callbackUndoCommentSubst'), $html); // fix comments } // preprocess html, essential for UTF-8 $html = $this->wrapHTML($html, $config, $context); $doc = new DOMDocument(); $doc->encoding = 'UTF-8'; // theoretically, the above has this covered set_error_handler(array($this, 'muteErrorHandler')); $doc->loadHTML($html); restore_error_handler(); $tokens = array(); $this->tokenizeDOM( $doc->getElementsByTagName('html')->item(0)-> // getElementsByTagName('body')->item(0)-> // getElementsByTagName('div')->item(0) //
      , $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 )#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 "asdf" but not "asdf " // 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
      , 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 => $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 ================================================ FILE: libraries/purifier/HTMLPurifier/Lexer/PEARSax3.php ================================================ tokens = array(); $string = $this->normalize($string, $config, $context); $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); 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); } else { $this->tokens[] = new HTMLPurifier_Token_Start($name, $attrs); } 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->tokens[count($this->tokens)-1] instanceof HTMLPurifier_Token_Empty) { return true; } $this->tokens[] = new HTMLPurifier_Token_End($name); return true; } /** * Data event handler, interface is defined by PEAR package. */ public function dataHandler(&$parser, $data) { $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) { $this->tokens[] = new HTMLPurifier_Token_Comment($data); } // 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; } } // vim: et sw=4 sts=4 ================================================ FILE: libraries/purifier/HTMLPurifier/Lexer/PH5P.php ================================================ 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)-> // getElementsByTagName('body')->item(0)-> // getElementsByTagName('div')->item(0) //
      , $tokens); return $tokens; } } /* Copyright 2007 Jeroen van der Meer 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 (""), 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' => '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' => '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; } } ?> ================================================ FILE: libraries/purifier/HTMLPurifier/Lexer.php ================================================ 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( '"' => '"', '&' => '&', '<' => '<', '>' => '>', ''' => "'", ''' => "'", ''' => "'" ); /** * Parses special entities into the proper characters. * * This string will translate escaped versions of the special characters * into the correct ones. * * @warning * You should be able to treat the output of this function as * completely parsed, but that's only because all other entities should * have been handled previously in substituteNonSpecialEntities() * * @param $string String character data to be parsed. * @returns Parsed character data. */ public function parseData($string) { // following functions require at least one character if ($string === '') return ''; // subtracts amps that cannot possibly be escaped $num_amp = substr_count($string, '&') - substr_count($string, '& ') - ($string[strlen($string)-1] === '&' ? 1 : 0); if (!$num_amp) return $string; // abort if no entities $num_esc_amp = substr_count($string, '&'); $string = strtr($string, $this->_special_entity2str); // code duplication for sake of optimization, see above $num_amp_2 = substr_count($string, '&') - substr_count($string, '& ') - ($string[strlen($string)-1] === '&' ? 1 : 0); if ($num_amp_2 <= $num_esc_amp) return $string; // hmm... now we have some uncommon entities. Use the callback. $string = $this->_entity_parser->substituteSpecialEntities($string); return $string; } /** * Lexes an HTML string into tokens. * * @param $string String HTML. * @return HTMLPurifier_Token array representation of HTML. */ public function tokenizeHTML($string, $config, $context) { trigger_error('Call to abstract class', E_USER_ERROR); } /** * Translates CDATA sections into regular sections (through escaping). * * @param $string HTML string to process. * @returns HTML with CDATA sections escaped. */ protected static function escapeCDATA($string) { return preg_replace_callback( '//s', array('HTMLPurifier_Lexer', 'CDATACallback'), $string ); } /** * Special CDATA case that is especially convoluted for
      ================================================ FILE: views/helpers/getuser.php ================================================ ================================================ FILE: views/questions/ask.php ================================================
      " method="post" onsubmit="javascript:return cform();">

      What would you like to ask or contribute?


      Share a Link


      Tags


      Make this a knowledge-base article

      ================================================ FILE: views/questions/cache.php ================================================
      Cached Version of
      ================================================ FILE: views/questions/edit.php ================================================
      " method="post" onsubmit="javascript:return cform();">

      Edit Your Question


      Share a Link


      Tags


      > Make this a knowledge-base article

      ================================================ FILE: views/questions/index.php ================================================

      Questions

      QUESTIONS_PER_PAGE && $nopagination != 1):?>
      cmts | votes
      ans | votes

      QUESTIONS_PER_PAGE):?>

      Sorry, we could not find what you were looking for. You may want to have a look at tags.

      Or ask questions.

      ================================================ FILE: views/questions/view.php ================================================

      ">˄
      ">˅
      ">★


      (view cache)

      -
      " id="commentfave_">♥
      ANSWERS_PER_PAGE):?>

      Comments Answers

      Accepted Answer
      ">˄
      ">˅
      -
      " id="commentfave_">♥
      ANSWERS_PER_PAGE):?>

      No comments on this article as yet.No answers as yet. Be the first to write an answer.

      " method="post" onsubmit="javascript:return cform();">

      Add a commentAnswer Question



      ================================================ FILE: views/tags/index.php ================================================

      Tags

      • x
      ================================================ FILE: views/users/edit.php ================================================
      " method="post" onsubmit="javascript:return cform();">

      Edit Profile

      Name


      E-mail


      Password (Leave blank if you do not want to update)



      ================================================ FILE: views/users/index.php ================================================

      Users

      ================================================ FILE: views/users/login.php ================================================
      " method="post">

      Login

      E-mail


      Password



      ================================================ FILE: views/users/logout.php ================================================

      You have successfully logged out

      ================================================ FILE: views/users/register.php ================================================
      " method="post" onsubmit="javascript:return cform();">

      Register

      Name


      E-mail


      Password



      ================================================ FILE: views/users/view.php ================================================

      Points:

      ================================================ FILE: web.config ================================================