summaryrefslogtreecommitdiff
path: root/includes/pear
diff options
context:
space:
mode:
Diffstat (limited to 'includes/pear')
-rw-r--r--includes/pear/Archive/Tar.php1909
-rw-r--r--includes/pear/Auth.php1330
-rw-r--r--includes/pear/Auth/Anonymous.php138
-rw-r--r--includes/pear/Auth/Auth.php30
-rw-r--r--includes/pear/Auth/Container.php262
-rw-r--r--includes/pear/Auth/Container/Array.php161
-rw-r--r--includes/pear/Auth/Container/DB.php639
-rw-r--r--includes/pear/Auth/Container/DBLite.php320
-rw-r--r--includes/pear/Auth/Container/File.php314
-rw-r--r--includes/pear/Auth/Container/IMAP.php210
-rw-r--r--includes/pear/Auth/Container/KADM5.php171
-rw-r--r--includes/pear/Auth/Container/LDAP.php766
-rw-r--r--includes/pear/Auth/Container/MDB.php625
-rw-r--r--includes/pear/Auth/Container/MDB2.php624
-rw-r--r--includes/pear/Auth/Container/Multiple.php188
-rw-r--r--includes/pear/Auth/Container/NetVPOPMaild.php129
-rw-r--r--includes/pear/Auth/Container/PEAR.php165
-rw-r--r--includes/pear/Auth/Container/POP3.php145
-rw-r--r--includes/pear/Auth/Container/RADIUS.php182
-rw-r--r--includes/pear/Auth/Container/SAP.php179
-rw-r--r--includes/pear/Auth/Container/SMBPasswd.php182
-rw-r--r--includes/pear/Auth/Container/SOAP.php229
-rw-r--r--includes/pear/Auth/Container/SOAP5.php268
-rw-r--r--includes/pear/Auth/Container/vpopmail.php88
-rw-r--r--includes/pear/Auth/Controller.php302
-rw-r--r--includes/pear/Auth/Frontend/Html.php142
-rw-r--r--includes/pear/Console/Getopt.php360
-rw-r--r--includes/pear/HTTP.php548
-rw-r--r--includes/pear/HTTP/Download.php1243
-rw-r--r--includes/pear/HTTP/Download/Archive.php122
-rw-r--r--includes/pear/HTTP/Download/PgLOB.php177
-rw-r--r--includes/pear/HTTP/Header.php537
-rw-r--r--includes/pear/HTTP/Header/Cache.php238
-rw-r--r--includes/pear/Image/GraphViz.php1005
-rw-r--r--includes/pear/MIME/Type.php598
-rw-r--r--includes/pear/MIME/Type/Extension.php292
-rw-r--r--includes/pear/MIME/Type/Parameter.php170
-rw-r--r--includes/pear/OS/Guess.php338
-rw-r--r--includes/pear/PEAR.php1040
-rw-r--r--includes/pear/PEAR/Autoloader.php218
-rw-r--r--includes/pear/PEAR/Builder.php518
-rw-r--r--includes/pear/PEAR/ChannelFile.php1559
-rw-r--r--includes/pear/PEAR/ChannelFile/Parser.php68
-rw-r--r--includes/pear/PEAR/Command.php414
-rw-r--r--includes/pear/PEAR/Command/Auth.php81
-rw-r--r--includes/pear/PEAR/Command/Auth.xml30
-rw-r--r--includes/pear/PEAR/Command/Build.php85
-rw-r--r--includes/pear/PEAR/Command/Build.xml10
-rw-r--r--includes/pear/PEAR/Command/Channels.php883
-rw-r--r--includes/pear/PEAR/Command/Channels.xml123
-rw-r--r--includes/pear/PEAR/Command/Common.php273
-rw-r--r--includes/pear/PEAR/Command/Config.php414
-rw-r--r--includes/pear/PEAR/Command/Config.xml92
-rw-r--r--includes/pear/PEAR/Command/Install.php1268
-rw-r--r--includes/pear/PEAR/Command/Install.xml276
-rw-r--r--includes/pear/PEAR/Command/Mirror.php139
-rw-r--r--includes/pear/PEAR/Command/Mirror.xml18
-rw-r--r--includes/pear/PEAR/Command/Package.php1124
-rw-r--r--includes/pear/PEAR/Command/Package.xml237
-rw-r--r--includes/pear/PEAR/Command/Pickle.php421
-rw-r--r--includes/pear/PEAR/Command/Pickle.xml36
-rw-r--r--includes/pear/PEAR/Command/Registry.php1145
-rw-r--r--includes/pear/PEAR/Command/Registry.xml58
-rw-r--r--includes/pear/PEAR/Command/Remote.php810
-rw-r--r--includes/pear/PEAR/Command/Remote.xml109
-rw-r--r--includes/pear/PEAR/Command/Test.php337
-rw-r--r--includes/pear/PEAR/Command/Test.xml54
-rw-r--r--includes/pear/PEAR/Common.php837
-rw-r--r--includes/pear/PEAR/Config.php2092
-rw-r--r--includes/pear/PEAR/Dependency2.php1362
-rw-r--r--includes/pear/PEAR/DependencyDB.php757
-rw-r--r--includes/pear/PEAR/Downloader.php1766
-rw-r--r--includes/pear/PEAR/Downloader/Package.php1988
-rw-r--r--includes/pear/PEAR/ErrorStack.php985
-rw-r--r--includes/pear/PEAR/ErrorStack5.php921
-rw-r--r--includes/pear/PEAR/Exception.php389
-rw-r--r--includes/pear/PEAR/FixPHP5PEARWarnings.php7
-rw-r--r--includes/pear/PEAR/Frontend.php228
-rw-r--r--includes/pear/PEAR/Frontend/CLI.php751
-rw-r--r--includes/pear/PEAR/Installer.php1753
-rw-r--r--includes/pear/PEAR/Installer/Role.php267
-rw-r--r--includes/pear/PEAR/Installer/Role/Cfg.php106
-rw-r--r--includes/pear/PEAR/Installer/Role/Cfg.xml15
-rw-r--r--includes/pear/PEAR/Installer/Role/Common.php189
-rw-r--r--includes/pear/PEAR/Installer/Role/Data.php28
-rw-r--r--includes/pear/PEAR/Installer/Role/Data.xml15
-rw-r--r--includes/pear/PEAR/Installer/Role/Doc.php28
-rw-r--r--includes/pear/PEAR/Installer/Role/Doc.xml15
-rw-r--r--includes/pear/PEAR/Installer/Role/Ext.php28
-rw-r--r--includes/pear/PEAR/Installer/Role/Ext.xml12
-rw-r--r--includes/pear/PEAR/Installer/Role/Php.php28
-rw-r--r--includes/pear/PEAR/Installer/Role/Php.xml15
-rw-r--r--includes/pear/PEAR/Installer/Role/Script.php28
-rw-r--r--includes/pear/PEAR/Installer/Role/Script.xml15
-rw-r--r--includes/pear/PEAR/Installer/Role/Src.php34
-rw-r--r--includes/pear/PEAR/Installer/Role/Src.xml12
-rw-r--r--includes/pear/PEAR/Installer/Role/Test.php28
-rw-r--r--includes/pear/PEAR/Installer/Role/Test.xml15
-rw-r--r--includes/pear/PEAR/Installer/Role/Www.php28
-rw-r--r--includes/pear/PEAR/Installer/Role/Www.xml15
-rw-r--r--includes/pear/PEAR/PackageFile.php503
-rw-r--r--includes/pear/PEAR/PackageFile/Generator/v1.php1284
-rw-r--r--includes/pear/PEAR/PackageFile/Generator/v2.php893
-rw-r--r--includes/pear/PEAR/PackageFile/Parser/v1.php459
-rw-r--r--includes/pear/PEAR/PackageFile/Parser/v2.php113
-rw-r--r--includes/pear/PEAR/PackageFile/v1.php1612
-rw-r--r--includes/pear/PEAR/PackageFile/v2.php2049
-rw-r--r--includes/pear/PEAR/PackageFile/v2/Validator.php2154
-rw-r--r--includes/pear/PEAR/PackageFile/v2/rw.php1607
-rw-r--r--includes/pear/PEAR/Packager.php201
-rw-r--r--includes/pear/PEAR/REST.php483
-rw-r--r--includes/pear/PEAR/REST/10.php871
-rw-r--r--includes/pear/PEAR/REST/11.php341
-rw-r--r--includes/pear/PEAR/REST/13.php299
-rw-r--r--includes/pear/PEAR/REST/14.php120
-rw-r--r--includes/pear/PEAR/Registry.php2339
-rw-r--r--includes/pear/PEAR/RunTest.php973
-rw-r--r--includes/pear/PEAR/Start.php427
-rw-r--r--includes/pear/PEAR/Start/CLI.php616
-rw-r--r--includes/pear/PEAR/Task/Common.php202
-rw-r--r--includes/pear/PEAR/Task/Postinstallscript.php323
-rw-r--r--includes/pear/PEAR/Task/Postinstallscript/rw.php169
-rw-r--r--includes/pear/PEAR/Task/Replace.php176
-rw-r--r--includes/pear/PEAR/Task/Replace/rw.php61
-rw-r--r--includes/pear/PEAR/Task/Unixeol.php77
-rw-r--r--includes/pear/PEAR/Task/Unixeol/rw.php56
-rw-r--r--includes/pear/PEAR/Task/Windowseol.php77
-rw-r--r--includes/pear/PEAR/Task/Windowseol/rw.php56
-rw-r--r--includes/pear/PEAR/Validate.php629
-rw-r--r--includes/pear/PEAR/Validator/PECL.php63
-rw-r--r--includes/pear/PEAR/Warning.php379
-rw-r--r--includes/pear/PEAR/XMLParser.php253
-rw-r--r--includes/pear/PEAR5.php33
-rw-r--r--includes/pear/Structures/Graph.php154
-rw-r--r--includes/pear/Structures/Graph/Manipulator/AcyclicTest.php136
-rw-r--r--includes/pear/Structures/Graph/Manipulator/TopologicalSorter.php153
-rw-r--r--includes/pear/Structures/Graph/Node.php342
-rw-r--r--includes/pear/System.php624
-rw-r--r--includes/pear/System/Command.php598
-rw-r--r--includes/pear/Text/Diff.php453
-rw-r--r--includes/pear/Text/Diff/Engine/native.php438
-rw-r--r--includes/pear/Text/Diff/Engine/shell.php164
-rw-r--r--includes/pear/Text/Diff/Engine/string.php250
-rw-r--r--includes/pear/Text/Diff/Engine/xdiff.php66
-rw-r--r--includes/pear/Text/Diff/Mapped.php55
-rw-r--r--includes/pear/Text/Diff/Renderer.php237
-rw-r--r--includes/pear/Text/Diff/Renderer/context.php77
-rw-r--r--includes/pear/Text/Diff/Renderer/inline.php172
-rw-r--r--includes/pear/Text/Diff/Renderer/unified.php67
-rw-r--r--includes/pear/Text/Diff/ThreeWay.php276
-rw-r--r--includes/pear/Text/Diff3.php276
-rw-r--r--includes/pear/Text/Wiki.php1550
-rw-r--r--includes/pear/Text/Wiki/Creole.php150
-rw-r--r--includes/pear/Text/Wiki/Default.php27
-rw-r--r--includes/pear/Text/Wiki/Parse.php264
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Address.php67
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Blockquote.php176
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Box.php81
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Break.php73
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Center.php78
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Delimiter.php68
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Emphasis.php83
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Footnote.php83
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Heading.php97
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Horiz.php58
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Image.php69
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/List.php244
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Newline.php60
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Paragraph.php139
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Prefilter.php54
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Preformatted.php68
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Raw.php61
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Strong.php84
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Subscript.php75
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Superscript.php75
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Table.php207
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Tighten.php37
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Trim.php75
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Tt.php78
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Underline.php83
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Url.php109
-rw-r--r--includes/pear/Text/Wiki/Parse/Creole/Wikilink.php322
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Anchor.php87
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Blockquote.php180
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Bold.php79
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Break.php72
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Center.php78
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Code.php99
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Colortext.php89
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Deflist.php122
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Delimiter.php80
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Embed.php106
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Emphasis.php85
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Freelink.php134
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Function.php141
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Heading.php107
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Horiz.php70
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Html.php75
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Image.php136
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Include.php100
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Interwiki.php138
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Italic.php85
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/List.php262
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Newline.php75
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Paragraph.php116
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Phplookup.php73
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Prefilter.php84
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Raw.php73
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Revise.php145
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Smiley.php157
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Strong.php86
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Subscript.php79
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Superscript.php79
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Table.php226
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Tighten.php49
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Toc.php130
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Tt.php84
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Underline.php79
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Url.php281
-rw-r--r--includes/pear/Text/Wiki/Parse/Default/Wikilink.php204
-rw-r--r--includes/pear/Text/Wiki/Render.php218
-rw-r--r--includes/pear/Text/Wiki/Render/Creole.php16
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Address.php29
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Anchor.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Blockquote.php47
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Bold.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Box.php30
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Break.php24
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Center.php30
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Code.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Colortext.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Deflist.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Delimiter.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Embed.php29
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Emphasis.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Freelink.php9
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Function.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Heading.php16
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Horiz.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Html.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Image.php26
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Include.php16
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Interwiki.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Italic.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/List.php53
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Newline.php12
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Paragraph.php29
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Phplookup.php25
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Prefilter.php10
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Preformatted.php27
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Raw.php33
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Revise.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Strong.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Subscript.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Superscript.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Table.php70
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Tighten.php10
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Toc.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Tt.php29
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Underline.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Url.php40
-rw-r--r--includes/pear/Text/Wiki/Render/Creole/Wikilink.php43
-rw-r--r--includes/pear/Text/Wiki/Render/Latex.php90
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Anchor.php33
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Blockquote.php36
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Bold.php28
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Box.php54
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Break.php24
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Center.php33
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Code.php26
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Colortext.php58
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Deflist.php53
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Delimiter.php25
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Embed.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Emphasis.php29
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Font.php73
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Freelink.php6
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Function.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Heading.php33
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Horiz.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Html.php25
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Image.php70
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Include.php8
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Interwiki.php58
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Italic.php28
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/List.php76
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Newline.php12
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Page.php48
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Paragraph.php31
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Phplookup.php34
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Plugin.php49
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Prefilter.php56
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Preformatted.php48
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Raw.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Revise.php38
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Smiley.php44
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Specialchar.php54
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Strong.php30
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Subscript.php54
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Superscript.php31
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Table.php99
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Tighten.php9
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Titlebar.php54
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Toc.php30
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Tt.php30
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Underline.php30
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Url.php41
-rw-r--r--includes/pear/Text/Wiki/Render/Latex/Wikilink.php60
-rw-r--r--includes/pear/Text/Wiki/Render/Plain.php16
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Anchor.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Blockquote.php39
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Bold.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Box.php48
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Break.php24
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Center.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Code.php24
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Colortext.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Deflist.php59
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Delimiter.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Embed.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Emphasis.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Font.php44
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Freelink.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Function.php39
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Heading.php14
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Horiz.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Html.php24
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Image.php22
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Include.php8
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Interwiki.php29
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Italic.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/List.php68
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Newline.php12
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Page.php48
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Paragraph.php31
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Phplookup.php25
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Plugin.php49
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Prefilter.php24
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Preformatted.php48
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Raw.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Revise.php24
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Smiley.php44
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Specialchar.php54
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Strong.php24
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Subscript.php48
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Superscript.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Table.php65
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Tighten.php10
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Titlebar.php54
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Toc.php39
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Tt.php24
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Underline.php23
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Url.php29
-rw-r--r--includes/pear/Text/Wiki/Render/Plain/Wikilink.php24
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml.php107
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Address.php54
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Anchor.php48
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Blockquote.php72
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Bold.php57
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Box.php62
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Break.php52
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Center.php62
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Code.php133
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Colortext.php79
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Deflist.php87
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Delimiter.php46
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Embed.php46
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Emphasis.php58
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Font.php83
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Freelink.php35
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Function.php108
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Heading.php88
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Horiz.php51
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Html.php47
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Image.php184
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Include.php32
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Interwiki.php103
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Italic.php57
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/List.php172
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Newline.php35
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Page.php46
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Paragraph.php59
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Phplookup.php81
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Plugin.php47
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Prefilter.php34
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Preformatted.php47
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Raw.php46
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Revise.php68
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Smiley.php74
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Specialchar.php52
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Strong.php58
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Subscript.php57
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Superscript.php57
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Table.php140
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Tighten.php34
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Titlebar.php57
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Toc.php115
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Tt.php58
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Underline.php57
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Url.php131
-rw-r--r--includes/pear/Text/Wiki/Render/Xhtml/Wikilink.php177
-rw-r--r--includes/pear/XML/Util.php911
402 files changed, 84321 insertions, 0 deletions
diff --git a/includes/pear/Archive/Tar.php b/includes/pear/Archive/Tar.php
new file mode 100644
index 0000000..61eff98
--- /dev/null
+++ b/includes/pear/Archive/Tar.php
@@ -0,0 +1,1909 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * File::CSV
+ *
+ * PHP versions 4 and 5
+ *
+ * Copyright (c) 1997-2008,
+ * Vincent Blavet <vincent@phpconcept.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 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.
+ *
+ *
+ * @category File_Formats
+ * @package Archive_Tar
+ * @author Vincent Blavet <vincent@phpconcept.net>
+ * @copyright 1997-2008 The Authors
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: Tar.php 295988 2010-03-09 08:39:37Z mrook $
+ * @link http://pear.php.net/package/Archive_Tar
+ */
+
+require_once 'PEAR.php';
+
+
+define ('ARCHIVE_TAR_ATT_SEPARATOR', 90001);
+define ('ARCHIVE_TAR_END_BLOCK', pack("a512", ''));
+
+/**
+* Creates a (compressed) Tar archive
+*
+* @author Vincent Blavet <vincent@phpconcept.net>
+* @version $Revision: 295988 $
+* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+* @package Archive_Tar
+*/
+class Archive_Tar extends PEAR
+{
+ /**
+ * @var string Name of the Tar
+ */
+ var $_tarname='';
+
+ /**
+ * @var boolean if true, the Tar file will be gzipped
+ */
+ var $_compress=false;
+
+ /**
+ * @var string Type of compression : 'none', 'gz' or 'bz2'
+ */
+ var $_compress_type='none';
+
+ /**
+ * @var string Explode separator
+ */
+ var $_separator=' ';
+
+ /**
+ * @var file descriptor
+ */
+ var $_file=0;
+
+ /**
+ * @var string Local Tar name of a remote Tar (http:// or ftp://)
+ */
+ var $_temp_tarname='';
+
+ /**
+ * @var string regular expression for ignoring files or directories
+ */
+ var $_ignore_regexp='';
+
+ // {{{ constructor
+ /**
+ * Archive_Tar Class constructor. This flavour of the constructor only
+ * declare a new Archive_Tar object, identifying it by the name of the
+ * tar file.
+ * If the compress argument is set the tar will be read or created as a
+ * gzip or bz2 compressed TAR file.
+ *
+ * @param string $p_tarname The name of the tar archive to create
+ * @param string $p_compress can be null, 'gz' or 'bz2'. This
+ * parameter indicates if gzip or bz2 compression
+ * is required. For compatibility reason the
+ * boolean value 'true' means 'gz'.
+ * @access public
+ */
+ function Archive_Tar($p_tarname, $p_compress = null)
+ {
+ $this->PEAR();
+ $this->_compress = false;
+ $this->_compress_type = 'none';
+ if (($p_compress === null) || ($p_compress == '')) {
+ if (@file_exists($p_tarname)) {
+ if ($fp = @fopen($p_tarname, "rb")) {
+ // look for gzip magic cookie
+ $data = fread($fp, 2);
+ fclose($fp);
+ if ($data == "\37\213") {
+ $this->_compress = true;
+ $this->_compress_type = 'gz';
+ // No sure it's enought for a magic code ....
+ } elseif ($data == "BZ") {
+ $this->_compress = true;
+ $this->_compress_type = 'bz2';
+ }
+ }
+ } else {
+ // probably a remote file or some file accessible
+ // through a stream interface
+ if (substr($p_tarname, -2) == 'gz') {
+ $this->_compress = true;
+ $this->_compress_type = 'gz';
+ } elseif ((substr($p_tarname, -3) == 'bz2') ||
+ (substr($p_tarname, -2) == 'bz')) {
+ $this->_compress = true;
+ $this->_compress_type = 'bz2';
+ }
+ }
+ } else {
+ if (($p_compress === true) || ($p_compress == 'gz')) {
+ $this->_compress = true;
+ $this->_compress_type = 'gz';
+ } else if ($p_compress == 'bz2') {
+ $this->_compress = true;
+ $this->_compress_type = 'bz2';
+ } else {
+ $this->_error("Unsupported compression type '$p_compress'\n".
+ "Supported types are 'gz' and 'bz2'.\n");
+ return false;
+ }
+ }
+ $this->_tarname = $p_tarname;
+ if ($this->_compress) { // assert zlib or bz2 extension support
+ if ($this->_compress_type == 'gz')
+ $extname = 'zlib';
+ else if ($this->_compress_type == 'bz2')
+ $extname = 'bz2';
+
+ if (!extension_loaded($extname)) {
+ PEAR::loadExtension($extname);
+ }
+ if (!extension_loaded($extname)) {
+ $this->_error("The extension '$extname' couldn't be found.\n".
+ "Please make sure your version of PHP was built ".
+ "with '$extname' support.\n");
+ return false;
+ }
+ }
+ }
+ // }}}
+
+ // {{{ destructor
+ function _Archive_Tar()
+ {
+ $this->_close();
+ // ----- Look for a local copy to delete
+ if ($this->_temp_tarname != '')
+ @unlink($this->_temp_tarname);
+ $this->_PEAR();
+ }
+ // }}}
+
+ // {{{ create()
+ /**
+ * This method creates the archive file and add the files / directories
+ * that are listed in $p_filelist.
+ * If a file with the same name exist and is writable, it is replaced
+ * by the new tar.
+ * The method return false and a PEAR error text.
+ * The $p_filelist parameter can be an array of string, each string
+ * representing a filename or a directory name with their path if
+ * needed. It can also be a single string with names separated by a
+ * single blank.
+ * For each directory added in the archive, the files and
+ * sub-directories are also added.
+ * See also createModify() method for more details.
+ *
+ * @param array $p_filelist An array of filenames and directory names, or a
+ * single string with names separated by a single
+ * blank space.
+ * @return true on success, false on error.
+ * @see createModify()
+ * @access public
+ */
+ function create($p_filelist)
+ {
+ return $this->createModify($p_filelist, '', '');
+ }
+ // }}}
+
+ // {{{ add()
+ /**
+ * This method add the files / directories that are listed in $p_filelist in
+ * the archive. If the archive does not exist it is created.
+ * The method return false and a PEAR error text.
+ * The files and directories listed are only added at the end of the archive,
+ * even if a file with the same name is already archived.
+ * See also createModify() method for more details.
+ *
+ * @param array $p_filelist An array of filenames and directory names, or a
+ * single string with names separated by a single
+ * blank space.
+ * @return true on success, false on error.
+ * @see createModify()
+ * @access public
+ */
+ function add($p_filelist)
+ {
+ return $this->addModify($p_filelist, '', '');
+ }
+ // }}}
+
+ // {{{ extract()
+ function extract($p_path='')
+ {
+ return $this->extractModify($p_path, '');
+ }
+ // }}}
+
+ // {{{ listContent()
+ function listContent()
+ {
+ $v_list_detail = array();
+
+ if ($this->_openRead()) {
+ if (!$this->_extractList('', $v_list_detail, "list", '', '')) {
+ unset($v_list_detail);
+ $v_list_detail = 0;
+ }
+ $this->_close();
+ }
+
+ return $v_list_detail;
+ }
+ // }}}
+
+ // {{{ createModify()
+ /**
+ * This method creates the archive file and add the files / directories
+ * that are listed in $p_filelist.
+ * If the file already exists and is writable, it is replaced by the
+ * new tar. It is a create and not an add. If the file exists and is
+ * read-only or is a directory it is not replaced. The method return
+ * false and a PEAR error text.
+ * The $p_filelist parameter can be an array of string, each string
+ * representing a filename or a directory name with their path if
+ * needed. It can also be a single string with names separated by a
+ * single blank.
+ * The path indicated in $p_remove_dir will be removed from the
+ * memorized path of each file / directory listed when this path
+ * exists. By default nothing is removed (empty path '')
+ * The path indicated in $p_add_dir will be added at the beginning of
+ * the memorized path of each file / directory listed. However it can
+ * be set to empty ''. The adding of a path is done after the removing
+ * of path.
+ * The path add/remove ability enables the user to prepare an archive
+ * for extraction in a different path than the origin files are.
+ * See also addModify() method for file adding properties.
+ *
+ * @param array $p_filelist An array of filenames and directory names,
+ * or a single string with names separated by
+ * a single blank space.
+ * @param string $p_add_dir A string which contains a path to be added
+ * to the memorized path of each element in
+ * the list.
+ * @param string $p_remove_dir A string which contains a path to be
+ * removed from the memorized path of each
+ * element in the list, when relevant.
+ * @return boolean true on success, false on error.
+ * @access public
+ * @see addModify()
+ */
+ function createModify($p_filelist, $p_add_dir, $p_remove_dir='')
+ {
+ $v_result = true;
+
+ if (!$this->_openWrite())
+ return false;
+
+ if ($p_filelist != '') {
+ if (is_array($p_filelist))
+ $v_list = $p_filelist;
+ elseif (is_string($p_filelist))
+ $v_list = explode($this->_separator, $p_filelist);
+ else {
+ $this->_cleanFile();
+ $this->_error('Invalid file list');
+ return false;
+ }
+
+ $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir);
+ }
+
+ if ($v_result) {
+ $this->_writeFooter();
+ $this->_close();
+ } else
+ $this->_cleanFile();
+
+ return $v_result;
+ }
+ // }}}
+
+ // {{{ addModify()
+ /**
+ * This method add the files / directories listed in $p_filelist at the
+ * end of the existing archive. If the archive does not yet exists it
+ * is created.
+ * The $p_filelist parameter can be an array of string, each string
+ * representing a filename or a directory name with their path if
+ * needed. It can also be a single string with names separated by a
+ * single blank.
+ * The path indicated in $p_remove_dir will be removed from the
+ * memorized path of each file / directory listed when this path
+ * exists. By default nothing is removed (empty path '')
+ * The path indicated in $p_add_dir will be added at the beginning of
+ * the memorized path of each file / directory listed. However it can
+ * be set to empty ''. The adding of a path is done after the removing
+ * of path.
+ * The path add/remove ability enables the user to prepare an archive
+ * for extraction in a different path than the origin files are.
+ * If a file/dir is already in the archive it will only be added at the
+ * end of the archive. There is no update of the existing archived
+ * file/dir. However while extracting the archive, the last file will
+ * replace the first one. This results in a none optimization of the
+ * archive size.
+ * If a file/dir does not exist the file/dir is ignored. However an
+ * error text is send to PEAR error.
+ * If a file/dir is not readable the file/dir is ignored. However an
+ * error text is send to PEAR error.
+ *
+ * @param array $p_filelist An array of filenames and directory
+ * names, or a single string with names
+ * separated by a single blank space.
+ * @param string $p_add_dir A string which contains a path to be
+ * added to the memorized path of each
+ * element in the list.
+ * @param string $p_remove_dir A string which contains a path to be
+ * removed from the memorized path of
+ * each element in the list, when
+ * relevant.
+ * @return true on success, false on error.
+ * @access public
+ */
+ function addModify($p_filelist, $p_add_dir, $p_remove_dir='')
+ {
+ $v_result = true;
+
+ if (!$this->_isArchive())
+ $v_result = $this->createModify($p_filelist, $p_add_dir,
+ $p_remove_dir);
+ else {
+ if (is_array($p_filelist))
+ $v_list = $p_filelist;
+ elseif (is_string($p_filelist))
+ $v_list = explode($this->_separator, $p_filelist);
+ else {
+ $this->_error('Invalid file list');
+ return false;
+ }
+
+ $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir);
+ }
+
+ return $v_result;
+ }
+ // }}}
+
+ // {{{ addString()
+ /**
+ * This method add a single string as a file at the
+ * end of the existing archive. If the archive does not yet exists it
+ * is created.
+ *
+ * @param string $p_filename A string which contains the full
+ * filename path that will be associated
+ * with the string.
+ * @param string $p_string The content of the file added in
+ * the archive.
+ * @return true on success, false on error.
+ * @access public
+ */
+ function addString($p_filename, $p_string)
+ {
+ $v_result = true;
+
+ if (!$this->_isArchive()) {
+ if (!$this->_openWrite()) {
+ return false;
+ }
+ $this->_close();
+ }
+
+ if (!$this->_openAppend())
+ return false;
+
+ // Need to check the get back to the temporary file ? ....
+ $v_result = $this->_addString($p_filename, $p_string);
+
+ $this->_writeFooter();
+
+ $this->_close();
+
+ return $v_result;
+ }
+ // }}}
+
+ // {{{ extractModify()
+ /**
+ * This method extract all the content of the archive in the directory
+ * indicated by $p_path. When relevant the memorized path of the
+ * files/dir can be modified by removing the $p_remove_path path at the
+ * beginning of the file/dir path.
+ * While extracting a file, if the directory path does not exists it is
+ * created.
+ * While extracting a file, if the file already exists it is replaced
+ * without looking for last modification date.
+ * While extracting a file, if the file already exists and is write
+ * protected, the extraction is aborted.
+ * While extracting a file, if a directory with the same name already
+ * exists, the extraction is aborted.
+ * While extracting a directory, if a file with the same name already
+ * exists, the extraction is aborted.
+ * While extracting a file/directory if the destination directory exist
+ * and is write protected, or does not exist but can not be created,
+ * the extraction is aborted.
+ * If after extraction an extracted file does not show the correct
+ * stored file size, the extraction is aborted.
+ * When the extraction is aborted, a PEAR error text is set and false
+ * is returned. However the result can be a partial extraction that may
+ * need to be manually cleaned.
+ *
+ * @param string $p_path The path of the directory where the
+ * files/dir need to by extracted.
+ * @param string $p_remove_path Part of the memorized path that can be
+ * removed if present at the beginning of
+ * the file/dir path.
+ * @return boolean true on success, false on error.
+ * @access public
+ * @see extractList()
+ */
+ function extractModify($p_path, $p_remove_path)
+ {
+ $v_result = true;
+ $v_list_detail = array();
+
+ if ($v_result = $this->_openRead()) {
+ $v_result = $this->_extractList($p_path, $v_list_detail,
+ "complete", 0, $p_remove_path);
+ $this->_close();
+ }
+
+ return $v_result;
+ }
+ // }}}
+
+ // {{{ extractInString()
+ /**
+ * This method extract from the archive one file identified by $p_filename.
+ * The return value is a string with the file content, or NULL on error.
+ * @param string $p_filename The path of the file to extract in a string.
+ * @return a string with the file content or NULL.
+ * @access public
+ */
+ function extractInString($p_filename)
+ {
+ if ($this->_openRead()) {
+ $v_result = $this->_extractInString($p_filename);
+ $this->_close();
+ } else {
+ $v_result = NULL;
+ }
+
+ return $v_result;
+ }
+ // }}}
+
+ // {{{ extractList()
+ /**
+ * This method extract from the archive only the files indicated in the
+ * $p_filelist. These files are extracted in the current directory or
+ * in the directory indicated by the optional $p_path parameter.
+ * If indicated the $p_remove_path can be used in the same way as it is
+ * used in extractModify() method.
+ * @param array $p_filelist An array of filenames and directory names,
+ * or a single string with names separated
+ * by a single blank space.
+ * @param string $p_path The path of the directory where the
+ * files/dir need to by extracted.
+ * @param string $p_remove_path Part of the memorized path that can be
+ * removed if present at the beginning of
+ * the file/dir path.
+ * @return true on success, false on error.
+ * @access public
+ * @see extractModify()
+ */
+ function extractList($p_filelist, $p_path='', $p_remove_path='')
+ {
+ $v_result = true;
+ $v_list_detail = array();
+
+ if (is_array($p_filelist))
+ $v_list = $p_filelist;
+ elseif (is_string($p_filelist))
+ $v_list = explode($this->_separator, $p_filelist);
+ else {
+ $this->_error('Invalid string list');
+ return false;
+ }
+
+ if ($v_result = $this->_openRead()) {
+ $v_result = $this->_extractList($p_path, $v_list_detail, "partial",
+ $v_list, $p_remove_path);
+ $this->_close();
+ }
+
+ return $v_result;
+ }
+ // }}}
+
+ // {{{ setAttribute()
+ /**
+ * This method set specific attributes of the archive. It uses a variable
+ * list of parameters, in the format attribute code + attribute values :
+ * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ',');
+ * @param mixed $argv variable list of attributes and values
+ * @return true on success, false on error.
+ * @access public
+ */
+ function setAttribute()
+ {
+ $v_result = true;
+
+ // ----- Get the number of variable list of arguments
+ if (($v_size = func_num_args()) == 0) {
+ return true;
+ }
+
+ // ----- Get the arguments
+ $v_att_list = &func_get_args();
+
+ // ----- Read the attributes
+ $i=0;
+ while ($i<$v_size) {
+
+ // ----- Look for next option
+ switch ($v_att_list[$i]) {
+ // ----- Look for options that request a string value
+ case ARCHIVE_TAR_ATT_SEPARATOR :
+ // ----- Check the number of parameters
+ if (($i+1) >= $v_size) {
+ $this->_error('Invalid number of parameters for '
+ .'attribute ARCHIVE_TAR_ATT_SEPARATOR');
+ return false;
+ }
+
+ // ----- Get the value
+ $this->_separator = $v_att_list[$i+1];
+ $i++;
+ break;
+
+ default :
+ $this->_error('Unknow attribute code '.$v_att_list[$i].'');
+ return false;
+ }
+
+ // ----- Next attribute
+ $i++;
+ }
+
+ return $v_result;
+ }
+ // }}}
+
+ // {{{ setIgnoreRegexp()
+ /**
+ * This method sets the regular expression for ignoring files and directories
+ * at import, for example:
+ * $arch->setIgnoreRegexp("#CVS|\.svn#");
+ * @param string $regexp regular expression defining which files or directories to ignore
+ * @access public
+ */
+ function setIgnoreRegexp($regexp)
+ {
+ $this->_ignore_regexp = $regexp;
+ }
+ // }}}
+
+ // {{{ setIgnoreList()
+ /**
+ * This method sets the regular expression for ignoring all files and directories
+ * matching the filenames in the array list at import, for example:
+ * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool'));
+ * @param array $list a list of file or directory names to ignore
+ * @access public
+ */
+ function setIgnoreList($list)
+ {
+ $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list);
+ $regexp = '#/'.join('$|/', $list).'#';
+ $this->setIgnoreRegexp($regexp);
+ }
+ // }}}
+
+ // {{{ _error()
+ function _error($p_message)
+ {
+ // ----- To be completed
+ $this->raiseError($p_message);
+ }
+ // }}}
+
+ // {{{ _warning()
+ function _warning($p_message)
+ {
+ // ----- To be completed
+ $this->raiseError($p_message);
+ }
+ // }}}
+
+ // {{{ _isArchive()
+ function _isArchive($p_filename=NULL)
+ {
+ if ($p_filename == NULL) {
+ $p_filename = $this->_tarname;
+ }
+ clearstatcache();
+ return @is_file($p_filename) && !@is_link($p_filename);
+ }
+ // }}}
+
+ // {{{ _openWrite()
+ function _openWrite()
+ {
+ if ($this->_compress_type == 'gz')
+ $this->_file = @gzopen($this->_tarname, "wb9");
+ else if ($this->_compress_type == 'bz2')
+ $this->_file = @bzopen($this->_tarname, "w");
+ else if ($this->_compress_type == 'none')
+ $this->_file = @fopen($this->_tarname, "wb");
+ else
+ $this->_error('Unknown or missing compression type ('
+ .$this->_compress_type.')');
+
+ if ($this->_file == 0) {
+ $this->_error('Unable to open in write mode \''
+ .$this->_tarname.'\'');
+ return false;
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _openRead()
+ function _openRead()
+ {
+ if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') {
+
+ // ----- Look if a local copy need to be done
+ if ($this->_temp_tarname == '') {
+ $this->_temp_tarname = uniqid('tar').'.tmp';
+ if (!$v_file_from = @fopen($this->_tarname, 'rb')) {
+ $this->_error('Unable to open in read mode \''
+ .$this->_tarname.'\'');
+ $this->_temp_tarname = '';
+ return false;
+ }
+ if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) {
+ $this->_error('Unable to open in write mode \''
+ .$this->_temp_tarname.'\'');
+ $this->_temp_tarname = '';
+ return false;
+ }
+ while ($v_data = @fread($v_file_from, 1024))
+ @fwrite($v_file_to, $v_data);
+ @fclose($v_file_from);
+ @fclose($v_file_to);
+ }
+
+ // ----- File to open if the local copy
+ $v_filename = $this->_temp_tarname;
+
+ } else
+ // ----- File to open if the normal Tar file
+ $v_filename = $this->_tarname;
+
+ if ($this->_compress_type == 'gz')
+ $this->_file = @gzopen($v_filename, "rb");
+ else if ($this->_compress_type == 'bz2')
+ $this->_file = @bzopen($v_filename, "r");
+ else if ($this->_compress_type == 'none')
+ $this->_file = @fopen($v_filename, "rb");
+ else
+ $this->_error('Unknown or missing compression type ('
+ .$this->_compress_type.')');
+
+ if ($this->_file == 0) {
+ $this->_error('Unable to open in read mode \''.$v_filename.'\'');
+ return false;
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _openReadWrite()
+ function _openReadWrite()
+ {
+ if ($this->_compress_type == 'gz')
+ $this->_file = @gzopen($this->_tarname, "r+b");
+ else if ($this->_compress_type == 'bz2') {
+ $this->_error('Unable to open bz2 in read/write mode \''
+ .$this->_tarname.'\' (limitation of bz2 extension)');
+ return false;
+ } else if ($this->_compress_type == 'none')
+ $this->_file = @fopen($this->_tarname, "r+b");
+ else
+ $this->_error('Unknown or missing compression type ('
+ .$this->_compress_type.')');
+
+ if ($this->_file == 0) {
+ $this->_error('Unable to open in read/write mode \''
+ .$this->_tarname.'\'');
+ return false;
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _close()
+ function _close()
+ {
+ //if (isset($this->_file)) {
+ if (is_resource($this->_file)) {
+ if ($this->_compress_type == 'gz')
+ @gzclose($this->_file);
+ else if ($this->_compress_type == 'bz2')
+ @bzclose($this->_file);
+ else if ($this->_compress_type == 'none')
+ @fclose($this->_file);
+ else
+ $this->_error('Unknown or missing compression type ('
+ .$this->_compress_type.')');
+
+ $this->_file = 0;
+ }
+
+ // ----- Look if a local copy need to be erase
+ // Note that it might be interesting to keep the url for a time : ToDo
+ if ($this->_temp_tarname != '') {
+ @unlink($this->_temp_tarname);
+ $this->_temp_tarname = '';
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _cleanFile()
+ function _cleanFile()
+ {
+ $this->_close();
+
+ // ----- Look for a local copy
+ if ($this->_temp_tarname != '') {
+ // ----- Remove the local copy but not the remote tarname
+ @unlink($this->_temp_tarname);
+ $this->_temp_tarname = '';
+ } else {
+ // ----- Remove the local tarname file
+ @unlink($this->_tarname);
+ }
+ $this->_tarname = '';
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _writeBlock()
+ function _writeBlock($p_binary_data, $p_len=null)
+ {
+ if (is_resource($this->_file)) {
+ if ($p_len === null) {
+ if ($this->_compress_type == 'gz')
+ @gzputs($this->_file, $p_binary_data);
+ else if ($this->_compress_type == 'bz2')
+ @bzwrite($this->_file, $p_binary_data);
+ else if ($this->_compress_type == 'none')
+ @fputs($this->_file, $p_binary_data);
+ else
+ $this->_error('Unknown or missing compression type ('
+ .$this->_compress_type.')');
+ } else {
+ if ($this->_compress_type == 'gz')
+ @gzputs($this->_file, $p_binary_data, $p_len);
+ else if ($this->_compress_type == 'bz2')
+ @bzwrite($this->_file, $p_binary_data, $p_len);
+ else if ($this->_compress_type == 'none')
+ @fputs($this->_file, $p_binary_data, $p_len);
+ else
+ $this->_error('Unknown or missing compression type ('
+ .$this->_compress_type.')');
+
+ }
+ }
+ return true;
+ }
+ // }}}
+
+ // {{{ _readBlock()
+ function _readBlock()
+ {
+ $v_block = null;
+ if (is_resource($this->_file)) {
+ if ($this->_compress_type == 'gz')
+ $v_block = @gzread($this->_file, 512);
+ else if ($this->_compress_type == 'bz2')
+ $v_block = @bzread($this->_file, 512);
+ else if ($this->_compress_type == 'none')
+ $v_block = @fread($this->_file, 512);
+ else
+ $this->_error('Unknown or missing compression type ('
+ .$this->_compress_type.')');
+ }
+ return $v_block;
+ }
+ // }}}
+
+ // {{{ _jumpBlock()
+ function _jumpBlock($p_len=null)
+ {
+ if (is_resource($this->_file)) {
+ if ($p_len === null)
+ $p_len = 1;
+
+ if ($this->_compress_type == 'gz') {
+ @gzseek($this->_file, gztell($this->_file)+($p_len*512));
+ }
+ else if ($this->_compress_type == 'bz2') {
+ // ----- Replace missing bztell() and bzseek()
+ for ($i=0; $i<$p_len; $i++)
+ $this->_readBlock();
+ } else if ($this->_compress_type == 'none')
+ @fseek($this->_file, $p_len*512, SEEK_CUR);
+ else
+ $this->_error('Unknown or missing compression type ('
+ .$this->_compress_type.')');
+
+ }
+ return true;
+ }
+ // }}}
+
+ // {{{ _writeFooter()
+ function _writeFooter()
+ {
+ if (is_resource($this->_file)) {
+ // ----- Write the last 0 filled block for end of archive
+ $v_binary_data = pack('a1024', '');
+ $this->_writeBlock($v_binary_data);
+ }
+ return true;
+ }
+ // }}}
+
+ // {{{ _addList()
+ function _addList($p_list, $p_add_dir, $p_remove_dir)
+ {
+ $v_result=true;
+ $v_header = array();
+
+ // ----- Remove potential windows directory separator
+ $p_add_dir = $this->_translateWinPath($p_add_dir);
+ $p_remove_dir = $this->_translateWinPath($p_remove_dir, false);
+
+ if (!$this->_file) {
+ $this->_error('Invalid file descriptor');
+ return false;
+ }
+
+ if (sizeof($p_list) == 0)
+ return true;
+
+ foreach ($p_list as $v_filename) {
+ if (!$v_result) {
+ break;
+ }
+
+ // ----- Skip the current tar name
+ if ($v_filename == $this->_tarname)
+ continue;
+
+ if ($v_filename == '')
+ continue;
+
+ // ----- ignore files and directories matching the ignore regular expression
+ if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/'.$v_filename)) {
+ $this->_warning("File '$v_filename' ignored");
+ continue;
+ }
+
+ if (!file_exists($v_filename)) {
+ $this->_warning("File '$v_filename' does not exist");
+ continue;
+ }
+
+ // ----- Add the file or directory header
+ if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir))
+ return false;
+
+ if (@is_dir($v_filename) && !@is_link($v_filename)) {
+ if (!($p_hdir = opendir($v_filename))) {
+ $this->_warning("Directory '$v_filename' can not be read");
+ continue;
+ }
+ while (false !== ($p_hitem = readdir($p_hdir))) {
+ if (($p_hitem != '.') && ($p_hitem != '..')) {
+ if ($v_filename != ".")
+ $p_temp_list[0] = $v_filename.'/'.$p_hitem;
+ else
+ $p_temp_list[0] = $p_hitem;
+
+ $v_result = $this->_addList($p_temp_list,
+ $p_add_dir,
+ $p_remove_dir);
+ }
+ }
+
+ unset($p_temp_list);
+ unset($p_hdir);
+ unset($p_hitem);
+ }
+ }
+
+ return $v_result;
+ }
+ // }}}
+
+ // {{{ _addFile()
+ function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir)
+ {
+ if (!$this->_file) {
+ $this->_error('Invalid file descriptor');
+ return false;
+ }
+
+ if ($p_filename == '') {
+ $this->_error('Invalid file name');
+ return false;
+ }
+
+ // ----- Calculate the stored filename
+ $p_filename = $this->_translateWinPath($p_filename, false);;
+ $v_stored_filename = $p_filename;
+ if (strcmp($p_filename, $p_remove_dir) == 0) {
+ return true;
+ }
+ if ($p_remove_dir != '') {
+ if (substr($p_remove_dir, -1) != '/')
+ $p_remove_dir .= '/';
+
+ if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir)
+ $v_stored_filename = substr($p_filename, strlen($p_remove_dir));
+ }
+ $v_stored_filename = $this->_translateWinPath($v_stored_filename);
+ if ($p_add_dir != '') {
+ if (substr($p_add_dir, -1) == '/')
+ $v_stored_filename = $p_add_dir.$v_stored_filename;
+ else
+ $v_stored_filename = $p_add_dir.'/'.$v_stored_filename;
+ }
+
+ $v_stored_filename = $this->_pathReduction($v_stored_filename);
+
+ if ($this->_isArchive($p_filename)) {
+ if (($v_file = @fopen($p_filename, "rb")) == 0) {
+ $this->_warning("Unable to open file '".$p_filename
+ ."' in binary read mode");
+ return true;
+ }
+
+ if (!$this->_writeHeader($p_filename, $v_stored_filename))
+ return false;
+
+ while (($v_buffer = fread($v_file, 512)) != '') {
+ $v_binary_data = pack("a512", "$v_buffer");
+ $this->_writeBlock($v_binary_data);
+ }
+
+ fclose($v_file);
+
+ } else {
+ // ----- Only header for dir
+ if (!$this->_writeHeader($p_filename, $v_stored_filename))
+ return false;
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _addString()
+ function _addString($p_filename, $p_string)
+ {
+ if (!$this->_file) {
+ $this->_error('Invalid file descriptor');
+ return false;
+ }
+
+ if ($p_filename == '') {
+ $this->_error('Invalid file name');
+ return false;
+ }
+
+ // ----- Calculate the stored filename
+ $p_filename = $this->_translateWinPath($p_filename, false);;
+
+ if (!$this->_writeHeaderBlock($p_filename, strlen($p_string),
+ time(), 384, "", 0, 0))
+ return false;
+
+ $i=0;
+ while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') {
+ $v_binary_data = pack("a512", $v_buffer);
+ $this->_writeBlock($v_binary_data);
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _writeHeader()
+ function _writeHeader($p_filename, $p_stored_filename)
+ {
+ if ($p_stored_filename == '')
+ $p_stored_filename = $p_filename;
+ $v_reduce_filename = $this->_pathReduction($p_stored_filename);
+
+ if (strlen($v_reduce_filename) > 99) {
+ if (!$this->_writeLongHeader($v_reduce_filename))
+ return false;
+ }
+
+ $v_info = lstat($p_filename);
+ $v_uid = sprintf("%07s", DecOct($v_info[4]));
+ $v_gid = sprintf("%07s", DecOct($v_info[5]));
+ $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777));
+
+ $v_mtime = sprintf("%011s", DecOct($v_info['mtime']));
+
+ $v_linkname = '';
+
+ if (@is_link($p_filename)) {
+ $v_typeflag = '2';
+ $v_linkname = readlink($p_filename);
+ $v_size = sprintf("%011s", DecOct(0));
+ } elseif (@is_dir($p_filename)) {
+ $v_typeflag = "5";
+ $v_size = sprintf("%011s", DecOct(0));
+ } else {
+ $v_typeflag = '0';
+ clearstatcache();
+ $v_size = sprintf("%011s", DecOct($v_info['size']));
+ }
+
+ $v_magic = 'ustar ';
+
+ $v_version = ' ';
+
+ if (function_exists('posix_getpwuid'))
+ {
+ $userinfo = posix_getpwuid($v_info[4]);
+ $groupinfo = posix_getgrgid($v_info[5]);
+
+ $v_uname = $userinfo['name'];
+ $v_gname = $groupinfo['name'];
+ }
+ else
+ {
+ $v_uname = '';
+ $v_gname = '';
+ }
+
+ $v_devmajor = '';
+
+ $v_devminor = '';
+
+ $v_prefix = '';
+
+ $v_binary_data_first = pack("a100a8a8a8a12a12",
+ $v_reduce_filename, $v_perms, $v_uid,
+ $v_gid, $v_size, $v_mtime);
+ $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
+ $v_typeflag, $v_linkname, $v_magic,
+ $v_version, $v_uname, $v_gname,
+ $v_devmajor, $v_devminor, $v_prefix, '');
+
+ // ----- Calculate the checksum
+ $v_checksum = 0;
+ // ..... First part of the header
+ for ($i=0; $i<148; $i++)
+ $v_checksum += ord(substr($v_binary_data_first,$i,1));
+ // ..... Ignore the checksum value and replace it by ' ' (space)
+ for ($i=148; $i<156; $i++)
+ $v_checksum += ord(' ');
+ // ..... Last part of the header
+ for ($i=156, $j=0; $i<512; $i++, $j++)
+ $v_checksum += ord(substr($v_binary_data_last,$j,1));
+
+ // ----- Write the first 148 bytes of the header in the archive
+ $this->_writeBlock($v_binary_data_first, 148);
+
+ // ----- Write the calculated checksum
+ $v_checksum = sprintf("%06s ", DecOct($v_checksum));
+ $v_binary_data = pack("a8", $v_checksum);
+ $this->_writeBlock($v_binary_data, 8);
+
+ // ----- Write the last 356 bytes of the header in the archive
+ $this->_writeBlock($v_binary_data_last, 356);
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _writeHeaderBlock()
+ function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0,
+ $p_type='', $p_uid=0, $p_gid=0)
+ {
+ $p_filename = $this->_pathReduction($p_filename);
+
+ if (strlen($p_filename) > 99) {
+ if (!$this->_writeLongHeader($p_filename))
+ return false;
+ }
+
+ if ($p_type == "5") {
+ $v_size = sprintf("%011s", DecOct(0));
+ } else {
+ $v_size = sprintf("%011s", DecOct($p_size));
+ }
+
+ $v_uid = sprintf("%07s", DecOct($p_uid));
+ $v_gid = sprintf("%07s", DecOct($p_gid));
+ $v_perms = sprintf("%07s", DecOct($p_perms & 000777));
+
+ $v_mtime = sprintf("%11s", DecOct($p_mtime));
+
+ $v_linkname = '';
+
+ $v_magic = 'ustar ';
+
+ $v_version = ' ';
+
+ if (function_exists('posix_getpwuid'))
+ {
+ $userinfo = posix_getpwuid($p_uid);
+ $groupinfo = posix_getgrgid($p_gid);
+
+ $v_uname = $userinfo['name'];
+ $v_gname = $groupinfo['name'];
+ }
+ else
+ {
+ $v_uname = '';
+ $v_gname = '';
+ }
+
+ $v_devmajor = '';
+
+ $v_devminor = '';
+
+ $v_prefix = '';
+
+ $v_binary_data_first = pack("a100a8a8a8a12A12",
+ $p_filename, $v_perms, $v_uid, $v_gid,
+ $v_size, $v_mtime);
+ $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
+ $p_type, $v_linkname, $v_magic,
+ $v_version, $v_uname, $v_gname,
+ $v_devmajor, $v_devminor, $v_prefix, '');
+
+ // ----- Calculate the checksum
+ $v_checksum = 0;
+ // ..... First part of the header
+ for ($i=0; $i<148; $i++)
+ $v_checksum += ord(substr($v_binary_data_first,$i,1));
+ // ..... Ignore the checksum value and replace it by ' ' (space)
+ for ($i=148; $i<156; $i++)
+ $v_checksum += ord(' ');
+ // ..... Last part of the header
+ for ($i=156, $j=0; $i<512; $i++, $j++)
+ $v_checksum += ord(substr($v_binary_data_last,$j,1));
+
+ // ----- Write the first 148 bytes of the header in the archive
+ $this->_writeBlock($v_binary_data_first, 148);
+
+ // ----- Write the calculated checksum
+ $v_checksum = sprintf("%06s ", DecOct($v_checksum));
+ $v_binary_data = pack("a8", $v_checksum);
+ $this->_writeBlock($v_binary_data, 8);
+
+ // ----- Write the last 356 bytes of the header in the archive
+ $this->_writeBlock($v_binary_data_last, 356);
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _writeLongHeader()
+ function _writeLongHeader($p_filename)
+ {
+ $v_size = sprintf("%11s ", DecOct(strlen($p_filename)));
+
+ $v_typeflag = 'L';
+
+ $v_linkname = '';
+
+ $v_magic = '';
+
+ $v_version = '';
+
+ $v_uname = '';
+
+ $v_gname = '';
+
+ $v_devmajor = '';
+
+ $v_devminor = '';
+
+ $v_prefix = '';
+
+ $v_binary_data_first = pack("a100a8a8a8a12a12",
+ '././@LongLink', 0, 0, 0, $v_size, 0);
+ $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
+ $v_typeflag, $v_linkname, $v_magic,
+ $v_version, $v_uname, $v_gname,
+ $v_devmajor, $v_devminor, $v_prefix, '');
+
+ // ----- Calculate the checksum
+ $v_checksum = 0;
+ // ..... First part of the header
+ for ($i=0; $i<148; $i++)
+ $v_checksum += ord(substr($v_binary_data_first,$i,1));
+ // ..... Ignore the checksum value and replace it by ' ' (space)
+ for ($i=148; $i<156; $i++)
+ $v_checksum += ord(' ');
+ // ..... Last part of the header
+ for ($i=156, $j=0; $i<512; $i++, $j++)
+ $v_checksum += ord(substr($v_binary_data_last,$j,1));
+
+ // ----- Write the first 148 bytes of the header in the archive
+ $this->_writeBlock($v_binary_data_first, 148);
+
+ // ----- Write the calculated checksum
+ $v_checksum = sprintf("%06s ", DecOct($v_checksum));
+ $v_binary_data = pack("a8", $v_checksum);
+ $this->_writeBlock($v_binary_data, 8);
+
+ // ----- Write the last 356 bytes of the header in the archive
+ $this->_writeBlock($v_binary_data_last, 356);
+
+ // ----- Write the filename as content of the block
+ $i=0;
+ while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') {
+ $v_binary_data = pack("a512", "$v_buffer");
+ $this->_writeBlock($v_binary_data);
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _readHeader()
+ function _readHeader($v_binary_data, &$v_header)
+ {
+ if (strlen($v_binary_data)==0) {
+ $v_header['filename'] = '';
+ return true;
+ }
+
+ if (strlen($v_binary_data) != 512) {
+ $v_header['filename'] = '';
+ $this->_error('Invalid block size : '.strlen($v_binary_data));
+ return false;
+ }
+
+ if (!is_array($v_header)) {
+ $v_header = array();
+ }
+ // ----- Calculate the checksum
+ $v_checksum = 0;
+ // ..... First part of the header
+ for ($i=0; $i<148; $i++)
+ $v_checksum+=ord(substr($v_binary_data,$i,1));
+ // ..... Ignore the checksum value and replace it by ' ' (space)
+ for ($i=148; $i<156; $i++)
+ $v_checksum += ord(' ');
+ // ..... Last part of the header
+ for ($i=156; $i<512; $i++)
+ $v_checksum+=ord(substr($v_binary_data,$i,1));
+
+ $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/"
+ ."a8checksum/a1typeflag/a100link/a6magic/a2version/"
+ ."a32uname/a32gname/a8devmajor/a8devminor",
+ $v_binary_data);
+
+ // ----- Extract the checksum
+ $v_header['checksum'] = OctDec(trim($v_data['checksum']));
+ if ($v_header['checksum'] != $v_checksum) {
+ $v_header['filename'] = '';
+
+ // ----- Look for last block (empty block)
+ if (($v_checksum == 256) && ($v_header['checksum'] == 0))
+ return true;
+
+ $this->_error('Invalid checksum for file "'.$v_data['filename']
+ .'" : '.$v_checksum.' calculated, '
+ .$v_header['checksum'].' expected');
+ return false;
+ }
+
+ // ----- Extract the properties
+ $v_header['filename'] = $v_data['filename'];
+ if ($this->_maliciousFilename($v_header['filename'])) {
+ $this->_error('Malicious .tar detected, file "' . $v_header['filename'] .
+ '" will not install in desired directory tree');
+ return false;
+ }
+ $v_header['mode'] = OctDec(trim($v_data['mode']));
+ $v_header['uid'] = OctDec(trim($v_data['uid']));
+ $v_header['gid'] = OctDec(trim($v_data['gid']));
+ $v_header['size'] = OctDec(trim($v_data['size']));
+ $v_header['mtime'] = OctDec(trim($v_data['mtime']));
+ if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {
+ $v_header['size'] = 0;
+ }
+ $v_header['link'] = trim($v_data['link']);
+ /* ----- All these fields are removed form the header because
+ they do not carry interesting info
+ $v_header[magic] = trim($v_data[magic]);
+ $v_header[version] = trim($v_data[version]);
+ $v_header[uname] = trim($v_data[uname]);
+ $v_header[gname] = trim($v_data[gname]);
+ $v_header[devmajor] = trim($v_data[devmajor]);
+ $v_header[devminor] = trim($v_data[devminor]);
+ */
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _maliciousFilename()
+ /**
+ * Detect and report a malicious file name
+ *
+ * @param string $file
+ * @return bool
+ * @access private
+ */
+ function _maliciousFilename($file)
+ {
+ if (strpos($file, '/../') !== false) {
+ return true;
+ }
+ if (strpos($file, '../') === 0) {
+ return true;
+ }
+ return false;
+ }
+ // }}}
+
+ // {{{ _readLongHeader()
+ function _readLongHeader(&$v_header)
+ {
+ $v_filename = '';
+ $n = floor($v_header['size']/512);
+ for ($i=0; $i<$n; $i++) {
+ $v_content = $this->_readBlock();
+ $v_filename .= $v_content;
+ }
+ if (($v_header['size'] % 512) != 0) {
+ $v_content = $this->_readBlock();
+ $v_filename .= trim($v_content);
+ }
+
+ // ----- Read the next header
+ $v_binary_data = $this->_readBlock();
+
+ if (!$this->_readHeader($v_binary_data, $v_header))
+ return false;
+
+ $v_filename = trim($v_filename);
+ $v_header['filename'] = $v_filename;
+ if ($this->_maliciousFilename($v_filename)) {
+ $this->_error('Malicious .tar detected, file "' . $v_filename .
+ '" will not install in desired directory tree');
+ return false;
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _extractInString()
+ /**
+ * This method extract from the archive one file identified by $p_filename.
+ * The return value is a string with the file content, or NULL on error.
+ * @param string $p_filename The path of the file to extract in a string.
+ * @return a string with the file content or NULL.
+ * @access private
+ */
+ function _extractInString($p_filename)
+ {
+ $v_result_str = "";
+
+ While (strlen($v_binary_data = $this->_readBlock()) != 0)
+ {
+ if (!$this->_readHeader($v_binary_data, $v_header))
+ return NULL;
+
+ if ($v_header['filename'] == '')
+ continue;
+
+ // ----- Look for long filename
+ if ($v_header['typeflag'] == 'L') {
+ if (!$this->_readLongHeader($v_header))
+ return NULL;
+ }
+
+ if ($v_header['filename'] == $p_filename) {
+ if ($v_header['typeflag'] == "5") {
+ $this->_error('Unable to extract in string a directory '
+ .'entry {'.$v_header['filename'].'}');
+ return NULL;
+ } else {
+ $n = floor($v_header['size']/512);
+ for ($i=0; $i<$n; $i++) {
+ $v_result_str .= $this->_readBlock();
+ }
+ if (($v_header['size'] % 512) != 0) {
+ $v_content = $this->_readBlock();
+ $v_result_str .= substr($v_content, 0,
+ ($v_header['size'] % 512));
+ }
+ return $v_result_str;
+ }
+ } else {
+ $this->_jumpBlock(ceil(($v_header['size']/512)));
+ }
+ }
+
+ return NULL;
+ }
+ // }}}
+
+ // {{{ _extractList()
+ function _extractList($p_path, &$p_list_detail, $p_mode,
+ $p_file_list, $p_remove_path)
+ {
+ $v_result=true;
+ $v_nb = 0;
+ $v_extract_all = true;
+ $v_listing = false;
+
+ $p_path = $this->_translateWinPath($p_path, false);
+ if ($p_path == '' || (substr($p_path, 0, 1) != '/'
+ && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) {
+ $p_path = "./".$p_path;
+ }
+ $p_remove_path = $this->_translateWinPath($p_remove_path);
+
+ // ----- Look for path to remove format (should end by /)
+ if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/'))
+ $p_remove_path .= '/';
+ $p_remove_path_size = strlen($p_remove_path);
+
+ switch ($p_mode) {
+ case "complete" :
+ $v_extract_all = TRUE;
+ $v_listing = FALSE;
+ break;
+ case "partial" :
+ $v_extract_all = FALSE;
+ $v_listing = FALSE;
+ break;
+ case "list" :
+ $v_extract_all = FALSE;
+ $v_listing = TRUE;
+ break;
+ default :
+ $this->_error('Invalid extract mode ('.$p_mode.')');
+ return false;
+ }
+
+ clearstatcache();
+
+ while (strlen($v_binary_data = $this->_readBlock()) != 0)
+ {
+ $v_extract_file = FALSE;
+ $v_extraction_stopped = 0;
+
+ if (!$this->_readHeader($v_binary_data, $v_header))
+ return false;
+
+ if ($v_header['filename'] == '') {
+ continue;
+ }
+
+ // ----- Look for long filename
+ if ($v_header['typeflag'] == 'L') {
+ if (!$this->_readLongHeader($v_header))
+ return false;
+ }
+
+ if ((!$v_extract_all) && (is_array($p_file_list))) {
+ // ----- By default no unzip if the file is not found
+ $v_extract_file = false;
+
+ for ($i=0; $i<sizeof($p_file_list); $i++) {
+ // ----- Look if it is a directory
+ if (substr($p_file_list[$i], -1) == '/') {
+ // ----- Look if the directory is in the filename path
+ if ((strlen($v_header['filename']) > strlen($p_file_list[$i]))
+ && (substr($v_header['filename'], 0, strlen($p_file_list[$i]))
+ == $p_file_list[$i])) {
+ $v_extract_file = TRUE;
+ break;
+ }
+ }
+
+ // ----- It is a file, so compare the file names
+ elseif ($p_file_list[$i] == $v_header['filename']) {
+ $v_extract_file = TRUE;
+ break;
+ }
+ }
+ } else {
+ $v_extract_file = TRUE;
+ }
+
+ // ----- Look if this file need to be extracted
+ if (($v_extract_file) && (!$v_listing))
+ {
+ if (($p_remove_path != '')
+ && (substr($v_header['filename'], 0, $p_remove_path_size)
+ == $p_remove_path))
+ $v_header['filename'] = substr($v_header['filename'],
+ $p_remove_path_size);
+ if (($p_path != './') && ($p_path != '/')) {
+ while (substr($p_path, -1) == '/')
+ $p_path = substr($p_path, 0, strlen($p_path)-1);
+
+ if (substr($v_header['filename'], 0, 1) == '/')
+ $v_header['filename'] = $p_path.$v_header['filename'];
+ else
+ $v_header['filename'] = $p_path.'/'.$v_header['filename'];
+ }
+ if (file_exists($v_header['filename'])) {
+ if ( (@is_dir($v_header['filename']))
+ && ($v_header['typeflag'] == '')) {
+ $this->_error('File '.$v_header['filename']
+ .' already exists as a directory');
+ return false;
+ }
+ if ( ($this->_isArchive($v_header['filename']))
+ && ($v_header['typeflag'] == "5")) {
+ $this->_error('Directory '.$v_header['filename']
+ .' already exists as a file');
+ return false;
+ }
+ if (!is_writeable($v_header['filename'])) {
+ $this->_error('File '.$v_header['filename']
+ .' already exists and is write protected');
+ return false;
+ }
+ if (filemtime($v_header['filename']) > $v_header['mtime']) {
+ // To be completed : An error or silent no replace ?
+ }
+ }
+
+ // ----- Check the directory availability and create it if necessary
+ elseif (($v_result
+ = $this->_dirCheck(($v_header['typeflag'] == "5"
+ ?$v_header['filename']
+ :dirname($v_header['filename'])))) != 1) {
+ $this->_error('Unable to create path for '.$v_header['filename']);
+ return false;
+ }
+
+ if ($v_extract_file) {
+ if ($v_header['typeflag'] == "5") {
+ if (!@file_exists($v_header['filename'])) {
+ if (!@mkdir($v_header['filename'], 0777)) {
+ $this->_error('Unable to create directory {'
+ .$v_header['filename'].'}');
+ return false;
+ }
+ }
+ } elseif ($v_header['typeflag'] == "2") {
+ if (@file_exists($v_header['filename'])) {
+ @unlink($v_header['filename']);
+ }
+ if (!@symlink($v_header['link'], $v_header['filename'])) {
+ $this->_error('Unable to extract symbolic link {'
+ .$v_header['filename'].'}');
+ return false;
+ }
+ } else {
+ if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
+ $this->_error('Error while opening {'.$v_header['filename']
+ .'} in write binary mode');
+ return false;
+ } else {
+ $n = floor($v_header['size']/512);
+ for ($i=0; $i<$n; $i++) {
+ $v_content = $this->_readBlock();
+ fwrite($v_dest_file, $v_content, 512);
+ }
+ if (($v_header['size'] % 512) != 0) {
+ $v_content = $this->_readBlock();
+ fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
+ }
+
+ @fclose($v_dest_file);
+
+ // ----- Change the file mode, mtime
+ @touch($v_header['filename'], $v_header['mtime']);
+ if ($v_header['mode'] & 0111) {
+ // make file executable, obey umask
+ $mode = fileperms($v_header['filename']) | (~umask() & 0111);
+ @chmod($v_header['filename'], $mode);
+ }
+ }
+
+ // ----- Check the file size
+ clearstatcache();
+ if (filesize($v_header['filename']) != $v_header['size']) {
+ $this->_error('Extracted file '.$v_header['filename']
+ .' does not have the correct file size \''
+ .filesize($v_header['filename'])
+ .'\' ('.$v_header['size']
+ .' expected). Archive may be corrupted.');
+ return false;
+ }
+ }
+ } else {
+ $this->_jumpBlock(ceil(($v_header['size']/512)));
+ }
+ } else {
+ $this->_jumpBlock(ceil(($v_header['size']/512)));
+ }
+
+ /* TBC : Seems to be unused ...
+ if ($this->_compress)
+ $v_end_of_file = @gzeof($this->_file);
+ else
+ $v_end_of_file = @feof($this->_file);
+ */
+
+ if ($v_listing || $v_extract_file || $v_extraction_stopped) {
+ // ----- Log extracted files
+ if (($v_file_dir = dirname($v_header['filename']))
+ == $v_header['filename'])
+ $v_file_dir = '';
+ if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == ''))
+ $v_file_dir = '/';
+
+ $p_list_detail[$v_nb++] = $v_header;
+ if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
+ return true;
+ }
+ }
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _openAppend()
+ function _openAppend()
+ {
+ if (filesize($this->_tarname) == 0)
+ return $this->_openWrite();
+
+ if ($this->_compress) {
+ $this->_close();
+
+ if (!@rename($this->_tarname, $this->_tarname.".tmp")) {
+ $this->_error('Error while renaming \''.$this->_tarname
+ .'\' to temporary file \''.$this->_tarname
+ .'.tmp\'');
+ return false;
+ }
+
+ if ($this->_compress_type == 'gz')
+ $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb");
+ elseif ($this->_compress_type == 'bz2')
+ $v_temp_tar = @bzopen($this->_tarname.".tmp", "r");
+
+ if ($v_temp_tar == 0) {
+ $this->_error('Unable to open file \''.$this->_tarname
+ .'.tmp\' in binary read mode');
+ @rename($this->_tarname.".tmp", $this->_tarname);
+ return false;
+ }
+
+ if (!$this->_openWrite()) {
+ @rename($this->_tarname.".tmp", $this->_tarname);
+ return false;
+ }
+
+ if ($this->_compress_type == 'gz') {
+ while (!@gzeof($v_temp_tar)) {
+ $v_buffer = @gzread($v_temp_tar, 512);
+ if ($v_buffer == ARCHIVE_TAR_END_BLOCK) {
+ // do not copy end blocks, we will re-make them
+ // after appending
+ continue;
+ }
+ $v_binary_data = pack("a512", $v_buffer);
+ $this->_writeBlock($v_binary_data);
+ }
+
+ @gzclose($v_temp_tar);
+ }
+ elseif ($this->_compress_type == 'bz2') {
+ while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) {
+ if ($v_buffer == ARCHIVE_TAR_END_BLOCK) {
+ continue;
+ }
+ $v_binary_data = pack("a512", $v_buffer);
+ $this->_writeBlock($v_binary_data);
+ }
+
+ @bzclose($v_temp_tar);
+ }
+
+ if (!@unlink($this->_tarname.".tmp")) {
+ $this->_error('Error while deleting temporary file \''
+ .$this->_tarname.'.tmp\'');
+ }
+
+ } else {
+ // ----- For not compressed tar, just add files before the last
+ // one or two 512 bytes block
+ if (!$this->_openReadWrite())
+ return false;
+
+ clearstatcache();
+ $v_size = filesize($this->_tarname);
+
+ // We might have zero, one or two end blocks.
+ // The standard is two, but we should try to handle
+ // other cases.
+ fseek($this->_file, $v_size - 1024);
+ if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
+ fseek($this->_file, $v_size - 1024);
+ }
+ elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
+ fseek($this->_file, $v_size - 512);
+ }
+ }
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _append()
+ function _append($p_filelist, $p_add_dir='', $p_remove_dir='')
+ {
+ if (!$this->_openAppend())
+ return false;
+
+ if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir))
+ $this->_writeFooter();
+
+ $this->_close();
+
+ return true;
+ }
+ // }}}
+
+ // {{{ _dirCheck()
+
+ /**
+ * Check if a directory exists and create it (including parent
+ * dirs) if not.
+ *
+ * @param string $p_dir directory to check
+ *
+ * @return bool TRUE if the directory exists or was created
+ */
+ function _dirCheck($p_dir)
+ {
+ clearstatcache();
+ if ((@is_dir($p_dir)) || ($p_dir == ''))
+ return true;
+
+ $p_parent_dir = dirname($p_dir);
+
+ if (($p_parent_dir != $p_dir) &&
+ ($p_parent_dir != '') &&
+ (!$this->_dirCheck($p_parent_dir)))
+ return false;
+
+ if (!@mkdir($p_dir, 0777)) {
+ $this->_error("Unable to create directory '$p_dir'");
+ return false;
+ }
+
+ return true;
+ }
+
+ // }}}
+
+ // {{{ _pathReduction()
+
+ /**
+ * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
+ * rand emove double slashes.
+ *
+ * @param string $p_dir path to reduce
+ *
+ * @return string reduced path
+ *
+ * @access private
+ *
+ */
+ function _pathReduction($p_dir)
+ {
+ $v_result = '';
+
+ // ----- Look for not empty path
+ if ($p_dir != '') {
+ // ----- Explode path by directory names
+ $v_list = explode('/', $p_dir);
+
+ // ----- Study directories from last to first
+ for ($i=sizeof($v_list)-1; $i>=0; $i--) {
+ // ----- Look for current path
+ if ($v_list[$i] == ".") {
+ // ----- Ignore this directory
+ // Should be the first $i=0, but no check is done
+ }
+ else if ($v_list[$i] == "..") {
+ // ----- Ignore it and ignore the $i-1
+ $i--;
+ }
+ else if ( ($v_list[$i] == '')
+ && ($i!=(sizeof($v_list)-1))
+ && ($i!=0)) {
+ // ----- Ignore only the double '//' in path,
+ // but not the first and last /
+ } else {
+ $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/'
+ .$v_result:'');
+ }
+ }
+ }
+ $v_result = strtr($v_result, '\\', '/');
+ return $v_result;
+ }
+
+ // }}}
+
+ // {{{ _translateWinPath()
+ function _translateWinPath($p_path, $p_remove_disk_letter=true)
+ {
+ if (defined('OS_WINDOWS') && OS_WINDOWS) {
+ // ----- Look for potential disk letter
+ if ( ($p_remove_disk_letter)
+ && (($v_position = strpos($p_path, ':')) != false)) {
+ $p_path = substr($p_path, $v_position+1);
+ }
+ // ----- Change potential windows directory separator
+ if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) {
+ $p_path = strtr($p_path, '\\', '/');
+ }
+ }
+ return $p_path;
+ }
+ // }}}
+
+}
+?>
diff --git a/includes/pear/Auth.php b/includes/pear/Auth.php
new file mode 100644
index 0000000..f34b8f6
--- /dev/null
+++ b/includes/pear/Auth.php
@@ -0,0 +1,1330 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * The main include file for Auth package
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Martin Jansen <mj@php.net>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/Auth
+ */
+
+/**
+ * Returned if session exceeds idle time
+ */
+define('AUTH_IDLED', -1);
+/**
+ * Returned if session has expired
+ */
+define('AUTH_EXPIRED', -2);
+/**
+ * Returned if container is unable to authenticate user/password pair
+ */
+define('AUTH_WRONG_LOGIN', -3);
+/**
+ * Returned if a container method is not supported.
+ */
+define('AUTH_METHOD_NOT_SUPPORTED', -4);
+/**
+ * Returned if new Advanced security system detects a breach
+ */
+define('AUTH_SECURITY_BREACH', -5);
+/**
+ * Returned if checkAuthCallback says session should not continue.
+ */
+define('AUTH_CALLBACK_ABORT', -6);
+
+/**
+ * Auth Log level - INFO
+ */
+define('AUTH_LOG_INFO', 6);
+/**
+ * Auth Log level - DEBUG
+ */
+define('AUTH_LOG_DEBUG', 7);
+
+/**
+ * Auth Advanced Security - IP Checks
+ */
+define('AUTH_ADV_IPCHECK', 1);
+/**
+ * Auth Advanced Security - User Agent Checks
+ */
+define('AUTH_ADV_USERAGENT', 2);
+/**
+ * Auth Advanced Security - Challenge Response
+ */
+define('AUTH_ADV_CHALLENGE', 3);
+
+
+/**
+ * PEAR::Auth
+ *
+ * The PEAR::Auth class provides methods for creating an
+ * authentication system using PHP.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Martin Jansen <mj@php.net>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision$
+ * @link http://pear.php.net/package/Auth
+ */
+class Auth {
+
+ // {{{ properties
+
+ /**
+ * Auth lifetime in seconds
+ *
+ * If this variable is set to 0, auth never expires
+ *
+ * @var integer
+ * @see setExpire(), checkAuth()
+ */
+ private $expire = 0;
+
+ /**
+ * Has the auth session expired?
+ *
+ * @var bool
+ * @see checkAuth()
+ */
+ private $expired = false;
+
+ /**
+ * Maximum idletime in seconds
+ *
+ * The difference to $expire is, that the idletime gets
+ * refreshed each time checkAuth() is called. If this
+ * variable is set to 0, idletime is never checked.
+ *
+ * @var integer
+ * @see setIdle(), checkAuth()
+ */
+ private $idle = 0;
+
+ /**
+ * Is the maximum idletime over?
+ *
+ * @var boolean
+ * @see checkAuth()
+ */
+ private $idled = false;
+
+ /**
+ * Storage object
+ *
+ * @var object
+ * @see Auth(), validateLogin()
+ */
+ private $storage = '';
+
+ /**
+ * User-defined function that creates the login screen
+ *
+ * @var string
+ */
+ private $loginFunction = '';
+
+ /**
+ * Should the login form be displayed
+ *
+ * @var bool
+ * @see setShowlogin()
+ */
+ private $showLogin = true;
+
+ /**
+ * Is Login Allowed from this page
+ *
+ * @var bool
+ * @see setAllowLogin
+ */
+ private $allowLogin = true;
+
+ /**
+ * Current authentication status
+ *
+ * @var string
+ */
+ private $status = '';
+
+ /**
+ * Username
+ *
+ * @var string
+ */
+ private $username = '';
+
+ /**
+ * Password
+ *
+ * @var string
+ */
+ private $password = '';
+
+ /**
+ * checkAuth callback function name
+ *
+ * @var string
+ * @see setCheckAuthCallback()
+ */
+ private $checkAuthCallback = '';
+
+ /**
+ * Login callback function name
+ *
+ * @var string
+ * @see setLoginCallback()
+ */
+ private $loginCallback = '';
+
+ /**
+ * Failed Login callback function name
+ *
+ * @var string
+ * @see setFailedLoginCallback()
+ */
+ private $loginFailedCallback = '';
+
+ /**
+ * Logout callback function name
+ *
+ * @var string
+ * @see setLogoutCallback()
+ */
+ private $logoutCallback = '';
+
+ /**
+ * Auth session-array name
+ *
+ * @var string
+ */
+ private $_sessionName = '_authsession';
+
+ /**
+ * Package Version
+ *
+ * @var string
+ */
+ private $version = "@version@";
+
+ /**
+ * Flag to use advanced security
+ * When set extra checks will be made to see if the
+ * user's IP or useragent have changed across requests.
+ * Turned off by default to preserve BC.
+ *
+ * @var mixed Boolean to turn all advanced security options on or off
+ * Array containing named values turning specific advanced
+ * security features on or off individually
+ * array(
+ * AUTH_ADV_IPCHECK => true,
+ * AUTH_ADV_USERAGENT => true,
+ * AUTH_ADV_CHALLENGE => true,
+ * );
+ */
+ private $advancedsecurity = false;
+
+ /**
+ * Username key in POST array
+ *
+ * @var string
+ */
+ private $_postUsername = 'username';
+
+ /**
+ * Password key in POST array
+ *
+ * @var string
+ */
+ private $_postPassword = 'password';
+
+ /**
+ * Holds a reference to the session auth variable
+ * @var array
+ */
+ private $session;
+
+ /**
+ * Holds a reference to the global server variable
+ * @var array
+ */
+ private $server;
+
+ /**
+ * Holds a reference to the global post variable
+ * @var array
+ */
+ private $post;
+
+ /**
+ * Holds a reference to the global cookie variable
+ * @var array
+ */
+ private $cookie;
+
+ /**
+ * A hash to hold various superglobals as reference
+ * @var array
+ */
+ private $authdata;
+
+ /**
+ * How many times has checkAuth been called
+ * @var int
+ */
+ private $authChecks = 0;
+
+ /**
+ * PEAR::Log object
+ *
+ * @var object Log
+ */
+ private $logger = null;
+
+ /**
+ * Whether to enable logging of behaviour
+ *
+ * @var boolean
+ */
+ private $enableLogging = false;
+
+ /**
+ * Whether to regenerate session id everytime start is called
+ *
+ * @var boolean
+ */
+ private $regenerateSessionId = false;
+
+ // }}}
+ // {{{ Auth() [constructor]
+
+ /**
+ * Constructor
+ *
+ * Set up the storage driver.
+ *
+ * @param string Type of the storage driver
+ * @param mixed Additional options for the storage driver
+ * (example: if you are using DB as the storage
+ * driver, you have to pass the dsn string here)
+ *
+ * @param string Name of the function that creates the login form
+ * @param boolean Should the login form be displayed if necessary?
+ * @return void
+ */
+ function Auth($storageDriver, $options = '', $loginFunction = '', $showLogin = true)
+ {
+ $this->applyAuthOptions($options);
+
+ // Start the session suppress error if already started
+ if(!session_id()){
+ @session_start();
+ if(!session_id()) {
+ // Throw error
+ include_once 'PEAR.php';
+ PEAR::throwError('Session could not be started by Auth, '
+ .'possibly headers are already sent, try putting '
+ .'ob_start in the beginning of your script');
+ }
+ }
+
+ // Make Sure Auth session variable is there
+ if(!isset($_SESSION[$this->_sessionName])) {
+ $_SESSION[$this->_sessionName] = array();
+ }
+
+ // Assign Some globals to internal references, this will replace _importGlobalVariable
+ $this->session =& $_SESSION[$this->_sessionName];
+ $this->server =& $_SERVER;
+ $this->post =& $_POST;
+ $this->cookie =& $_COOKIE;
+
+ if ($loginFunction != '' && is_callable($loginFunction)) {
+ $this->loginFunction = $loginFunction;
+ }
+
+ if (is_bool($showLogin)) {
+ $this->showLogin = $showLogin;
+ }
+
+ if (is_object($storageDriver)) {
+ $this->storage =& $storageDriver;
+ // Pass a reference to auth to the container, ugly but works
+ // this is used by the DB container to use method setAuthData not staticaly.
+ $this->storage->_auth_obj =& $this;
+ } else {
+ // $this->storage = $this->_factory($storageDriver, $options);
+ //
+ $this->storage_driver = $storageDriver;
+ $this->storage_options =& $options;
+ }
+ }
+
+ // }}}
+ // {{{ applyAuthOptions()
+
+ /**
+ * Set the Auth options
+ *
+ * Some options which are Auth specific will be applied
+ * the rest will be left for usage by the container
+ *
+ * @param array An array of Auth options
+ * @return array The options which were not applied
+ */
+ private function &applyAuthOptions(&$options)
+ {
+ if(is_array($options)){
+ if (!empty($options['sessionName'])) {
+ $this->_sessionName = $options['sessionName'];
+ unset($options['sessionName']);
+ }
+ if (isset($options['allowLogin'])) {
+ $this->allowLogin = $options['allowLogin'];
+ unset($options['allowLogin']);
+ }
+ if (!empty($options['postUsername'])) {
+ $this->_postUsername = $options['postUsername'];
+ unset($options['postUsername']);
+ }
+ if (!empty($options['postPassword'])) {
+ $this->_postPassword = $options['postPassword'];
+ unset($options['postPassword']);
+ }
+ if (isset($options['advancedsecurity'])) {
+ $this->advancedsecurity = $options['advancedsecurity'];
+ unset($options['advancedsecurity']);
+ }
+ if (isset($options['enableLogging'])) {
+ $this->enableLogging = $options['enableLogging'];
+ unset($options['enableLogging']);
+ }
+ if (isset($options['regenerateSessionId']) && is_bool($options['regenerateSessionId'])) {
+ $this->regenerateSessionId = $options['regenerateSessionId'];
+ }
+ }
+ return($options);
+ }
+
+ // }}}
+ // {{{ _loadStorage()
+
+ /**
+ * Load Storage Driver if not already loaded
+ *
+ * Suspend storage instantiation to make Auth lighter to use
+ * for calls which do not require login
+ *
+ * @return bool True if the conainer is loaded, false if the container
+ * is already loaded
+ */
+ private function _loadStorage()
+ {
+ if(!is_object($this->storage)) {
+ $this->storage =& $this->_factory($this->storage_driver,
+ $this->storage_options);
+ $this->storage->_auth_obj =& $this;
+ $this->log('Loaded storage container ('.$this->storage_driver.')', AUTH_LOG_DEBUG);
+ return(true);
+ }
+ return(false);
+ }
+
+ // }}}
+ // {{{ _factory()
+
+ /**
+ * Return a storage driver based on $driver and $options
+ *
+ * @static
+ * @param string $driver Type of storage class to return
+ * @param string $options Optional parameters for the storage class
+ * @return object Object Storage object
+ */
+ private function &_factory($driver, $options = '')
+ {
+ $storage_class = 'Auth_Container_' . $driver;
+ include_once 'Auth/Container/' . $driver . '.php';
+ $obj = new $storage_class($options);
+ return $obj;
+ }
+
+ // }}}
+ // {{{ assignData()
+
+ /**
+ * Assign data from login form to internal values
+ *
+ * This function takes the values for username and password
+ * from $HTTP_POST_VARS/$_POST and assigns them to internal variables.
+ * If you wish to use another source apart from $HTTP_POST_VARS/$_POST,
+ * you have to derive this function.
+ *
+ * @global $HTTP_POST_VARS, $_POST
+ * @see Auth
+ * @return void
+ */
+ private function assignData()
+ {
+ $this->log('Auth::assignData() called.', AUTH_LOG_DEBUG);
+
+ if ( isset($this->post[$this->_postUsername])
+ && $this->post[$this->_postUsername] != '') {
+ $this->username = (get_magic_quotes_gpc() == 1
+ ? stripslashes($this->post[$this->_postUsername])
+ : $this->post[$this->_postUsername]);
+ }
+ if ( isset($this->post[$this->_postPassword])
+ && $this->post[$this->_postPassword] != '') {
+ $this->password = (get_magic_quotes_gpc() == 1
+ ? stripslashes($this->post[$this->_postPassword])
+ : $this->post[$this->_postPassword] );
+ }
+ }
+
+ // }}}
+ // {{{ start()
+
+ /**
+ * Start new auth session
+ *
+ * @return void
+ */
+ public function start()
+ {
+ $this->log('Auth::start() called.', AUTH_LOG_DEBUG);
+
+ // #10729 - Regenerate session id here if we are generating it on every
+ // page load.
+ if ($this->regenerateSessionId) {
+ session_regenerate_id(true);
+ }
+
+ $this->assignData();
+ if (!$this->checkAuth() && $this->allowLogin) {
+ $this->login();
+ }
+ }
+
+ // }}}
+ // {{{ login()
+
+ /**
+ * Login function
+ *
+ * @return void
+ */
+ private function login()
+ {
+ $this->log('Auth::login() called.', AUTH_LOG_DEBUG);
+
+ $login_ok = false;
+ $this->_loadStorage();
+
+ // Check if using challenge response
+ (isset($this->post['authsecret']) && $this->post['authsecret'] == 1)
+ ? $usingChap = true
+ : $usingChap = false;
+
+
+ // When the user has already entered a username, we have to validate it.
+ if (!empty($this->username)) {
+ if (true === $this->storage->fetchData($this->username, $this->password, $usingChap)) {
+ $this->session['challengekey'] = md5($this->username.$this->password);
+ $login_ok = true;
+ $this->log('Successful login.', AUTH_LOG_INFO);
+ }
+ }
+
+ if (!empty($this->username) && $login_ok) {
+ $this->setAuth($this->username);
+ if (is_callable($this->loginCallback)) {
+ $this->log('Calling loginCallback ('.$this->loginCallback.').', AUTH_LOG_DEBUG);
+ call_user_func_array($this->loginCallback, array($this->username, &$this));
+ }
+ }
+
+ // If the login failed or the user entered no username,
+ // output the login screen again.
+ if (!empty($this->username) && !$login_ok) {
+ $this->log('Incorrect login.', AUTH_LOG_INFO);
+ $this->status = AUTH_WRONG_LOGIN;
+ if (is_callable($this->loginFailedCallback)) {
+ $this->log('Calling loginFailedCallback ('.$this->loginFailedCallback.').', AUTH_LOG_DEBUG);
+ call_user_func_array($this->loginFailedCallback, array($this->username, &$this));
+ }
+ }
+
+ if ((empty($this->username) || !$login_ok) && $this->showLogin) {
+ $this->log('Rendering Login Form.', AUTH_LOG_INFO);
+ if (is_callable($this->loginFunction)) {
+ $this->log('Calling loginFunction ('.$this->loginFunction.').', AUTH_LOG_DEBUG);
+ call_user_func_array($this->loginFunction, array($this->username, $this->status, &$this));
+ } else {
+ // BC fix Auth used to use drawLogin for this
+ // call is sub classes implement this
+ if (is_callable(array($this, 'drawLogin'))) {
+ $this->log('Calling Auth::drawLogin()', AUTH_LOG_DEBUG);
+ return $this->drawLogin($this->username, $this);
+ }
+
+ $this->log('Using default Auth_Frontend_Html', AUTH_LOG_DEBUG);
+
+ // New Login form
+ include_once 'Auth/Frontend/Html.php';
+ return Auth_Frontend_Html::render($this, $this->username);
+ }
+ } else {
+ return;
+ }
+ }
+
+ // }}}
+ // {{{ setExpire()
+
+ /**
+ * Set the maximum expire time
+ *
+ * @param integer time in seconds
+ * @param bool add time to current expire time or not
+ * @return void
+ */
+ public function setExpire($time, $add = false)
+ {
+ $add ? $this->expire += $time : $this->expire = $time;
+ }
+
+ // }}}
+ // {{{ setIdle()
+
+ /**
+ * Set the maximum idle time
+ *
+ * @param integer time in seconds
+ * @param bool add time to current maximum idle time or not
+ * @return void
+ */
+ public function setIdle($time, $add = false)
+ {
+ $add ? $this->idle += $time : $this->idle = $time;
+ }
+
+ // }}}
+ // {{{ setSessionName()
+
+ /**
+ * Set name of the session to a customized value.
+ *
+ * If you are using multiple instances of PEAR::Auth
+ * on the same domain, you can change the name of
+ * session per application via this function.
+ * This will chnage the name of the session variable
+ * auth uses to store it's data in the session
+ *
+ * @param string New name for the session
+ * @return void
+ */
+ public function setSessionName($name = 'session')
+ {
+ $this->_sessionName = '_auth_'.$name;
+ // Make Sure Auth session variable is there
+ if(!isset($_SESSION[$this->_sessionName])) {
+ $_SESSION[$this->_sessionName] = array();
+ }
+ $this->session =& $_SESSION[$this->_sessionName];
+ }
+
+ // }}}
+ // {{{ setShowLogin()
+
+ /**
+ * Should the login form be displayed if necessary?
+ *
+ * @param bool show login form or not
+ * @return void
+ */
+ public function setShowLogin($showLogin = true)
+ {
+ $this->showLogin = $showLogin;
+ }
+
+ // }}}
+ // {{{ setAllowLogin()
+
+ /**
+ * Is Login Allowed from this page?
+ *
+ * @param bool allow login from this page or not
+ * @return void
+ */
+ public function setAllowLogin($allowLogin = true)
+ {
+ $this->allowLogin = $allowLogin;
+ }
+
+ // }}}
+ // {{{ setCheckAuthCallback()
+
+ /**
+ * Register a callback function to be called whenever the validity of the login is checked
+ * The function will receive two parameters, the username and a reference to the auth object.
+ *
+ * @param string callback function name
+ * @return void
+ * @since Method available since Release 1.4.3
+ */
+ public function setCheckAuthCallback($checkAuthCallback)
+ {
+ $this->checkAuthCallback = $checkAuthCallback;
+ }
+
+ // }}}
+ // {{{ setLoginCallback()
+
+ /**
+ * Register a callback function to be called on user login.
+ * The function will receive two parameters, the username and a reference to the auth object.
+ *
+ * @param string callback function name
+ * @return void
+ * @see setLogoutCallback()
+ */
+ public function setLoginCallback($loginCallback)
+ {
+ $this->loginCallback = $loginCallback;
+ }
+
+ // }}}
+ // {{{ setFailedLoginCallback()
+
+ /**
+ * Register a callback function to be called on failed user login.
+ * The function will receive two parameters, the username and a reference to the auth object.
+ *
+ * @param string callback function name
+ * @return void
+ */
+ public function setFailedLoginCallback($loginFailedCallback)
+ {
+ $this->loginFailedCallback = $loginFailedCallback;
+ }
+
+ // }}}
+ // {{{ setLogoutCallback()
+
+ /**
+ * Register a callback function to be called on user logout.
+ * The function will receive three parameters, the username and a reference to the auth object.
+ *
+ * @param string callback function name
+ * @return void
+ * @see setLoginCallback()
+ */
+ public function setLogoutCallback($logoutCallback)
+ {
+ $this->logoutCallback = $logoutCallback;
+ }
+
+ // }}}
+ // {{{ setAuthData()
+
+ /**
+ * Register additional information that is to be stored
+ * in the session.
+ *
+ * @param string Name of the data field
+ * @param mixed Value of the data field
+ * @param boolean Should existing data be overwritten? (default
+ * is true)
+ * @return void
+ */
+ public function setAuthData($name, $value, $overwrite = true)
+ {
+ if (!empty($this->session['data'][$name]) && $overwrite == false) {
+ return;
+ }
+ $this->session['data'][$name] = $value;
+ }
+
+ // }}}
+ // {{{ getAuthData()
+
+ /**
+ * Get additional information that is stored in the session.
+ *
+ * If no value for the first parameter is passed, the method will
+ * return all data that is currently stored.
+ *
+ * @param string Name of the data field
+ * @return mixed Value of the data field.
+ */
+ public function getAuthData($name = null)
+ {
+ if (!isset($this->session['data'])) {
+ return null;
+ }
+ if(!isset($name)) {
+ return $this->session['data'];
+ }
+ if (isset($name) && isset($this->session['data'][$name])) {
+ return $this->session['data'][$name];
+ }
+ return null;
+ }
+
+ // }}}
+ // {{{ setAuth()
+
+ /**
+ * Register variable in a session telling that the user
+ * has logged in successfully
+ *
+ * @param string Username
+ * @return void
+ */
+ public function setAuth($username)
+ {
+ $this->log('Auth::setAuth() called.', AUTH_LOG_DEBUG);
+
+ // #10729 - Regenerate session id here only if generating at login only
+ // Don't do it if we are regenerating on every request so we don't
+ // regenerate it twice in one request.
+ if (!$this->regenerateSessionId) {
+ // #2021 - Change the session id to avoid session fixation attacks php 4.3.3 >
+ session_regenerate_id(true);
+ }
+
+ if (!isset($this->session) || !is_array($this->session)) {
+ $this->session = array();
+ }
+
+ if (!isset($this->session['data'])) {
+ $this->session['data'] = array();
+ }
+
+ $this->session['sessionip'] = isset($this->server['REMOTE_ADDR'])
+ ? $this->server['REMOTE_ADDR']
+ : '';
+ $this->session['sessionuseragent'] = isset($this->server['HTTP_USER_AGENT'])
+ ? $this->server['HTTP_USER_AGENT']
+ : '';
+ $this->session['sessionforwardedfor'] = isset($this->server['HTTP_X_FORWARDED_FOR'])
+ ? $this->server['HTTP_X_FORWARDED_FOR']
+ : '';
+
+ // This should be set by the container to something more safe
+ // Like md5(passwd.microtime)
+ if(empty($this->session['challengekey'])) {
+ $this->session['challengekey'] = md5($username.microtime());
+ }
+
+ $this->session['challengecookie'] = md5($this->session['challengekey'].microtime());
+ setcookie('authchallenge', $this->session['challengecookie'], 0, '/');
+
+ $this->session['registered'] = true;
+ $this->session['username'] = $username;
+ $this->session['timestamp'] = time();
+ $this->session['idle'] = time();
+ }
+
+ // }}}
+ // {{{ setAdvancedSecurity()
+
+ /**
+ * Enables advanced security checks
+ *
+ * Currently only ip change and useragent change
+ * are detected
+ * @todo Add challenge cookies - Create a cookie which changes every time
+ * and contains some challenge key which the server can verify with
+ * a session var cookie might need to be crypted (user pass)
+ * @param bool Enable or disable
+ * @return void
+ */
+ public function setAdvancedSecurity($flag=true)
+ {
+ $this->advancedsecurity = $flag;
+ }
+
+ // }}}
+ // {{{ checkAuth()
+
+ /**
+ * Checks if there is a session with valid auth information.
+ *
+ * @return boolean Whether or not the user is authenticated.
+ */
+ public function checkAuth()
+ {
+ $this->log('Auth::checkAuth() called.', AUTH_LOG_DEBUG);
+ $this->authChecks++;
+ if (isset($this->session)) {
+ // Check if authentication session is expired
+ if ( $this->expire > 0
+ && isset($this->session['timestamp'])
+ && ($this->session['timestamp'] + $this->expire) < time()) {
+ $this->log('Session Expired', AUTH_LOG_INFO);
+ $this->expired = true;
+ $this->status = AUTH_EXPIRED;
+ $this->logout();
+ return false;
+ }
+
+ // Check if maximum idle time is reached
+ if ( $this->idle > 0
+ && isset($this->session['idle'])
+ && ($this->session['idle'] + $this->idle) < time()) {
+ $this->log('Session Idle Time Reached', AUTH_LOG_INFO);
+ $this->idled = true;
+ $this->status = AUTH_IDLED;
+ $this->logout();
+ return false;
+ }
+
+ if ( isset($this->session['registered'])
+ && isset($this->session['username'])
+ && $this->session['registered'] == true
+ && $this->session['username'] != '') {
+ Auth::updateIdle();
+
+ if ($this->_isAdvancedSecurityEnabled()) {
+ $this->log('Advanced Security Mode Enabled.', AUTH_LOG_DEBUG);
+
+ // Only Generate the challenge once
+ if ( $this->authChecks == 1
+ && $this->_isAdvancedSecurityEnabled(AUTH_ADV_CHALLENGE)) {
+ $this->log('Generating new Challenge Cookie.', AUTH_LOG_DEBUG);
+ $this->session['challengecookieold'] = $this->session['challengecookie'];
+ $this->session['challengecookie'] = md5($this->session['challengekey'].microtime());
+ setcookie('authchallenge', $this->session['challengecookie'], 0, '/');
+ }
+
+ // Check for ip change
+ if ( $this->_isAdvancedSecurityEnabled(AUTH_ADV_IPCHECK)
+ && isset($this->server['REMOTE_ADDR'])
+ && $this->session['sessionip'] != $this->server['REMOTE_ADDR']) {
+ $this->log('Security Breach. Remote IP Address changed.', AUTH_LOG_INFO);
+ // Check if the IP of the user has changed, if so we
+ // assume a man in the middle attack and log him out
+ $this->expired = true;
+ $this->status = AUTH_SECURITY_BREACH;
+ $this->logout();
+ return false;
+ }
+
+ // Check for ip change (if connected via proxy)
+ if ( $this->_isAdvancedSecurityEnabled(AUTH_ADV_IPCHECK)
+ && isset($this->server['HTTP_X_FORWARDED_FOR'])
+ && $this->session['sessionforwardedfor'] != $this->server['HTTP_X_FORWARDED_FOR']) {
+ $this->log('Security Breach. Forwarded For IP Address changed.', AUTH_LOG_INFO);
+ // Check if the IP of the user connecting via proxy has
+ // changed, if so we assume a man in the middle attack
+ // and log him out.
+ $this->expired = true;
+ $this->status = AUTH_SECURITY_BREACH;
+ $this->logout();
+ return false;
+ }
+
+ // Check for useragent change
+ if ( $this->_isAdvancedSecurityEnabled(AUTH_ADV_USERAGENT)
+ && isset($this->server['HTTP_USER_AGENT'])
+ && $this->session['sessionuseragent'] != $this->server['HTTP_USER_AGENT']) {
+ $this->log('Security Breach. User Agent changed.', AUTH_LOG_INFO);
+ // Check if the User-Agent of the user has changed, if
+ // so we assume a man in the middle attack and log him out
+ $this->expired = true;
+ $this->status = AUTH_SECURITY_BREACH;
+ $this->logout();
+ return false;
+ }
+
+ // Check challenge cookie here, if challengecookieold is not set
+ // this is the first time and check is skipped
+ // TODO when user open two pages similtaneuly (open in new window,open
+ // in tab) auth breach is caused find out a way around that if possible
+ if ( $this->_isAdvancedSecurityEnabled(AUTH_ADV_CHALLENGE)
+ && isset($this->session['challengecookieold'])
+ && $this->session['challengecookieold'] != $this->cookie['authchallenge']) {
+ $this->log('Security Breach. Challenge Cookie mismatch.', AUTH_LOG_INFO);
+ $this->expired = true;
+ $this->status = AUTH_SECURITY_BREACH;
+ $this->logout();
+ $this->login();
+ return false;
+ }
+ }
+
+ if (is_callable($this->checkAuthCallback)) {
+ $this->log('Calling checkAuthCallback ('.$this->checkAuthCallback.').', AUTH_LOG_DEBUG);
+ $checkCallback = call_user_func_array($this->checkAuthCallback, array($this->username, &$this));
+ if ($checkCallback == false) {
+ $this->log('checkAuthCallback failed.', AUTH_LOG_INFO);
+ $this->expired = true;
+ $this->status = AUTH_CALLBACK_ABORT;
+ $this->logout();
+ return false;
+ }
+ }
+
+ $this->log('Session OK.', AUTH_LOG_INFO);
+ return true;
+ }
+ } else {
+ $this->log('Unable to locate session storage.', AUTH_LOG_DEBUG);
+ return false;
+ }
+ $this->log('No login session.', AUTH_LOG_DEBUG);
+ return false;
+ }
+
+ // }}}
+ // {{{ staticCheckAuth() [static]
+
+ /**
+ * Statically checks if there is a session with valid auth information.
+ *
+ * @access public
+ * @see checkAuth
+ * @return boolean Whether or not the user is authenticated.
+ */
+ public static function staticCheckAuth($options = null)
+ {
+ static $staticAuth;
+ if(!isset($staticAuth)) {
+ $staticAuth = new Auth('null', $options);
+ }
+ $staticAuth->log('Auth::staticCheckAuth() called', AUTH_LOG_DEBUG);
+ return $staticAuth->checkAuth();
+ }
+
+ // }}}
+ // {{{ getAuth()
+
+ /**
+ * Has the user been authenticated?
+ *
+ * Is there a valid login session. Previously this was different from
+ * checkAuth() but now it is just an alias.
+ *
+ * @return bool True if the user is logged in, otherwise false.
+ */
+ public function getAuth()
+ {
+ $this->log('Auth::getAuth() called.', AUTH_LOG_DEBUG);
+ return $this->checkAuth();
+ }
+
+ // }}}
+ // {{{ logout()
+
+ /**
+ * Logout function
+ *
+ * This function clears any auth tokens in the currently
+ * active session and executes the logout callback function,
+ * if any
+ *
+ * @return void
+ */
+ public function logout()
+ {
+ $this->log('Auth::logout() called.', AUTH_LOG_DEBUG);
+
+ if (is_callable($this->logoutCallback) && isset($this->session['username'])) {
+ $this->log('Calling logoutCallback ('.$this->logoutCallback.').', AUTH_LOG_DEBUG);
+ call_user_func_array($this->logoutCallback, array($this->session['username'], &$this));
+ }
+
+ $this->username = '';
+ $this->password = '';
+
+ $this->session = null;
+ }
+
+ // }}}
+ // {{{ updateIdle()
+
+ /**
+ * Update the idletime
+ *
+ * @return void
+ */
+ public function updateIdle()
+ {
+ $this->session['idle'] = time();
+ }
+
+ // }}}
+ // {{{ getUsername()
+
+ /**
+ * Get the username
+ *
+ * @return string
+ */
+ public function getUsername()
+ {
+ if (isset($this->session['username'])) {
+ return($this->session['username']);
+ }
+ return('');
+ }
+
+ // }}}
+ // {{{ getStatus()
+
+ /**
+ * Get the current status
+ *
+ * @return string
+ */
+ public function getStatus()
+ {
+ return $this->status;
+ }
+
+ // }}}
+ // {{{ getPostUsernameField()
+
+ /**
+ * Gets the post varible used for the username
+ *
+ * @return string
+ */
+ public function getPostUsernameField()
+ {
+ return($this->_postUsername);
+ }
+
+ // }}}
+ // {{{ getPostPasswordField()
+
+ /**
+ * Gets the post varible used for the username
+ *
+ * @return string
+ */
+ public function getPostPasswordField()
+ {
+ return($this->_postPassword);
+ }
+
+ // }}}
+ // {{{ sessionValidThru()
+
+ /**
+ * Returns the time up to the session is valid
+ *
+ * @return integer
+ */
+ public function sessionValidThru()
+ {
+ if (!isset($this->session['idle'])) {
+ return 0;
+ }
+ if ($this->idle == 0) {
+ return 0;
+ }
+ return ($this->session['idle'] + $this->idle);
+ }
+
+ // }}}
+ // {{{ listUsers()
+
+ /**
+ * List all users that are currently available in the storage
+ * container
+ *
+ * @return array
+ */
+ public function listUsers()
+ {
+ $this->log('Auth::listUsers() called.', AUTH_LOG_DEBUG);
+ $this->_loadStorage();
+ return $this->storage->listUsers();
+ }
+
+ // }}}
+ // {{{ addUser()
+
+ /**
+ * Add user to the storage container
+ *
+ * @param string Username
+ * @param string Password
+ * @param mixed Additional parameters
+ * @return mixed True on success, PEAR error object on error
+ * and AUTH_METHOD_NOT_SUPPORTED otherwise.
+ */
+ public function addUser($username, $password, $additional = '')
+ {
+ $this->log('Auth::addUser() called.', AUTH_LOG_DEBUG);
+ $this->_loadStorage();
+ return $this->storage->addUser($username, $password, $additional);
+ }
+
+ // }}}
+ // {{{ removeUser()
+
+ /**
+ * Remove user from the storage container
+ *
+ * @param string Username
+ * @return mixed True on success, PEAR error object on error
+ * and AUTH_METHOD_NOT_SUPPORTED otherwise.
+ */
+ public function removeUser($username)
+ {
+ $this->log('Auth::removeUser() called.', AUTH_LOG_DEBUG);
+ $this->_loadStorage();
+ return $this->storage->removeUser($username);
+ }
+
+ // }}}
+ // {{{ changePassword()
+
+ /**
+ * Change password for user in the storage container
+ *
+ * @param string Username
+ * @param string The new password
+ * @return mixed True on success, PEAR error object on error
+ * and AUTH_METHOD_NOT_SUPPORTED otherwise.
+ */
+ public function changePassword($username, $password)
+ {
+ $this->log('Auth::changePassword() called', AUTH_LOG_DEBUG);
+ $this->_loadStorage();
+ return $this->storage->changePassword($username, $password);
+ }
+
+ // }}}
+ // {{{ log()
+
+ /**
+ * Log a message from the Auth system
+ *
+ * @param string The message to log
+ * @param string The log level to log the message under. See the Log documentation for more info.
+ * @return boolean
+ */
+ public function log($message, $level = AUTH_LOG_DEBUG)
+ {
+ if (!$this->enableLogging) return false;
+
+ $this->_loadLogger();
+
+ $this->logger->log('AUTH: '.$message, $level);
+ }
+
+ // }}}
+ // {{{ _loadLogger()
+
+ /**
+ * Load Log object if not already loaded
+ *
+ * Suspend logger instantiation to make Auth lighter to use
+ * for calls which do not require logging
+ *
+ * @return bool True if the logger is loaded, false if the logger
+ * is already loaded
+ */
+ private function _loadLogger()
+ {
+ if(is_null($this->logger)) {
+ if (!class_exists('Log')) {
+ include_once 'Log.php';
+ }
+ $this->logger =& Log::singleton('null',
+ null,
+ 'auth['.getmypid().']',
+ array(),
+ AUTH_LOG_DEBUG);
+ return(true);
+ }
+ return(false);
+ }
+
+ // }}}
+ // {{{ attachLogObserver()
+
+ /**
+ * Attach an Observer to the Auth Log Source
+ *
+ * @param object Log_Observer A Log Observer instance
+ * @return boolean
+ */
+ public function attachLogObserver(&$observer) {
+
+ $this->_loadLogger();
+
+ return $this->logger->attach($observer);
+
+ }
+
+ // }}}
+ // {{{ _isAdvancedSecurityEnabled()
+
+ /**
+ * Is advanced security enabled?
+ *
+ * Pass one of the Advanced Security constants as the first parameter
+ * to check if that advanced security check is enabled.
+ *
+ * @param integer
+ * @return boolean
+ */
+ private function _isAdvancedSecurityEnabled($feature = null) {
+
+ if (is_null($feature)) {
+
+ if ($this->advancedsecurity === true)
+ return true;
+
+ if ( is_array($this->advancedsecurity)
+ && in_array(true, $this->advancedsecurity, true))
+ return true;
+
+ return false;
+
+ } else {
+
+ if (is_array($this->advancedsecurity)) {
+
+ if ( isset($this->advancedsecurity[$feature])
+ && $this->advancedsecurity[$feature] == true)
+ return true;
+
+ return false;
+
+ }
+
+ return (bool)$this->advancedsecurity;
+
+ }
+
+ }
+
+ // }}}
+
+}
+?>
diff --git a/includes/pear/Auth/Anonymous.php b/includes/pear/Auth/Anonymous.php
new file mode 100644
index 0000000..ec29bd6
--- /dev/null
+++ b/includes/pear/Auth/Anonymous.php
@@ -0,0 +1,138 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Anonymous authentication support
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Yavor Shahpasov <yavo@netsmart.com.cy>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: Anonymous.php 289651 2009-10-15 04:39:07Z aashley $
+ * @link http://pear.php.net/package/Auth
+ * @since File available since Release 1.3.0
+ */
+
+/**
+ * Include Auth package
+ */
+require_once 'Auth.php';
+
+/**
+ * Anonymous Authentication
+ *
+ * This class provides anonymous authentication if username and password
+ * were not supplied
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Yavor Shahpasov <yavo@netsmart.com.cy>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 289651 $
+ * @link http://pear.php.net/package/Auth
+ * @since Class available since Release 1.3.0
+ */
+class Auth_Anonymous extends Auth
+{
+
+ // {{{ properties
+
+ /**
+ * Whether to allow anonymous authentication
+ *
+ * @var boolean
+ */
+ var $allow_anonymous = true;
+
+ /**
+ * Username to use for anonymous user
+ *
+ * @var string
+ */
+ var $anonymous_username = 'anonymous';
+
+ // }}}
+ // {{{ Auth_Anonymous() [constructor]
+
+ /**
+ * Pass all parameters to Parent Auth class
+ *
+ * Set up the storage driver.
+ *
+ * @param string Type of the storage driver
+ * @param mixed Additional options for the storage driver
+ * (example: if you are using DB as the storage
+ * driver, you have to pass the dsn string here)
+ *
+ * @param string Name of the function that creates the login form
+ * @param boolean Should the login form be displayed if necessary?
+ * @return void
+ * @see Auth::Auth()
+ */
+ function Auth_Anonymous($storageDriver, $options = '', $loginFunction = '', $showLogin = true) {
+ parent::Auth($storageDriver, $options, $loginFunction, $showLogin);
+ }
+
+ // }}}
+ // {{{ login()
+
+ /**
+ * Login function
+ *
+ * If no username & password is passed then login as the username
+ * provided in $this->anonymous_username else call standard login()
+ * function.
+ *
+ * @return void
+ * @access private
+ * @see Auth::login()
+ */
+ function login() {
+ if ( $this->allow_anonymous
+ && empty($this->username)
+ && empty($this->password) ) {
+ $this->setAuth($this->anonymous_username);
+ if (is_callable($this->loginCallback)) {
+ call_user_func_array($this->loginCallback, array($this->username, $this) );
+ }
+ } else {
+ // Call normal login system
+ parent::login();
+ }
+ }
+
+ // }}}
+ // {{{ forceLogin()
+
+ /**
+ * Force the user to login
+ *
+ * Calling this function forces the user to provide a real username and
+ * password before continuing.
+ *
+ * @return void
+ */
+ function forceLogin() {
+ $this->allow_anonymous = false;
+ if( !empty($this->session['username']) && $this->session['username'] == $this->anonymous_username ) {
+ $this->logout();
+ }
+ }
+
+ // }}}
+
+}
+
+?>
diff --git a/includes/pear/Auth/Auth.php b/includes/pear/Auth/Auth.php
new file mode 100644
index 0000000..61ffe5c
--- /dev/null
+++ b/includes/pear/Auth/Auth.php
@@ -0,0 +1,30 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Provide compatibility with previous Auth include location.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Martin Jansen <mj@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: Auth.php 208437 2006-03-02 06:53:08Z aashley $
+ * @link http://pear.php.net/package/Auth
+ * @deprecated File deprecated since Release 1.2.0
+ */
+
+/**
+ * Include Auth package
+ */
+require_once 'Auth.php';
+
+?>
diff --git a/includes/pear/Auth/Container.php b/includes/pear/Auth/Container.php
new file mode 100644
index 0000000..5724d7d
--- /dev/null
+++ b/includes/pear/Auth/Container.php
@@ -0,0 +1,262 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Auth_Container Base Class
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Martin Jansen <mj@php.net>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: Container.php 294935 2010-02-12 00:05:45Z clockwerx $
+ * @link http://pear.php.net/package/Auth
+ */
+
+/**
+ * Storage class for fetching login data
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Martin Jansen <mj@php.net>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 294935 $
+ * @link http://pear.php.net/package/Auth
+ */
+class Auth_Container
+{
+
+ // {{{ properties
+
+ /**
+ * User that is currently selected from the storage container.
+ *
+ * @access public
+ */
+ var $activeUser = "";
+
+ /**
+ * The Auth object this container is attached to.
+ *
+ * @access public
+ */
+ var $_auth_obj = null;
+
+ // }}}
+ // {{{ Auth_Container() [constructor]
+
+ /**
+ * Constructor
+ *
+ * Has to be overwritten by each storage class
+ *
+ * @access public
+ */
+ function Auth_Container()
+ {
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Fetch data from storage container
+ *
+ * Has to be overwritten by each storage class
+ *
+ * @access public
+ */
+ function fetchData($username, $password, $isChallengeResponse=false)
+ {
+ $this->log('Auth_Container::fetchData() called.', AUTH_LOG_DEBUG);
+ }
+
+ // }}}
+ // {{{ verifyPassword()
+
+ /**
+ * Crypt and verfiy the entered password
+ *
+ * @param string Entered password
+ * @param string Password from the data container (usually this password
+ * is already encrypted.
+ * @param string Type of algorithm with which the password from
+ * the container has been crypted. (md5, crypt etc.)
+ * Defaults to "md5".
+ * @return bool True, if the passwords match
+ */
+ function verifyPassword($password1, $password2, $cryptType = "md5")
+ {
+ $this->log('Auth_Container::verifyPassword() called.', AUTH_LOG_DEBUG);
+ switch ($cryptType) {
+ case "crypt" :
+ return ((string)crypt($password1, $password2) === (string)$password2);
+ break;
+ case "none" :
+ case "" :
+ return ((string)$password1 === (string)$password2);
+ break;
+ case "md5" :
+ return ((string)md5($password1) === (string)$password2);
+ break;
+ default :
+ if (function_exists($cryptType)) {
+ return ((string)$cryptType($password1) === (string)$password2);
+ } elseif (method_exists($this,$cryptType)) {
+ return ((string)$this->$cryptType($password1) === (string)$password2);
+ } else {
+ return false;
+ }
+ break;
+ }
+ }
+
+ // }}}
+ // {{{ supportsChallengeResponse()
+
+ /**
+ * Returns true if the container supports Challenge Response
+ * password authentication
+ */
+ function supportsChallengeResponse()
+ {
+ return(false);
+ }
+
+ // }}}
+ // {{{ getCryptType()
+
+ /**
+ * Returns the crypt current crypt type of the container
+ *
+ * @return string
+ */
+ function getCryptType()
+ {
+ return('');
+ }
+
+ // }}}
+ // {{{ listUsers()
+
+ /**
+ * List all users that are available from the storage container
+ */
+ function listUsers()
+ {
+ $this->log('Auth_Container::listUsers() called.', AUTH_LOG_DEBUG);
+ return AUTH_METHOD_NOT_SUPPORTED;
+ }
+
+ // }}}
+ // {{{ getUser()
+
+ /**
+ * Returns a user assoc array
+ *
+ * Containers which want should overide this
+ *
+ * @param string The username
+ */
+ function getUser($username)
+ {
+ $this->log('Auth_Container::getUser() called.', AUTH_LOG_DEBUG);
+ $users = $this->listUsers();
+ if ($users === AUTH_METHOD_NOT_SUPPORTED) {
+ return AUTH_METHOD_NOT_SUPPORTED;
+ }
+ for ($i=0; $c = count($users), $i<$c; $i++) {
+ if ($users[$i]['username'] == $username) {
+ return $users[$i];
+ }
+ }
+ return false;
+ }
+
+ // }}}
+ // {{{ addUser()
+
+ /**
+ * Add a new user to the storage container
+ *
+ * @param string Username
+ * @param string Password
+ * @param array Additional information
+ *
+ * @return boolean
+ */
+ function addUser($username, $password, $additional=null)
+ {
+ $this->log('Auth_Container::addUser() called.', AUTH_LOG_DEBUG);
+ return AUTH_METHOD_NOT_SUPPORTED;
+ }
+
+ // }}}
+ // {{{ removeUser()
+
+ /**
+ * Remove user from the storage container
+ *
+ * @param string Username
+ */
+ function removeUser($username)
+ {
+ $this->log('Auth_Container::removeUser() called.', AUTH_LOG_DEBUG);
+ return AUTH_METHOD_NOT_SUPPORTED;
+ }
+
+ // }}}
+ // {{{ changePassword()
+
+ /**
+ * Change password for user in the storage container
+ *
+ * @param string Username
+ * @param string The new password
+ */
+ function changePassword($username, $password)
+ {
+ $this->log('Auth_Container::changePassword() called.', AUTH_LOG_DEBUG);
+ return AUTH_METHOD_NOT_SUPPORTED;
+ }
+
+ // }}}
+ // {{{ log()
+
+ /**
+ * Log a message to the Auth log
+ *
+ * @param string The message
+ * @param int
+ * @return boolean
+ */
+ function log($message, $level = AUTH_LOG_DEBUG) {
+
+ if (is_null($this->_auth_obj)) {
+
+ return false;
+
+ } else {
+
+ return $this->_auth_obj->log($message, $level);
+
+ }
+
+ }
+
+ // }}}
+
+}
+
+?>
diff --git a/includes/pear/Auth/Container/Array.php b/includes/pear/Auth/Container/Array.php
new file mode 100644
index 0000000..334080c
--- /dev/null
+++ b/includes/pear/Auth/Container/Array.php
@@ -0,0 +1,161 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for use against a PHP Array
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author georg_1 at have2 dot com
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: Array.php 237449 2007-06-12 03:11:27Z aashley $
+ * @since File available since Release 1.4.0
+ */
+
+/**
+ * Include Auth_Container base class
+ */
+require_once "Auth/Container.php";
+/**
+ * Include PEAR package for error handling
+ */
+require_once "PEAR.php";
+
+/**
+ * Storage driver for fetching authentication data from a PHP Array
+ *
+ * This container takes two options when configuring:
+ *
+ * cryptType: The crypt used to store the password. Currently recognised
+ * are: none, md5 and crypt. default: none
+ * users: A named array of usernames and passwords.
+ * Ex:
+ * array(
+ * 'guest' => '084e0343a0486ff05530df6c705c8bb4', // password guest
+ * 'georg' => 'fc77dba827fcc88e0243404572c51325' // password georg
+ * )
+ *
+ * Usage Example:
+ * <?php
+ * $AuthOptions = array(
+ * 'users' => array(
+ * 'guest' => '084e0343a0486ff05530df6c705c8bb4', // password guest
+ * 'georg' => 'fc77dba827fcc88e0243404572c51325' // password georg
+ * ),
+ * 'cryptType'=>'md5',
+ * );
+ *
+ * $auth = new Auth("Array", $AuthOptions);
+ * ?>
+ *
+ * @category Authentication
+ * @package Auth
+ * @author georg_1 at have2 dot com
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 237449 $
+ * @since File available since Release 1.4.0
+ */
+
+class Auth_Container_Array extends Auth_Container {
+
+ // {{{ properties
+
+ /**
+ * The users and their password to authenticate against
+ *
+ * @var array $users
+ */
+ var $users;
+
+ /**
+ * The cryptType used on the passwords
+ *
+ * @var string $cryptType
+ */
+ var $cryptType = 'none';
+
+ // }}}
+ // {{{ Auth_Container_Array()
+
+ /**
+ * Constructor for Array Container
+ *
+ * @param array $data Options for the container
+ * @return void
+ */
+ function Auth_Container_Array($data)
+ {
+ if (!is_array($data)) {
+ PEAR::raiseError('The options for Auth_Container_Array must be an array');
+ }
+ if (isset($data['users']) && is_array($data['users'])) {
+ $this->users = $data['users'];
+ } else {
+ $this->users = array();
+ PEAR::raiseError('Auth_Container_Array: no user data found in options array');
+ }
+ if (isset($data['cryptType'])) {
+ $this->cryptType = $data['cryptType'];
+ }
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Get user information from array
+ *
+ * This function uses the given username to fetch the corresponding
+ * login data from the array. If an account that matches the passed
+ * username and password is found, the function returns true.
+ * Otherwise it returns false.
+ *
+ * @param string Username
+ * @param string Password
+ * @return boolean|PEAR_Error Error object or boolean
+ */
+ function fetchData($user, $pass)
+ {
+ $this->log('Auth_Container_Array::fetchData() called.', AUTH_LOG_DEBUG);
+ if ( isset($this->users[$user])
+ && $this->verifyPassword($pass, $this->users[$user], $this->cryptType)) {
+ return true;
+ }
+ return false;
+ }
+
+ // }}}
+ // {{{ listUsers()
+
+ /**
+ * Returns a list of users available within the container
+ *
+ * @return array
+ */
+ function listUsers()
+ {
+ $this->log('Auth_Container_Array::listUsers() called.', AUTH_LOG_DEBUG);
+ $ret = array();
+ foreach ($this->users as $username => $password) {
+ $ret[]['username'] = $username;
+ }
+ return $ret;
+ }
+
+ // }}}
+
+}
+
+?>
diff --git a/includes/pear/Auth/Container/DB.php b/includes/pear/Auth/Container/DB.php
new file mode 100644
index 0000000..9137d39
--- /dev/null
+++ b/includes/pear/Auth/Container/DB.php
@@ -0,0 +1,639 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for use against PEAR DB
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Martin Jansen <mj@php.net>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: DB.php 256753 2008-04-04 07:57:02Z aashley $
+ * @link http://pear.php.net/package/Auth
+ */
+
+/**
+ * Include Auth_Container base class
+ */
+require_once 'Auth/Container.php';
+/**
+ * Include PEAR DB
+ */
+require_once 'DB.php';
+
+/**
+ * Storage driver for fetching login data from a database
+ *
+ * This storage driver can use all databases which are supported
+ * by the PEAR DB abstraction layer to fetch login data.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Martin Jansen <mj@php.net>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 256753 $
+ * @link http://pear.php.net/package/Auth
+ */
+class Auth_Container_DB extends Auth_Container
+{
+
+ // {{{ properties
+
+ /**
+ * Additional options for the storage container
+ * @var array
+ */
+ var $options = array();
+
+ /**
+ * DB object
+ * @var object
+ */
+ var $db = null;
+ var $dsn = '';
+
+ /**
+ * User that is currently selected from the DB.
+ * @var string
+ */
+ var $activeUser = '';
+
+ // }}}
+ // {{{ Auth_Container_DB [constructor]
+
+ /**
+ * Constructor of the container class
+ *
+ * Save the initial options passed to the container. Initiation of the DB
+ * connection is no longer performed here and is only done when needed.
+ *
+ * @param string Connection data or DB object
+ * @return object Returns an error object if something went wrong
+ */
+ function Auth_Container_DB($dsn)
+ {
+ $this->_setDefaults();
+
+ if (is_array($dsn)) {
+ $this->_parseOptions($dsn);
+
+ if (empty($this->options['dsn'])) {
+ PEAR::raiseError('No connection parameters specified!');
+ }
+ } else {
+ $this->options['dsn'] = $dsn;
+ }
+ }
+
+ // }}}
+ // {{{ _connect()
+
+ /**
+ * Connect to database by using the given DSN string
+ *
+ * @access private
+ * @param string DSN string
+ * @return mixed Object on error, otherwise bool
+ */
+ function _connect($dsn)
+ {
+ $this->log('Auth_Container_DB::_connect() called.', AUTH_LOG_DEBUG);
+
+ if (is_string($dsn) || is_array($dsn)) {
+ $this->db = DB::Connect($dsn, $this->options['db_options']);
+ } elseif (is_subclass_of($dsn, 'db_common')) {
+ $this->db = $dsn;
+ } elseif (DB::isError($dsn)) {
+ return PEAR::raiseError($dsn->getMessage(), $dsn->getCode());
+ } else {
+ return PEAR::raiseError('The given dsn was not valid in file ' . __FILE__ . ' at line ' . __LINE__,
+ 41,
+ PEAR_ERROR_RETURN,
+ null,
+ null
+ );
+ }
+
+ if (DB::isError($this->db) || PEAR::isError($this->db)) {
+ return PEAR::raiseError($this->db->getMessage(), $this->db->getCode());
+ } else {
+ return true;
+ }
+ }
+
+ // }}}
+ // {{{ _prepare()
+
+ /**
+ * Prepare database connection
+ *
+ * This function checks if we have already opened a connection to
+ * the database. If that's not the case, a new connection is opened.
+ *
+ * @access private
+ * @return mixed True or a DB error object.
+ */
+ function _prepare()
+ {
+ if (!DB::isConnection($this->db)) {
+ $res = $this->_connect($this->options['dsn']);
+ if (DB::isError($res) || PEAR::isError($res)) {
+ return $res;
+ }
+ }
+ if ($this->options['auto_quote'] && $this->db->dsn['phptype'] != 'sqlite') {
+ if (strpos('.', $this->options['table']) === false) {
+ $this->options['final_table'] = $this->db->quoteIdentifier($this->options['table']);
+ } else {
+ $t = explode('.', $this->options['table']);
+ for ($i = 0, $count = count($t); $i < $count; $i++)
+ $t[$i] = $this->db->quoteIdentifier($t[$i]);
+ $this->options['final_table'] = implode('.', $t);
+ }
+ $this->options['final_usernamecol'] = $this->db->quoteIdentifier($this->options['usernamecol']);
+ $this->options['final_passwordcol'] = $this->db->quoteIdentifier($this->options['passwordcol']);
+ } else {
+ $this->options['final_table'] = $this->options['table'];
+ $this->options['final_usernamecol'] = $this->options['usernamecol'];
+ $this->options['final_passwordcol'] = $this->options['passwordcol'];
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ query()
+
+ /**
+ * Prepare query to the database
+ *
+ * This function checks if we have already opened a connection to
+ * the database. If that's not the case, a new connection is opened.
+ * After that the query is passed to the database.
+ *
+ * @access public
+ * @param string Query string
+ * @return mixed a DB_result object or DB_OK on success, a DB
+ * or PEAR error on failure
+ */
+ function query($query)
+ {
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return $err;
+ }
+ return $this->db->query($query);
+ }
+
+ // }}}
+ // {{{ _setDefaults()
+
+ /**
+ * Set some default options
+ *
+ * @access private
+ * @return void
+ */
+ function _setDefaults()
+ {
+ $this->options['table'] = 'auth';
+ $this->options['usernamecol'] = 'username';
+ $this->options['passwordcol'] = 'password';
+ $this->options['dsn'] = '';
+ $this->options['db_fields'] = '';
+ $this->options['cryptType'] = 'md5';
+ $this->options['db_options'] = array();
+ $this->options['db_where'] = '';
+ $this->options['auto_quote'] = true;
+ }
+
+ // }}}
+ // {{{ _parseOptions()
+
+ /**
+ * Parse options passed to the container class
+ *
+ * @access private
+ * @param array
+ */
+ function _parseOptions($array)
+ {
+ foreach ($array as $key => $value) {
+ if (isset($this->options[$key])) {
+ $this->options[$key] = $value;
+ }
+ }
+ }
+
+ // }}}
+ // {{{ _quoteDBFields()
+
+ /**
+ * Quote the db_fields option to avoid the possibility of SQL injection.
+ *
+ * @access private
+ * @return string A properly quoted string that can be concatenated into a
+ * SELECT clause.
+ */
+ function _quoteDBFields()
+ {
+ if (isset($this->options['db_fields'])) {
+ if (is_array($this->options['db_fields'])) {
+ if ($this->options['auto_quote']) {
+ $fields = array();
+ foreach ($this->options['db_fields'] as $field) {
+ $fields[] = $this->db->quoteIdentifier($field);
+ }
+ return implode(', ', $fields);
+ } else {
+ return implode(', ', $this->options['db_fields']);
+ }
+ } else {
+ if (strlen($this->options['db_fields']) > 0) {
+ if ($this->options['auto_quote']) {
+ return $this->db->quoteIdentifier($this->options['db_fields']);
+ } else {
+ return $this->options['db_fields'];
+ }
+ }
+ }
+ }
+
+ return '';
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Get user information from database
+ *
+ * This function uses the given username to fetch
+ * the corresponding login data from the database
+ * table. If an account that matches the passed username
+ * and password is found, the function returns true.
+ * Otherwise it returns false.
+ *
+ * @param string Username
+ * @param string Password
+ * @param boolean If true password is secured using a md5 hash
+ * the frontend and auth are responsible for making sure the container supports
+ * challenge response password authentication
+ * @return mixed Error object or boolean
+ */
+ function fetchData($username, $password, $isChallengeResponse=false)
+ {
+ $this->log('Auth_Container_DB::fetchData() called.', AUTH_LOG_DEBUG);
+ // Prepare for a database query
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ // Find if db_fields contains a *, if so assume all columns are selected
+ if (is_string($this->options['db_fields'])
+ && strstr($this->options['db_fields'], '*')) {
+ $sql_from = "*";
+ } else {
+ $sql_from = $this->options['final_usernamecol'].
+ ", ".$this->options['final_passwordcol'];
+
+ if (strlen($fields = $this->_quoteDBFields()) > 0) {
+ $sql_from .= ', '.$fields;
+ }
+ }
+
+ $query = "SELECT ".$sql_from.
+ " FROM ".$this->options['final_table'].
+ " WHERE ".$this->options['final_usernamecol']." = ".$this->db->quoteSmart($username);
+
+ // check if there is an optional parameter db_where
+ if ($this->options['db_where'] != '') {
+ // there is one, so add it to the query
+ $query .= " AND ".$this->options['db_where'];
+ }
+
+ $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG);
+
+ $res = $this->db->getRow($query, null, DB_FETCHMODE_ASSOC);
+
+ if (DB::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->getCode());
+ }
+
+ if (!is_array($res)) {
+ $this->activeUser = '';
+ return false;
+ }
+
+ // Perform trimming here before the hashihg
+ $password = trim($password, "\r\n");
+ $res[$this->options['passwordcol']] = trim($res[$this->options['passwordcol']], "\r\n");
+
+ // If using Challenge Response md5 the pass with the secret
+ if ($isChallengeResponse) {
+ $res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]
+ .$this->_auth_obj->session['loginchallenege']);
+
+ // UGLY cannot avoid without modifying verifyPassword
+ if ($this->options['cryptType'] == 'md5') {
+ $res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]);
+ }
+
+ //print " Hashed Password [{$res[$this->options['passwordcol']]}]<br/>\n";
+ }
+
+ if ($this->verifyPassword($password,
+ $res[$this->options['passwordcol']],
+ $this->options['cryptType'])) {
+ // Store additional field values in the session
+ foreach ($res as $key => $value) {
+ if ($key == $this->options['passwordcol'] ||
+ $key == $this->options['usernamecol']) {
+ continue;
+ }
+
+ $this->log('Storing additional field: '.$key, AUTH_LOG_DEBUG);
+
+ // Use reference to the auth object if exists
+ // This is because the auth session variable can change so a
+ // static call to setAuthData does not make sence
+ $this->_auth_obj->setAuthData($key, $value);
+ }
+ return true;
+ }
+ $this->activeUser = $res[$this->options['usernamecol']];
+ return false;
+ }
+
+ // }}}
+ // {{{ listUsers()
+
+ /**
+ * Returns a list of users from the container
+ *
+ * @return mixed
+ * @access public
+ */
+ function listUsers()
+ {
+ $this->log('Auth_Container_DB::listUsers() called.', AUTH_LOG_DEBUG);
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ $retVal = array();
+
+ // Find if db_fields contains a *, if so assume all col are selected
+ if ( is_string($this->options['db_fields'])
+ && strstr($this->options['db_fields'], '*')) {
+ $sql_from = "*";
+ } else {
+ $sql_from = $this->options['final_usernamecol'].
+ ", ".$this->options['final_passwordcol'];
+
+ if (strlen($fields = $this->_quoteDBFields()) > 0) {
+ $sql_from .= ', '.$fields;
+ }
+ }
+
+ $query = sprintf("SELECT %s FROM %s",
+ $sql_from,
+ $this->options['final_table']
+ );
+
+ // check if there is an optional parameter db_where
+ if ($this->options['db_where'] != '') {
+ // there is one, so add it to the query
+ $query .= " WHERE ".$this->options['db_where'];
+ }
+
+ $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG);
+
+ $res = $this->db->getAll($query, null, DB_FETCHMODE_ASSOC);
+
+ if (DB::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->getCode());
+ } else {
+ foreach ($res as $user) {
+ $user['username'] = $user[$this->options['usernamecol']];
+ $retVal[] = $user;
+ }
+ }
+ $this->log('Found '.count($retVal).' users.', AUTH_LOG_DEBUG);
+ return $retVal;
+ }
+
+ // }}}
+ // {{{ addUser()
+
+ /**
+ * Add user to the storage container
+ *
+ * @access public
+ * @param string Username
+ * @param string Password
+ * @param mixed Additional information that are stored in the DB
+ *
+ * @return mixed True on success, otherwise error object
+ */
+ function addUser($username, $password, $additional = "")
+ {
+ $this->log('Auth_Container_DB::addUser() called.', AUTH_LOG_DEBUG);
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ if ( isset($this->options['cryptType'])
+ && $this->options['cryptType'] == 'none') {
+ $cryptFunction = 'strval';
+ } elseif ( isset($this->options['cryptType'])
+ && function_exists($this->options['cryptType'])) {
+ $cryptFunction = $this->options['cryptType'];
+ } else {
+ $cryptFunction = 'md5';
+ }
+
+ $password = $cryptFunction($password);
+
+ $additional_key = '';
+ $additional_value = '';
+
+ if (is_array($additional)) {
+ foreach ($additional as $key => $value) {
+ if ($this->options['auto_quote']) {
+ $additional_key .= ', ' . $this->db->quoteIdentifier($key);
+ } else {
+ $additional_key .= ', ' . $key;
+ }
+ $additional_value .= ", " . $this->db->quoteSmart($value);
+ }
+ }
+
+ $query = sprintf("INSERT INTO %s (%s, %s%s) VALUES (%s, %s%s)",
+ $this->options['final_table'],
+ $this->options['final_usernamecol'],
+ $this->options['final_passwordcol'],
+ $additional_key,
+ $this->db->quoteSmart($username),
+ $this->db->quoteSmart($password),
+ $additional_value
+ );
+
+ $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG);
+
+ $res = $this->query($query);
+
+ if (DB::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->getCode());
+ } else {
+ return true;
+ }
+ }
+
+ // }}}
+ // {{{ removeUser()
+
+ /**
+ * Remove user from the storage container
+ *
+ * @access public
+ * @param string Username
+ *
+ * @return mixed True on success, otherwise error object
+ */
+ function removeUser($username)
+ {
+ $this->log('Auth_Container_DB::removeUser() called.', AUTH_LOG_DEBUG);
+
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ // check if there is an optional parameter db_where
+ if ($this->options['db_where'] != '') {
+ // there is one, so add it to the query
+ $where = " AND ".$this->options['db_where'];
+ } else {
+ $where = '';
+ }
+
+ $query = sprintf("DELETE FROM %s WHERE %s = %s %s",
+ $this->options['final_table'],
+ $this->options['final_usernamecol'],
+ $this->db->quoteSmart($username),
+ $where
+ );
+
+ $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG);
+
+ $res = $this->query($query);
+
+ if (DB::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->getCode());
+ } else {
+ return true;
+ }
+ }
+
+ // }}}
+ // {{{ changePassword()
+
+ /**
+ * Change password for user in the storage container
+ *
+ * @param string Username
+ * @param string The new password (plain text)
+ */
+ function changePassword($username, $password)
+ {
+ $this->log('Auth_Container_DB::changePassword() called.', AUTH_LOG_DEBUG);
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ if ( isset($this->options['cryptType'])
+ && $this->options['cryptType'] == 'none') {
+ $cryptFunction = 'strval';
+ } elseif ( isset($this->options['cryptType'])
+ && function_exists($this->options['cryptType'])) {
+ $cryptFunction = $this->options['cryptType'];
+ } else {
+ $cryptFunction = 'md5';
+ }
+
+ $password = $cryptFunction($password);
+
+ // check if there is an optional parameter db_where
+ if ($this->options['db_where'] != '') {
+ // there is one, so add it to the query
+ $where = " AND ".$this->options['db_where'];
+ } else {
+ $where = '';
+ }
+
+ $query = sprintf("UPDATE %s SET %s = %s WHERE %s = %s %s",
+ $this->options['final_table'],
+ $this->options['final_passwordcol'],
+ $this->db->quoteSmart($password),
+ $this->options['final_usernamecol'],
+ $this->db->quoteSmart($username),
+ $where
+ );
+
+ $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG);
+
+ $res = $this->query($query);
+
+ if (DB::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->getCode());
+ } else {
+ return true;
+ }
+ }
+
+ // }}}
+ // {{{ supportsChallengeResponse()
+
+ /**
+ * Determine if this container supports
+ * password authentication with challenge response
+ *
+ * @return bool
+ * @access public
+ */
+ function supportsChallengeResponse()
+ {
+ return in_array($this->options['cryptType'], array('md5', 'none', ''));
+ }
+
+ // }}}
+ // {{{ getCryptType()
+
+ /**
+ * Returns the selected crypt type for this container
+ */
+ function getCryptType()
+ {
+ return($this->options['cryptType']);
+ }
+
+ // }}}
+
+}
+?>
diff --git a/includes/pear/Auth/Container/DBLite.php b/includes/pear/Auth/Container/DBLite.php
new file mode 100644
index 0000000..2996108
--- /dev/null
+++ b/includes/pear/Auth/Container/DBLite.php
@@ -0,0 +1,320 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Reduced storage driver for use against PEAR DB
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Martin Jansen <mj@php.net>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: DBLite.php 256753 2008-04-04 07:57:02Z aashley $
+ * @link http://pear.php.net/package/Auth
+ * @since File available since Release 1.3.0
+ */
+
+/**
+ * Include Auth_Container base class
+ */
+require_once 'Auth/Container.php';
+/**
+ * Include PEAR DB package
+ */
+require_once 'DB.php';
+
+/**
+ * A lighter storage driver for fetching login data from a database
+ *
+ * This driver is derived from the DB storage container but
+ * with the user manipulation function removed for smaller file size
+ * by the PEAR DB abstraction layer to fetch login data.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Martin Jansen <mj@php.net>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 256753 $
+ * @link http://pear.php.net/package/Auth
+ * @since Class available since Release 1.3.0
+ */
+class Auth_Container_DBLite extends Auth_Container
+{
+
+ // {{{ properties
+
+ /**
+ * Additional options for the storage container
+ * @var array
+ */
+ var $options = array();
+
+ /**
+ * DB object
+ * @var object
+ */
+ var $db = null;
+ var $dsn = '';
+
+ /**
+ * User that is currently selected from the DB.
+ * @var string
+ */
+ var $activeUser = '';
+
+ // }}}
+ // {{{ Auth_Container_DBLite() [constructor]
+
+ /**
+ * Constructor of the container class
+ *
+ * Initate connection to the database via PEAR::DB
+ *
+ * @param string Connection data or DB object
+ * @return object Returns an error object if something went wrong
+ */
+ function Auth_Container_DBLite($dsn)
+ {
+ $this->options['table'] = 'auth';
+ $this->options['usernamecol'] = 'username';
+ $this->options['passwordcol'] = 'password';
+ $this->options['dsn'] = '';
+ $this->options['db_fields'] = '';
+ $this->options['cryptType'] = 'md5';
+ $this->options['db_options'] = array();
+ $this->options['db_where'] = '';
+ $this->options['auto_quote'] = true;
+
+ if (is_array($dsn)) {
+ $this->_parseOptions($dsn);
+ if (empty($this->options['dsn'])) {
+ PEAR::raiseError('No connection parameters specified!');
+ }
+ } else {
+ $this->options['dsn'] = $dsn;
+ }
+ }
+
+ // }}}
+ // {{{ _connect()
+
+ /**
+ * Connect to database by using the given DSN string
+ *
+ * @access private
+ * @param string DSN string
+ * @return mixed Object on error, otherwise bool
+ */
+ function _connect(&$dsn)
+ {
+ $this->log('Auth_Container_DBLite::_connect() called.', AUTH_LOG_DEBUG);
+ if (is_string($dsn) || is_array($dsn)) {
+ $this->db =& DB::connect($dsn, $this->options['db_options']);
+ } elseif (is_subclass_of($dsn, "db_common")) {
+ $this->db =& $dsn;
+ } else {
+ return PEAR::raiseError("Invalid dsn or db object given");
+ }
+
+ if (DB::isError($this->db) || PEAR::isError($this->db)) {
+ return PEAR::raiseError($this->db->getMessage(), $this->db->getCode());
+ } else {
+ return true;
+ }
+ }
+
+ // }}}
+ // {{{ _prepare()
+
+ /**
+ * Prepare database connection
+ *
+ * This function checks if we have already opened a connection to
+ * the database. If that's not the case, a new connection is opened.
+ *
+ * @access private
+ * @return mixed True or a DB error object.
+ */
+ function _prepare()
+ {
+ if (!DB::isConnection($this->db)) {
+ $res = $this->_connect($this->options['dsn']);
+ if (DB::isError($res) || PEAR::isError($res)) {
+ return $res;
+ }
+ }
+ if ($this->options['auto_quote'] && $this->db->dsn['phptype'] != 'sqlite') {
+ if (strpos('.', $this->options['table']) === false) {
+ $this->options['final_table'] = $this->db->quoteIdentifier($this->options['table']);
+ } else {
+ $t = explode('.', $this->options['table']);
+ for ($i = 0, $count = count($t); $i < $count; $i++)
+ $t[$i] = $this->db->quoteIdentifier($t[$i]);
+ $this->options['final_table'] = implode('.', $t);
+ }
+ $this->options['final_usernamecol'] = $this->db->quoteIdentifier($this->options['usernamecol']);
+ $this->options['final_passwordcol'] = $this->db->quoteIdentifier($this->options['passwordcol']);
+ } else {
+ $this->options['final_table'] = $this->options['table'];
+ $this->options['final_usernamecol'] = $this->options['usernamecol'];
+ $this->options['final_passwordcol'] = $this->options['passwordcol'];
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ _parseOptions()
+
+ /**
+ * Parse options passed to the container class
+ *
+ * @access private
+ * @param array
+ */
+ function _parseOptions($array)
+ {
+ foreach ($array as $key => $value) {
+ if (isset($this->options[$key])) {
+ $this->options[$key] = $value;
+ }
+ }
+ }
+
+ // }}}
+ // {{{ _quoteDBFields()
+
+ /**
+ * Quote the db_fields option to avoid the possibility of SQL injection.
+ *
+ * @access private
+ * @return string A properly quoted string that can be concatenated into a
+ * SELECT clause.
+ */
+ function _quoteDBFields()
+ {
+ if (isset($this->options['db_fields'])) {
+ if (is_array($this->options['db_fields'])) {
+ if ($this->options['auto_quote']) {
+ $fields = array();
+ foreach ($this->options['db_fields'] as $field) {
+ $fields[] = $this->db->quoteIdentifier($field);
+ }
+ return implode(', ', $fields);
+ } else {
+ return implode(', ', $this->options['db_fields']);
+ }
+ } else {
+ if (strlen($this->options['db_fields']) > 0) {
+ if ($this->options['auto_quote']) {
+ return $this->db->quoteIdentifier($this->options['db_fields']);
+ } else {
+ $this->options['db_fields'];
+ }
+ }
+ }
+ }
+
+ return '';
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Get user information from database
+ *
+ * This function uses the given username to fetch
+ * the corresponding login data from the database
+ * table. If an account that matches the passed username
+ * and password is found, the function returns true.
+ * Otherwise it returns false.
+ *
+ * @param string Username
+ * @param string Password
+ * @return mixed Error object or boolean
+ */
+ function fetchData($username, $password)
+ {
+ $this->log('Auth_Container_DBLite::fetchData() called.', AUTH_LOG_DEBUG);
+ // Prepare for a database query
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ // Find if db_fields contains a *, if so assume all col are selected
+ if (is_string($this->options['db_fields'])
+ && strstr($this->options['db_fields'], '*')) {
+ $sql_from = "*";
+ } else {
+ $sql_from = $this->options['final_usernamecol'].
+ ", ".$this->options['final_passwordcol'];
+
+ if (strlen($fields = $this->_quoteDBFields()) > 0) {
+ $sql_from .= ', '.$fields;
+ }
+ }
+
+ $query = "SELECT ".$sql_from.
+ " FROM ".$this->options['final_table'].
+ " WHERE ".$this->options['final_usernamecol']." = ".$this->db->quoteSmart($username);
+
+ // check if there is an optional parameter db_where
+ if ($this->options['db_where'] != '') {
+ // there is one, so add it to the query
+ $query .= " AND ".$this->options['db_where'];
+ }
+
+ $this->log('Running SQL against DB: '.$query, AUTH_LOG_DEBUG);
+
+ $res = $this->db->getRow($query, null, DB_FETCHMODE_ASSOC);
+
+ if (DB::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->getCode());
+ }
+ if (!is_array($res)) {
+ $this->activeUser = '';
+ return false;
+ }
+ if ($this->verifyPassword(trim($password, "\r\n"),
+ trim($res[$this->options['passwordcol']], "\r\n"),
+ $this->options['cryptType'])) {
+ // Store additional field values in the session
+ foreach ($res as $key => $value) {
+ if ($key == $this->options['passwordcol'] ||
+ $key == $this->options['usernamecol']) {
+ continue;
+ }
+
+ $this->log('Storing additional field: '.$key, AUTH_LOG_DEBUG);
+
+ // Use reference to the auth object if exists
+ // This is because the auth session variable can change so a static call to setAuthData does not make sence
+ if (is_object($this->_auth_obj)) {
+ $this->_auth_obj->setAuthData($key, $value);
+ } else {
+ Auth::setAuthData($key, $value);
+ }
+ }
+ $this->activeUser = $res[$this->options['usernamecol']];
+ return true;
+ }
+ $this->activeUser = $res[$this->options['usernamecol']];
+ return false;
+ }
+
+ // }}}
+
+}
+?>
diff --git a/includes/pear/Auth/Container/File.php b/includes/pear/Auth/Container/File.php
new file mode 100644
index 0000000..2b8274c
--- /dev/null
+++ b/includes/pear/Auth/Container/File.php
@@ -0,0 +1,314 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for use against a generic password file
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Stefan Ekman <stekman@sedata.org>
+ * @author Martin Jansen <mj@php.net>
+ * @author Mika Tuupola <tuupola@appelsiini.net>
+ * @author Michael Wallner <mike@php.net>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: File.php 237449 2007-06-12 03:11:27Z aashley $
+ * @link http://pear.php.net/package/Auth
+ */
+
+/**
+ * Include PEAR File_Passwd package
+ */
+require_once "File/Passwd.php";
+/**
+ * Include Auth_Container base class
+ */
+require_once "Auth/Container.php";
+/**
+ * Include PEAR package for error handling
+ */
+require_once "PEAR.php";
+
+/**
+ * Storage driver for fetching login data from an encrypted password file.
+ *
+ * This storage container can handle CVS pserver style passwd files.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Stefan Ekman <stekman@sedata.org>
+ * @author Martin Jansen <mj@php.net>
+ * @author Mika Tuupola <tuupola@appelsiini.net>
+ * @author Michael Wallner <mike@php.net>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 237449 $
+ * @link http://pear.php.net/package/Auth
+ */
+class Auth_Container_File extends Auth_Container
+{
+
+ // {{{ properties
+
+ /**
+ * Path to passwd file
+ *
+ * @var string
+ */
+ var $pwfile = '';
+
+ /**
+ * Options for container
+ *
+ * @var array
+ */
+ var $options = array();
+
+ // }}}
+ // {{{ Auth_Container_File() [constructor]
+
+ /**
+ * Constructor of the container class
+ *
+ * @param string $filename path to passwd file
+ * @return object Auth_Container_File new Auth_Container_File object
+ */
+ function Auth_Container_File($filename) {
+ $this->_setDefaults();
+
+ // Only file is a valid option here
+ if(is_array($filename)) {
+ $this->pwfile = $filename['file'];
+ $this->_parseOptions($filename);
+ } else {
+ $this->pwfile = $filename;
+ }
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Authenticate an user
+ *
+ * @param string username
+ * @param string password
+ * @return mixed boolean|PEAR_Error
+ */
+ function fetchData($user, $pass)
+ {
+ $this->log('Auth_Container_File::fetchData() called.', AUTH_LOG_DEBUG);
+ return File_Passwd::staticAuth($this->options['type'], $this->pwfile, $user, $pass);
+ }
+
+ // }}}
+ // {{{ listUsers()
+
+ /**
+ * List all available users
+ *
+ * @return array
+ */
+ function listUsers()
+ {
+ $this->log('Auth_Container_File::listUsers() called.', AUTH_LOG_DEBUG);
+
+ $pw_obj = &$this->_load();
+ if (PEAR::isError($pw_obj)) {
+ return array();
+ }
+
+ $users = $pw_obj->listUser();
+ if (!is_array($users)) {
+ return array();
+ }
+
+ foreach ($users as $key => $value) {
+ $retVal[] = array("username" => $key,
+ "password" => $value['passwd'],
+ "cvsuser" => $value['system']);
+ }
+
+ $this->log('Found '.count($retVal).' users.', AUTH_LOG_DEBUG);
+
+ return $retVal;
+ }
+
+ // }}}
+ // {{{ addUser()
+
+ /**
+ * Add a new user to the storage container
+ *
+ * @param string username
+ * @param string password
+ * @param mixed Additional parameters to File_Password_*::addUser()
+ *
+ * @return boolean
+ */
+ function addUser($user, $pass, $additional='')
+ {
+ $this->log('Auth_Container_File::addUser() called.', AUTH_LOG_DEBUG);
+ $params = array($user, $pass);
+ if (is_array($additional)) {
+ foreach ($additional as $item) {
+ $params[] = $item;
+ }
+ } else {
+ $params[] = $additional;
+ }
+
+ $pw_obj = &$this->_load();
+ if (PEAR::isError($pw_obj)) {
+ return false;
+ }
+
+ $res = call_user_func_array(array(&$pw_obj, 'addUser'), $params);
+ if (PEAR::isError($res)) {
+ return false;
+ }
+
+ $res = $pw_obj->save();
+ if (PEAR::isError($res)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ // }}}
+ // {{{ removeUser()
+
+ /**
+ * Remove user from the storage container
+ *
+ * @param string Username
+ * @return boolean
+ */
+ function removeUser($user)
+ {
+ $this->log('Auth_Container_File::removeUser() called.', AUTH_LOG_DEBUG);
+ $pw_obj = &$this->_load();
+ if (PEAR::isError($pw_obj)) {
+ return false;
+ }
+
+ $res = $pw_obj->delUser($user);
+ if (PEAR::isError($res)) {
+ return false;
+ }
+
+ $res = $pw_obj->save();
+ if (PEAR::isError($res)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ // }}}
+ // {{{ changePassword()
+
+ /**
+ * Change password for user in the storage container
+ *
+ * @param string Username
+ * @param string The new password
+ */
+ function changePassword($username, $password)
+ {
+ $this->log('Auth_Container_File::changePassword() called.', AUTH_LOG_DEBUG);
+ $pw_obj = &$this->_load();
+ if (PEAR::isError($pw_obj)) {
+ return false;
+ }
+
+ $res = $pw_obj->changePasswd($username, $password);
+ if (PEAR::isError($res)) {
+ return false;
+ }
+
+ $res = $pw_obj->save();
+ if (PEAR::isError($res)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ // }}}
+ // {{{ _load()
+
+ /**
+ * Load and initialize the File_Passwd object
+ *
+ * @return object File_Passwd_Cvs|PEAR_Error
+ */
+ function &_load()
+ {
+ static $pw_obj;
+
+ if (!isset($pw_obj)) {
+ $this->log('Instanciating File_Password object of type '.$this->options['type'], AUTH_LOG_DEBUG);
+ $pw_obj = File_Passwd::factory($this->options['type']);
+ if (PEAR::isError($pw_obj)) {
+ return $pw_obj;
+ }
+
+ $pw_obj->setFile($this->pwfile);
+
+ $res = $pw_obj->load();
+ if (PEAR::isError($res)) {
+ return $res;
+ }
+ }
+
+ return $pw_obj;
+ }
+
+ // }}}
+ // {{{ _setDefaults()
+
+ /**
+ * Set some default options
+ *
+ * @access private
+ * @return void
+ */
+ function _setDefaults()
+ {
+ $this->options['type'] = 'Cvs';
+ }
+
+ // }}}
+ // {{{ _parseOptions()
+
+ /**
+ * Parse options passed to the container class
+ *
+ * @access private
+ * @param array
+ */
+ function _parseOptions($array)
+ {
+ foreach ($array as $key => $value) {
+ if (isset($this->options[$key])) {
+ $this->options[$key] = $value;
+ }
+ }
+ }
+
+ // }}}
+
+}
+?>
diff --git a/includes/pear/Auth/Container/IMAP.php b/includes/pear/Auth/Container/IMAP.php
new file mode 100644
index 0000000..2768483
--- /dev/null
+++ b/includes/pear/Auth/Container/IMAP.php
@@ -0,0 +1,210 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for use against IMAP servers
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Jeroen Houben <jeroen@terena.nl>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: IMAP.php 237449 2007-06-12 03:11:27Z aashley $
+ * @link http://pear.php.net/package/Auth
+ * @since File available since Release 1.2.0
+ */
+
+/**
+ * Include Auth_Container base class
+ */
+require_once "Auth/Container.php";
+
+/**
+ * Include PEAR class for error handling
+ */
+require_once "PEAR.php";
+
+/**
+ * Storage driver for fetching login data from an IMAP server
+ *
+ * This class is based on LDAP containers, but it very simple.
+ * By default it connects to localhost:143
+ * The constructor will first check if the host:port combination is
+ * actually reachable. This behaviour can be disabled.
+ * It then tries to create an IMAP stream (without opening a mailbox)
+ * If you wish to pass extended options to the connections, you may
+ * do so by specifying protocol options.
+ *
+ * To use this storage containers, you have to use the
+ * following syntax:
+ *
+ * <?php
+ * ...
+ * $params = array(
+ * 'host' => 'mail.example.com',
+ * 'port' => 143,
+ * );
+ * $myAuth = new Auth('IMAP', $params);
+ * ...
+ *
+ * By default we connect without any protocol options set. However, some
+ * servers require you to connect with the notls or norsh options set.
+ * To do this you need to add the following value to the params array:
+ * 'baseDSN' => '/imap/notls/norsh'
+ *
+ * To connect to an SSL IMAP server:
+ * 'baseDSN' => '/imap/ssl'
+ *
+ * To connect to an SSL IMAP server with a self-signed certificate:
+ * 'baseDSN' => '/imap/ssl/novalidate-cert'
+ *
+ * Further options may be available and can be found on the php site at
+ * http://www.php.net/manual/function.imap-open.php
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Jeroen Houben <jeroen@terena.nl>
+ * @author Cipriano Groenendal <cipri@campai.nl>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 237449 $
+ * @link http://pear.php.net/package/Auth
+ * @since Class available since Release 1.2.0
+ */
+class Auth_Container_IMAP extends Auth_Container
+{
+
+ // {{{ properties
+
+ /**
+ * Options for the class
+ * @var array
+ */
+ var $options = array();
+
+ // }}}
+ // {{{ Auth_Container_IMAP() [constructor]
+
+ /**
+ * Constructor of the container class
+ *
+ * @param $params associative array with host, port, baseDSN, checkServer
+ * and userattr key
+ * @return object Returns an error object if something went wrong
+ * @todo Use PEAR Net_IMAP if IMAP extension not loaded
+ */
+ function Auth_Container_IMAP($params)
+ {
+ if (!extension_loaded('imap')) {
+ return PEAR::raiseError('Cannot use IMAP authentication, '
+ .'IMAP extension not loaded!', 41, PEAR_ERROR_DIE);
+ }
+ $this->_setDefaults();
+
+ // set parameters (if any)
+ if (is_array($params)) {
+ $this->_parseOptions($params);
+ }
+
+ if ($this->options['checkServer']) {
+ $this->_checkServer($this->options['timeout']);
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ _setDefaults()
+
+ /**
+ * Set some default options
+ *
+ * @access private
+ */
+ function _setDefaults()
+ {
+ $this->options['host'] = 'localhost';
+ $this->options['port'] = 143;
+ $this->options['baseDSN'] = '';
+ $this->options['checkServer'] = true;
+ $this->options['timeout'] = 20;
+ }
+
+ // }}}
+ // {{{ _checkServer()
+
+ /**
+ * Check if the given server and port are reachable
+ *
+ * @access private
+ */
+ function _checkServer() {
+ $this->log('Auth_Container_IMAP::_checkServer() called.', AUTH_LOG_DEBUG);
+ $fp = @fsockopen ($this->options['host'], $this->options['port'],
+ $errno, $errstr, $this->options['timeout']);
+ if (is_resource($fp)) {
+ @fclose($fp);
+ } else {
+ $message = "Error connecting to IMAP server "
+ . $this->options['host']
+ . ":" . $this->options['port'];
+ return PEAR::raiseError($message, 41);
+ }
+ }
+
+ // }}}
+ // {{{ _parseOptions()
+
+ /**
+ * Parse options passed to the container class
+ *
+ * @access private
+ * @param array
+ */
+ function _parseOptions($array)
+ {
+ foreach ($array as $key => $value) {
+ $this->options[$key] = $value;
+ }
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Try to open a IMAP stream using $username / $password
+ *
+ * @param string Username
+ * @param string Password
+ * @return boolean
+ */
+ function fetchData($username, $password)
+ {
+ $this->log('Auth_Container_IMAP::fetchData() called.', AUTH_LOG_DEBUG);
+ $dsn = '{'.$this->options['host'].':'.$this->options['port'].$this->options['baseDSN'].'}';
+ $conn = @imap_open ($dsn, $username, $password, OP_HALFOPEN);
+ if (is_resource($conn)) {
+ $this->log('Successfully connected to IMAP server.', AUTH_LOG_DEBUG);
+ $this->activeUser = $username;
+ @imap_close($conn);
+ return true;
+ } else {
+ $this->log('Connection to IMAP server failed.', AUTH_LOG_DEBUG);
+ $this->activeUser = '';
+ return false;
+ }
+ }
+
+ // }}}
+
+}
+?>
diff --git a/includes/pear/Auth/Container/KADM5.php b/includes/pear/Auth/Container/KADM5.php
new file mode 100644
index 0000000..2853580
--- /dev/null
+++ b/includes/pear/Auth/Container/KADM5.php
@@ -0,0 +1,171 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for Authentication on a Kerberos V server.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Andrew Teixeira <ateixeira@gmail.com>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: KADM5.php 237449 2007-06-12 03:11:27Z aashley $
+ * @link http://pear.php.net/package/Auth
+ * @since File available since Release 1.4.0
+ */
+
+/**
+ * Include Auth_Container base class
+ */
+require_once 'Auth/Container.php';
+/**
+ * Include PEAR for error handling
+ */
+require_once 'PEAR.php';
+
+/**
+ * Storage driver for Authentication on a Kerberos V server.
+ *
+ * Available options:
+ * hostname: The hostname of the kerberos server
+ * realm: The Kerberos V realm
+ * timeout: The timeout for checking the server
+ * checkServer: Set to true to check if the server is running when
+ * constructing the object
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Andrew Teixeira <ateixeira@gmail.com>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 237449 $
+ * @link http://pear.php.net/package/Auth
+ * @since Class available since Release 1.4.0
+ */
+class Auth_Container_KADM5 extends Auth_Container {
+
+ // {{{ properties
+
+ /**
+ * Options for the class
+ * @var string
+ */
+ var $options = array();
+
+ // }}}
+ // {{{ Auth_Container_KADM5()
+
+ /**
+ * Constructor of the container class
+ *
+ * $options can have these keys:
+ * 'hostname' The hostname of the kerberos server
+ * 'realm' The Kerberos V realm
+ * 'timeout' The timeout for checking the server
+ * 'checkServer' Set to true to check if the server is running when
+ * constructing the object
+ *
+ * @param $options associative array
+ * @return object Returns an error object if something went wrong
+ */
+ function Auth_Container_KADM5($options) {
+ if (!extension_loaded('kadm5')) {
+ return PEAR::raiseError("Cannot use Kerberos V authentication, KADM5 extension not loaded!", 41, PEAR_ERROR_DIE);
+ }
+
+ $this->_setDefaults();
+
+ if (isset($options['hostname'])) {
+ $this->options['hostname'] = $options['hostname'];
+ }
+ if (isset($options['realm'])) {
+ $this->options['realm'] = $options['realm'];
+ }
+ if (isset($options['timeout'])) {
+ $this->options['timeout'] = $options['timeout'];
+ }
+ if (isset($options['checkServer'])) {
+ $this->options['checkServer'] = $options['checkServer'];
+ }
+
+ if ($this->options['checkServer']) {
+ $this->_checkServer();
+ }
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Try to login to the KADM5 server
+ *
+ * @param string Username
+ * @param string Password
+ * @return boolean
+ */
+ function fetchData($username, $password) {
+ $this->log('Auth_Container_KADM5::fetchData() called.', AUTH_LOG_DEBUG);
+ if ( ($username == NULL) || ($password == NULL) ) {
+ return false;
+ }
+
+ $server = $this->options['hostname'];
+ $realm = $this->options['realm'];
+ $check = @kadm5_init_with_password($server, $realm, $username, $password);
+
+ if ($check == false) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ // }}}
+ // {{{ _setDefaults()
+
+ /**
+ * Set some default options
+ *
+ * @access private
+ */
+ function _setDefaults() {
+ $this->options['hostname'] = 'localhost';
+ $this->options['realm'] = NULL;
+ $this->options['timeout'] = 10;
+ $this->options['checkServer'] = false;
+ }
+
+ // }}}
+ // {{{ _checkServer()
+
+ /**
+ * Check if the given server and port are reachable
+ *
+ * @access private
+ */
+ function _checkServer() {
+ $fp = @fsockopen ($this->options['hostname'], 88, $errno, $errstr, $this->options['timeout']);
+ if (is_resource($fp)) {
+ @fclose($fp);
+ } else {
+ $message = "Error connecting to Kerberos V server "
+ .$this->options['hostname'].":".$this->options['port'];
+ return PEAR::raiseError($message, 41, PEAR_ERROR_DIE);
+ }
+ }
+
+ // }}}
+
+}
+
+?>
diff --git a/includes/pear/Auth/Container/LDAP.php b/includes/pear/Auth/Container/LDAP.php
new file mode 100644
index 0000000..eaf35cf
--- /dev/null
+++ b/includes/pear/Auth/Container/LDAP.php
@@ -0,0 +1,766 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for use against an LDAP server
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Jan Wagner <wagner@netsols.de>
+ * @author Adam Ashley <aashley@php.net>
+ * @author Hugues Peeters <hugues.peeters@claroline.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: LDAP.php 237449 2007-06-12 03:11:27Z aashley $
+ * @link http://pear.php.net/package/Auth
+ */
+
+/**
+ * Include Auth_Container base class
+ */
+require_once "Auth/Container.php";
+/**
+ * Include PEAR package for error handling
+ */
+require_once "PEAR.php";
+
+/**
+ * Storage driver for fetching login data from LDAP
+ *
+ * This class is heavily based on the DB and File containers. By default it
+ * connects to localhost:389 and searches for uid=$username with the scope
+ * "sub". If no search base is specified, it will try to determine it via
+ * the namingContexts attribute. It takes its parameters in a hash, connects
+ * to the ldap server, binds anonymously, searches for the user, and tries
+ * to bind as the user with the supplied password. When a group was set, it
+ * will look for group membership of the authenticated user. If all goes
+ * well the authentication was successful.
+ *
+ * Parameters:
+ *
+ * host: localhost (default), ldap.netsols.de or 127.0.0.1
+ * port: 389 (default) or 636 or whereever your server runs
+ * url: ldap://localhost:389/
+ * useful for ldaps://, works only with openldap2 ?
+ * it will be preferred over host and port
+ * version: LDAP version to use, ususally 2 (default) or 3,
+ * must be an integer!
+ * referrals: If set, determines whether the LDAP library automatically
+ * follows referrals returned by LDAP servers or not. Possible
+ * values are true (default) or false.
+ * binddn: If set, searching for user will be done after binding
+ * as this user, if not set the bind will be anonymous.
+ * This is reported to make the container work with MS
+ * Active Directory, but should work with any server that
+ * is configured this way.
+ * This has to be a complete dn for now (basedn and
+ * userdn will not be appended).
+ * bindpw: The password to use for binding with binddn
+ * basedn: the base dn of your server
+ * userdn: gets prepended to basedn when searching for user
+ * userscope: Scope for user searching: one, sub (default), or base
+ * userattr: the user attribute to search for (default: uid)
+ * userfilter: filter that will be added to the search filter
+ * this way: (&(userattr=username)(userfilter))
+ * default: (objectClass=posixAccount)
+ * attributes: array of additional attributes to fetch from entry.
+ * these will added to auth data and can be retrieved via
+ * Auth::getAuthData(). An empty array will fetch all attributes,
+ * array('') will fetch no attributes at all (default)
+ * If you add 'dn' as a value to this array, the users DN that was
+ * used for binding will be added to auth data as well.
+ * attrformat: The returned format of the additional data defined in the
+ * 'attributes' option. Two formats are available.
+ * LDAP returns data formatted in a
+ * multidimensional array where each array starts with a
+ * 'count' element providing the number of attributes in the
+ * entry, or the number of values for attributes. When set
+ * to this format, the only way to retrieve data from the
+ * Auth object is by calling getAuthData('attributes').
+ * AUTH returns data formatted in a
+ * structure more compliant with other Auth Containers,
+ * where each attribute element can be directly called by
+ * getAuthData() method from Auth.
+ * For compatibily with previous LDAP container versions,
+ * the default format is LDAP.
+ * groupdn: gets prepended to basedn when searching for group
+ * groupattr: the group attribute to search for (default: cn)
+ * groupfilter: filter that will be added to the search filter when
+ * searching for a group:
+ * (&(groupattr=group)(memberattr=username)(groupfilter))
+ * default: (objectClass=groupOfUniqueNames)
+ * memberattr : the attribute of the group object where the user dn
+ * may be found (default: uniqueMember)
+ * memberisdn: whether the memberattr is the dn of the user (default)
+ * or the value of userattr (usually uid)
+ * group: the name of group to search for
+ * groupscope: Scope for group searching: one, sub (default), or base
+ * start_tls: enable/disable the use of START_TLS encrypted connection
+ * (default: false)
+ * debug: Enable/Disable debugging output (default: false)
+ * try_all: Whether to try all user accounts returned from the search
+ * or just the first one. (default: false)
+ *
+ * To use this storage container, you have to use the following syntax:
+ *
+ * <?php
+ * ...
+ *
+ * $a1 = new Auth("LDAP", array(
+ * 'host' => 'localhost',
+ * 'port' => '389',
+ * 'version' => 3,
+ * 'basedn' => 'o=netsols,c=de',
+ * 'userattr' => 'uid'
+ * 'binddn' => 'cn=admin,o=netsols,c=de',
+ * 'bindpw' => 'password'));
+ *
+ * $a2 = new Auth('LDAP', array(
+ * 'url' => 'ldaps://ldap.netsols.de',
+ * 'basedn' => 'o=netsols,c=de',
+ * 'userscope' => 'one',
+ * 'userdn' => 'ou=People',
+ * 'groupdn' => 'ou=Groups',
+ * 'groupfilter' => '(objectClass=posixGroup)',
+ * 'memberattr' => 'memberUid',
+ * 'memberisdn' => false,
+ * 'group' => 'admin'
+ * ));
+ *
+ * $a3 = new Auth('LDAP', array(
+ * 'host' => 'ldap.netsols.de',
+ * 'port' => 389,
+ * 'version' => 3,
+ * 'referrals' => false,
+ * 'basedn' => 'dc=netsols,dc=de',
+ * 'binddn' => 'cn=Jan Wagner,cn=Users,dc=netsols,dc=de',
+ * 'bindpw' => 'password',
+ * 'userattr' => 'samAccountName',
+ * 'userfilter' => '(objectClass=user)',
+ * 'attributes' => array(''),
+ * 'group' => 'testing',
+ * 'groupattr' => 'samAccountName',
+ * 'groupfilter' => '(objectClass=group)',
+ * 'memberattr' => 'member',
+ * 'memberisdn' => true,
+ * 'groupdn' => 'cn=Users',
+ * 'groupscope' => 'one',
+ * 'debug' => true);
+ *
+ * The parameter values have to correspond
+ * to the ones for your LDAP server of course.
+ *
+ * When talking to a Microsoft ActiveDirectory server you have to
+ * use 'samaccountname' as the 'userattr' and follow special rules
+ * to translate the ActiveDirectory directory names into 'basedn'.
+ * The 'basedn' for the default 'Users' folder on an ActiveDirectory
+ * server for the ActiveDirectory Domain (which is not related to
+ * its DNS name) "win2000.example.org" would be:
+ * "CN=Users, DC=win2000, DC=example, DC=org'
+ * where every component of the domain name becomes a DC attribute
+ * of its own. If you want to use a custom users folder you have to
+ * replace "CN=Users" with a sequence of "OU" attributes that specify
+ * the path to your custom folder in reverse order.
+ * So the ActiveDirectory folder
+ * "win2000.example.org\Custom\Accounts"
+ * would become
+ * "OU=Accounts, OU=Custom, DC=win2000, DC=example, DC=org'
+ *
+ * It seems that binding anonymously to an Active Directory
+ * is not allowed, so you have to set binddn and bindpw for
+ * user searching.
+ *
+ * LDAP Referrals need to be set to false for AD to work sometimes.
+ *
+ * Example a3 shows a full blown and tested example for connection to
+ * Windows 2000 Active Directory with group mebership checking
+ *
+ * Note also that if you want an encrypted connection to an MS LDAP
+ * server, then, on your webserver, you must specify
+ * TLS_REQCERT never
+ * in /etc/ldap/ldap.conf or in the webserver user's ~/.ldaprc (which
+ * may or may not be read depending on your configuration).
+ *
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Jan Wagner <wagner@netsols.de>
+ * @author Adam Ashley <aashley@php.net>
+ * @author Hugues Peeters <hugues.peeters@claroline.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 237449 $
+ * @link http://pear.php.net/package/Auth
+ */
+class Auth_Container_LDAP extends Auth_Container
+{
+
+ // {{{ properties
+
+ /**
+ * Options for the class
+ * @var array
+ */
+ var $options = array();
+
+ /**
+ * Connection ID of LDAP Link
+ * @var string
+ */
+ var $conn_id = false;
+
+ // }}}
+
+ // {{{ Auth_Container_LDAP() [constructor]
+
+ /**
+ * Constructor of the container class
+ *
+ * @param $params, associative hash with host,port,basedn and userattr key
+ * @return object Returns an error object if something went wrong
+ */
+ function Auth_Container_LDAP($params)
+ {
+ if (false === extension_loaded('ldap')) {
+ return PEAR::raiseError('Auth_Container_LDAP: LDAP Extension not loaded',
+ 41, PEAR_ERROR_DIE);
+ }
+
+ $this->_setDefaults();
+
+ if (is_array($params)) {
+ $this->_parseOptions($params);
+ }
+ }
+
+ // }}}
+ // {{{ _prepare()
+
+ /**
+ * Prepare LDAP connection
+ *
+ * This function checks if we have already opened a connection to
+ * the LDAP server. If that's not the case, a new connection is opened.
+ *
+ * @access private
+ * @return mixed True or a PEAR error object.
+ */
+ function _prepare()
+ {
+ if (!$this->_isValidLink()) {
+ $res = $this->_connect();
+ if (PEAR::isError($res)) {
+ return $res;
+ }
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ _connect()
+
+ /**
+ * Connect to the LDAP server using the global options
+ *
+ * @access private
+ * @return object Returns a PEAR error object if an error occurs.
+ */
+ function _connect()
+ {
+ $this->log('Auth_Container_LDAP::_connect() called.', AUTH_LOG_DEBUG);
+ // connect
+ if (isset($this->options['url']) && $this->options['url'] != '') {
+ $this->log('Connecting with URL', AUTH_LOG_DEBUG);
+ $conn_params = array($this->options['url']);
+ } else {
+ $this->log('Connecting with host:port', AUTH_LOG_DEBUG);
+ $conn_params = array($this->options['host'], $this->options['port']);
+ }
+
+ if (($this->conn_id = @call_user_func_array('ldap_connect', $conn_params)) === false) {
+ $this->log('Connection to server failed.', AUTH_LOG_DEBUG);
+ $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG);
+ return PEAR::raiseError('Auth_Container_LDAP: Could not connect to server.', 41);
+ }
+ $this->log('Successfully connected to server', AUTH_LOG_DEBUG);
+
+ // switch LDAP version
+ if (is_numeric($this->options['version']) && $this->options['version'] > 2) {
+ $this->log("Switching to LDAP version {$this->options['version']}", AUTH_LOG_DEBUG);
+ @ldap_set_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $this->options['version']);
+
+ // start TLS if available
+ if (isset($this->options['start_tls']) && $this->options['start_tls']) {
+ $this->log("Starting TLS session", AUTH_LOG_DEBUG);
+ if (@ldap_start_tls($this->conn_id) === false) {
+ $this->log('Could not start TLS session', AUTH_LOG_DEBUG);
+ $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG);
+ return PEAR::raiseError('Auth_Container_LDAP: Could not start tls.', 41);
+ }
+ }
+ }
+
+ // switch LDAP referrals
+ if (is_bool($this->options['referrals'])) {
+ $this->log("Switching LDAP referrals to " . (($this->options['referrals']) ? 'true' : 'false'), AUTH_LOG_DEBUG);
+ if (@ldap_set_option($this->conn_id, LDAP_OPT_REFERRALS, $this->options['referrals']) === false) {
+ $this->log('Could not change LDAP referrals options', AUTH_LOG_DEBUG);
+ $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG);
+ }
+ }
+
+ // bind with credentials or anonymously
+ if (strlen($this->options['binddn']) && strlen($this->options['bindpw'])) {
+ $this->log('Binding with credentials', AUTH_LOG_DEBUG);
+ $bind_params = array($this->conn_id, $this->options['binddn'], $this->options['bindpw']);
+ } else {
+ $this->log('Binding anonymously', AUTH_LOG_DEBUG);
+ $bind_params = array($this->conn_id);
+ }
+
+ // bind for searching
+ if ((@call_user_func_array('ldap_bind', $bind_params)) === false) {
+ $this->log('Bind failed', AUTH_LOG_DEBUG);
+ $this->log('LDAP ERROR: '.ldap_errno($this->conn_id).': '.ldap_error($this->conn_id), AUTH_LOG_DEBUG);
+ $this->_disconnect();
+ return PEAR::raiseError("Auth_Container_LDAP: Could not bind to LDAP server.", 41);
+ }
+ $this->log('Binding was successful', AUTH_LOG_DEBUG);
+
+ return true;
+ }
+
+ // }}}
+ // {{{ _disconnect()
+
+ /**
+ * Disconnects (unbinds) from ldap server
+ *
+ * @access private
+ */
+ function _disconnect()
+ {
+ $this->log('Auth_Container_LDAP::_disconnect() called.', AUTH_LOG_DEBUG);
+ if ($this->_isValidLink()) {
+ $this->log('disconnecting from server');
+ @ldap_unbind($this->conn_id);
+ }
+ }
+
+ // }}}
+ // {{{ _getBaseDN()
+
+ /**
+ * Tries to find Basedn via namingContext Attribute
+ *
+ * @access private
+ */
+ function _getBaseDN()
+ {
+ $this->log('Auth_Container_LDAP::_getBaseDN() called.', AUTH_LOG_DEBUG);
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ if ($this->options['basedn'] == "" && $this->_isValidLink()) {
+ $this->log("basedn not set, searching via namingContexts.", AUTH_LOG_DEBUG);
+
+ $result_id = @ldap_read($this->conn_id, "", "(objectclass=*)", array("namingContexts"));
+
+ if (@ldap_count_entries($this->conn_id, $result_id) == 1) {
+
+ $this->log("got result for namingContexts", AUTH_LOG_DEBUG);
+
+ $entry_id = @ldap_first_entry($this->conn_id, $result_id);
+ $attrs = @ldap_get_attributes($this->conn_id, $entry_id);
+ $basedn = $attrs['namingContexts'][0];
+
+ if ($basedn != "") {
+ $this->log("result for namingContexts was $basedn", AUTH_LOG_DEBUG);
+ $this->options['basedn'] = $basedn;
+ }
+ }
+ @ldap_free_result($result_id);
+ }
+
+ // if base ist still not set, raise error
+ if ($this->options['basedn'] == "") {
+ return PEAR::raiseError("Auth_Container_LDAP: LDAP search base not specified!", 41);
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ _isValidLink()
+
+ /**
+ * determines whether there is a valid ldap conenction or not
+ *
+ * @accessd private
+ * @return boolean
+ */
+ function _isValidLink()
+ {
+ if (is_resource($this->conn_id)) {
+ if (get_resource_type($this->conn_id) == 'ldap link') {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // }}}
+ // {{{ _setDefaults()
+
+ /**
+ * Set some default options
+ *
+ * @access private
+ */
+ function _setDefaults()
+ {
+ $this->options['url'] = '';
+ $this->options['host'] = 'localhost';
+ $this->options['port'] = '389';
+ $this->options['version'] = 2;
+ $this->options['referrals'] = true;
+ $this->options['binddn'] = '';
+ $this->options['bindpw'] = '';
+ $this->options['basedn'] = '';
+ $this->options['userdn'] = '';
+ $this->options['userscope'] = 'sub';
+ $this->options['userattr'] = 'uid';
+ $this->options['userfilter'] = '(objectClass=posixAccount)';
+ $this->options['attributes'] = array(''); // no attributes
+ $this->options['attrformat'] = 'AUTH'; // returns attribute like other Auth containers
+ $this->options['group'] = '';
+ $this->options['groupdn'] = '';
+ $this->options['groupscope'] = 'sub';
+ $this->options['groupattr'] = 'cn';
+ $this->options['groupfilter'] = '(objectClass=groupOfUniqueNames)';
+ $this->options['memberattr'] = 'uniqueMember';
+ $this->options['memberisdn'] = true;
+ $this->options['start_tls'] = false;
+ $this->options['debug'] = false;
+ $this->options['try_all'] = false; // Try all user ids returned not just the first one
+ }
+
+ // }}}
+ // {{{ _parseOptions()
+
+ /**
+ * Parse options passed to the container class
+ *
+ * @access private
+ * @param array
+ */
+ function _parseOptions($array)
+ {
+ $array = $this->_setV12OptionsToV13($array);
+
+ foreach ($array as $key => $value) {
+ if (array_key_exists($key, $this->options)) {
+ if ($key == 'attributes') {
+ if (is_array($value)) {
+ $this->options[$key] = $value;
+ } else {
+ $this->options[$key] = explode(',', $value);
+ }
+ } else {
+ $this->options[$key] = $value;
+ }
+ }
+ }
+ }
+
+ // }}}
+ // {{{ _setV12OptionsToV13()
+
+ /**
+ * Adapt deprecated options from Auth 1.2 LDAP to Auth 1.3 LDAP
+ *
+ * @author Hugues Peeters <hugues.peeters@claroline.net>
+ * @access private
+ * @param array
+ * @return array
+ */
+ function _setV12OptionsToV13($array)
+ {
+ if (isset($array['useroc']))
+ $array['userfilter'] = "(objectClass=".$array['useroc'].")";
+ if (isset($array['groupoc']))
+ $array['groupfilter'] = "(objectClass=".$array['groupoc'].")";
+ if (isset($array['scope']))
+ $array['userscope'] = $array['scope'];
+
+ return $array;
+ }
+
+ // }}}
+ // {{{ _scope2function()
+
+ /**
+ * Get search function for scope
+ *
+ * @param string scope
+ * @return string ldap search function
+ */
+ function _scope2function($scope)
+ {
+ switch($scope) {
+ case 'one':
+ $function = 'ldap_list';
+ break;
+ case 'base':
+ $function = 'ldap_read';
+ break;
+ default:
+ $function = 'ldap_search';
+ break;
+ }
+ return $function;
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Fetch data from LDAP server
+ *
+ * Searches the LDAP server for the given username/password
+ * combination. Escapes all LDAP meta characters in username
+ * before performing the query.
+ *
+ * @param string Username
+ * @param string Password
+ * @return boolean
+ */
+ function fetchData($username, $password)
+ {
+ $this->log('Auth_Container_LDAP::fetchData() called.', AUTH_LOG_DEBUG);
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ $err = $this->_getBaseDN();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ // UTF8 Encode username for LDAPv3
+ if (@ldap_get_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $ver) && $ver == 3) {
+ $this->log('UTF8 encoding username for LDAPv3', AUTH_LOG_DEBUG);
+ $username = utf8_encode($username);
+ }
+
+ // make search filter
+ $filter = sprintf('(&(%s=%s)%s)',
+ $this->options['userattr'],
+ $this->_quoteFilterString($username),
+ $this->options['userfilter']);
+
+ // make search base dn
+ $search_basedn = $this->options['userdn'];
+ if ($search_basedn != '' && substr($search_basedn, -1) != ',') {
+ $search_basedn .= ',';
+ }
+ $search_basedn .= $this->options['basedn'];
+
+ // attributes
+ $searchAttributes = $this->options['attributes'];
+
+ // make functions params array
+ $func_params = array($this->conn_id, $search_basedn, $filter, $searchAttributes);
+
+ // search function to use
+ $func_name = $this->_scope2function($this->options['userscope']);
+
+ $this->log("Searching with $func_name and filter $filter in $search_basedn", AUTH_LOG_DEBUG);
+
+ // search
+ if (($result_id = @call_user_func_array($func_name, $func_params)) === false) {
+ $this->log('User not found', AUTH_LOG_DEBUG);
+ } elseif (@ldap_count_entries($this->conn_id, $result_id) >= 1) { // did we get some possible results?
+
+ $this->log('User(s) found', AUTH_LOG_DEBUG);
+
+ $first = true;
+ $entry_id = null;
+
+ do {
+
+ // then get the user dn
+ if ($first) {
+ $entry_id = @ldap_first_entry($this->conn_id, $result_id);
+ $first = false;
+ } else {
+ $entry_id = @ldap_next_entry($this->conn_id, $entry_id);
+ if ($entry_id === false)
+ break;
+ }
+ $user_dn = @ldap_get_dn($this->conn_id, $entry_id);
+
+ // as the dn is not fetched as an attribute, we save it anyway
+ if (is_array($searchAttributes) && in_array('dn', $searchAttributes)) {
+ $this->log('Saving DN to AuthData', AUTH_LOG_DEBUG);
+ $this->_auth_obj->setAuthData('dn', $user_dn);
+ }
+
+ // fetch attributes
+ if ($attributes = @ldap_get_attributes($this->conn_id, $entry_id)) {
+
+ if (is_array($attributes) && isset($attributes['count']) &&
+ $attributes['count'] > 0) {
+
+ // ldap_get_attributes() returns a specific multi dimensional array
+ // format containing all the attributes and where each array starts
+ // with a 'count' element providing the number of attributes in the
+ // entry, or the number of values for attribute. For compatibility
+ // reasons, it remains the default format returned by LDAP container
+ // setAuthData().
+ // The code below optionally returns attributes in another format,
+ // more compliant with other Auth containers, where each attribute
+ // element are directly set in the 'authData' list. This option is
+ // enabled by setting 'attrformat' to
+ // 'AUTH' in the 'options' array.
+ // eg. $this->options['attrformat'] = 'AUTH'
+
+ if ( strtoupper($this->options['attrformat']) == 'AUTH' ) {
+ $this->log('Saving attributes to Auth data in AUTH format', AUTH_LOG_DEBUG);
+ unset ($attributes['count']);
+ foreach ($attributes as $attributeName => $attributeValue ) {
+ if (is_int($attributeName)) continue;
+ if (is_array($attributeValue) && isset($attributeValue['count'])) {
+ unset ($attributeValue['count']);
+ }
+ if (count($attributeValue)<=1) $attributeValue = $attributeValue[0];
+ $this->log('Storing additional field: '.$attributeName, AUTH_LOG_DEBUG);
+ $this->_auth_obj->setAuthData($attributeName, $attributeValue);
+ }
+ }
+ else
+ {
+ $this->log('Saving attributes to Auth data in LDAP format', AUTH_LOG_DEBUG);
+ $this->_auth_obj->setAuthData('attributes', $attributes);
+ }
+ }
+ }
+ @ldap_free_result($result_id);
+
+ // need to catch an empty password as openldap seems to return TRUE
+ // if anonymous binding is allowed
+ if ($password != "") {
+ $this->log("Bind as $user_dn", AUTH_LOG_DEBUG);
+
+ // try binding as this user with the supplied password
+ if (@ldap_bind($this->conn_id, $user_dn, $password)) {
+ $this->log('Bind successful', AUTH_LOG_DEBUG);
+
+ // check group if appropiate
+ if (strlen($this->options['group'])) {
+ // decide whether memberattr value is a dn or the username
+ $this->log('Checking group membership', AUTH_LOG_DEBUG);
+ $return = $this->checkGroup(($this->options['memberisdn']) ? $user_dn : $username);
+ $this->_disconnect();
+ return $return;
+ } else {
+ $this->log('Authenticated', AUTH_LOG_DEBUG);
+ $this->_disconnect();
+ return true; // user authenticated
+ } // checkGroup
+ } // bind
+ } // non-empty password
+ } while ($this->options['try_all'] == true); // interate through entries
+ } // get results
+ // default
+ $this->log('NOT authenticated!', AUTH_LOG_DEBUG);
+ $this->_disconnect();
+ return false;
+ }
+
+ // }}}
+ // {{{ checkGroup()
+
+ /**
+ * Validate group membership
+ *
+ * Searches the LDAP server for group membership of the
+ * supplied username. Quotes all LDAP filter meta characters in
+ * the user name before querying the LDAP server.
+ *
+ * @param string Distinguished Name of the authenticated User
+ * @return boolean
+ */
+ function checkGroup($user)
+ {
+ $this->log('Auth_Container_LDAP::checkGroup() called.', AUTH_LOG_DEBUG);
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ // make filter
+ $filter = sprintf('(&(%s=%s)(%s=%s)%s)',
+ $this->options['groupattr'],
+ $this->options['group'],
+ $this->options['memberattr'],
+ $this->_quoteFilterString($user),
+ $this->options['groupfilter']);
+
+ // make search base dn
+ $search_basedn = $this->options['groupdn'];
+ if ($search_basedn != '' && substr($search_basedn, -1) != ',') {
+ $search_basedn .= ',';
+ }
+ $search_basedn .= $this->options['basedn'];
+
+ $func_params = array($this->conn_id, $search_basedn, $filter,
+ array($this->options['memberattr']));
+ $func_name = $this->_scope2function($this->options['groupscope']);
+
+ $this->log("Searching with $func_name and filter $filter in $search_basedn", AUTH_LOG_DEBUG);
+
+ // search
+ if (($result_id = @call_user_func_array($func_name, $func_params)) != false) {
+ if (@ldap_count_entries($this->conn_id, $result_id) == 1) {
+ @ldap_free_result($result_id);
+ $this->log('User is member of group', AUTH_LOG_DEBUG);
+ return true;
+ }
+ }
+ // default
+ $this->log('User is NOT member of group', AUTH_LOG_DEBUG);
+ return false;
+ }
+
+ // }}}
+ // {{{ _quoteFilterString()
+
+ /**
+ * Escapes LDAP filter special characters as defined in RFC 2254.
+ *
+ * @access private
+ * @param string Filter String
+ */
+ function _quoteFilterString($filter_str)
+ {
+ $metas = array( '\\', '*', '(', ')', "\x00");
+ $quoted_metas = array('\\\\', '\*', '\(', '\)', "\\\x00");
+ return str_replace($metas, $quoted_metas, $filter_str);
+ }
+
+ // }}}
+
+}
+
+?>
diff --git a/includes/pear/Auth/Container/MDB.php b/includes/pear/Auth/Container/MDB.php
new file mode 100644
index 0000000..019a122
--- /dev/null
+++ b/includes/pear/Auth/Container/MDB.php
@@ -0,0 +1,625 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for use against PEAR MDB
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Lorenzo Alberton <l.alberton@quipo.it>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: MDB.php 256753 2008-04-04 07:57:02Z aashley $
+ * @link http://pear.php.net/package/Auth
+ * @since File available since Release 1.2.3
+ */
+
+/**
+ * Include Auth_Container base class
+ */
+require_once 'Auth/Container.php';
+/**
+ * Include PEAR MDB package
+ */
+require_once 'MDB.php';
+
+/**
+ * Storage driver for fetching login data from a database
+ *
+ * This storage driver can use all databases which are supported
+ * by the PEAR MDB abstraction layer to fetch login data.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Lorenzo Alberton <l.alberton@quipo.it>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 256753 $
+ * @link http://pear.php.net/package/Auth
+ * @since Class available since Release 1.2.3
+ */
+class Auth_Container_MDB extends Auth_Container
+{
+
+ // {{{ properties
+
+ /**
+ * Additional options for the storage container
+ * @var array
+ */
+ var $options = array();
+
+ /**
+ * MDB object
+ * @var object
+ */
+ var $db = null;
+ var $dsn = '';
+
+ /**
+ * User that is currently selected from the DB.
+ * @var string
+ */
+ var $activeUser = '';
+
+ // }}}
+ // {{{ Auth_Container_MDB() [constructor]
+
+ /**
+ * Constructor of the container class
+ *
+ * Initate connection to the database via PEAR::MDB
+ *
+ * @param string Connection data or MDB object
+ * @return object Returns an error object if something went wrong
+ */
+ function Auth_Container_MDB($dsn)
+ {
+ $this->_setDefaults();
+
+ if (is_array($dsn)) {
+ $this->_parseOptions($dsn);
+ if (empty($this->options['dsn'])) {
+ PEAR::raiseError('No connection parameters specified!');
+ }
+ } else {
+ $this->options['dsn'] = $dsn;
+ }
+ }
+
+ // }}}
+ // {{{ _connect()
+
+ /**
+ * Connect to database by using the given DSN string
+ *
+ * @access private
+ * @param mixed DSN string | array | mdb object
+ * @return mixed Object on error, otherwise bool
+ */
+ function _connect($dsn)
+ {
+ $this->log('Auth_Container_MDB::_connect() called.', AUTH_LOG_DEBUG);
+ if (is_string($dsn) || is_array($dsn)) {
+ $this->db =& MDB::connect($dsn, $this->options['db_options']);
+ } elseif (is_subclass_of($dsn, 'mdb_common')) {
+ $this->db = $dsn;
+ } elseif (is_object($dsn) && MDB::isError($dsn)) {
+ return PEAR::raiseError($dsn->getMessage(), $dsn->code);
+ } else {
+ return PEAR::raiseError('The given dsn was not valid in file ' . __FILE__ . ' at line ' . __LINE__,
+ 41,
+ PEAR_ERROR_RETURN,
+ null,
+ null
+ );
+
+ }
+
+ if (MDB::isError($this->db) || PEAR::isError($this->db)) {
+ return PEAR::raiseError($this->db->getMessage(), $this->db->code);
+ }
+
+ if ($this->options['auto_quote']) {
+ if (strpos('.', $this->options['table']) === false) {
+ $this->options['final_table'] = $this->db->quoteIdentifier($this->options['table']);
+ } else {
+ $t = explode('.', $this->options['table']);
+ for ($i = 0, $count = count($t); $i < $count; $i++)
+ $t[$i] = $this->db->quoteIdentifier($t[$i]);
+ $this->options['final_table'] = implode('.', $t);
+ }
+ $this->options['final_usernamecol'] = $this->db->quoteIdentifier($this->options['usernamecol']);
+ $this->options['final_passwordcol'] = $this->db->quoteIdentifier($this->options['passwordcol']);
+ } else {
+ $this->options['final_table'] = $this->options['table'];
+ $this->options['final_usernamecol'] = $this->options['usernamecol'];
+ $this->options['final_passwordcol'] = $this->options['passwordcol'];
+ }
+
+ return true;
+ }
+
+ // }}}
+ // {{{ _prepare()
+
+ /**
+ * Prepare database connection
+ *
+ * This function checks if we have already opened a connection to
+ * the database. If that's not the case, a new connection is opened.
+ *
+ * @access private
+ * @return mixed True or a MDB error object.
+ */
+ function _prepare()
+ {
+ if (is_subclass_of($this->db, 'mdb_common')) {
+ return true;
+ }
+ return $this->_connect($this->options['dsn']);
+ }
+
+ // }}}
+ // {{{ query()
+
+ /**
+ * Prepare query to the database
+ *
+ * This function checks if we have already opened a connection to
+ * the database. If that's not the case, a new connection is opened.
+ * After that the query is passed to the database.
+ *
+ * @access public
+ * @param string Query string
+ * @return mixed a MDB_result object or MDB_OK on success, a MDB
+ * or PEAR error on failure
+ */
+ function query($query)
+ {
+ $this->log('Auth_Container_MDB::query() called.', AUTH_LOG_DEBUG);
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return $err;
+ }
+ return $this->db->query($query);
+ }
+
+ // }}}
+ // {{{ _setDefaults()
+
+ /**
+ * Set some default options
+ *
+ * @access private
+ * @return void
+ */
+ function _setDefaults()
+ {
+ $this->options['table'] = 'auth';
+ $this->options['usernamecol'] = 'username';
+ $this->options['passwordcol'] = 'password';
+ $this->options['dsn'] = '';
+ $this->options['db_fields'] = '';
+ $this->options['cryptType'] = 'md5';
+ $this->options['db_options'] = array();
+ $this->options['db_where'] = '';
+ $this->options['auto_quote'] = true;
+ }
+
+ // }}}
+ // {{{ _parseOptions()
+
+ /**
+ * Parse options passed to the container class
+ *
+ * @access private
+ * @param array
+ */
+ function _parseOptions($array)
+ {
+ foreach ($array as $key => $value) {
+ if (isset($this->options[$key])) {
+ $this->options[$key] = $value;
+ }
+ }
+ }
+
+ // }}}
+ // {{{ _quoteDBFields()
+
+ /**
+ * Quote the db_fields option to avoid the possibility of SQL injection.
+ *
+ * @access private
+ * @return string A properly quoted string that can be concatenated into a
+ * SELECT clause.
+ */
+ function _quoteDBFields()
+ {
+ if (isset($this->options['db_fields'])) {
+ if (is_array($this->options['db_fields'])) {
+ if ($this->options['auto_quote']) {
+ $fields = array();
+ foreach ($this->options['db_fields'] as $field) {
+ $fields[] = $this->db->quoteIdentifier($field);
+ }
+ return implode(', ', $fields);
+ } else {
+ return implode(', ', $this->options['db_fields']);
+ }
+ } else {
+ if (strlen($this->options['db_fields']) > 0) {
+ if ($this->options['auto_quote']) {
+ return $this->db->quoteIdentifier($this->options['db_fields']);
+ } else {
+ return $this->options['db_fields'];
+ }
+ }
+ }
+ }
+
+ return '';
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Get user information from database
+ *
+ * This function uses the given username to fetch
+ * the corresponding login data from the database
+ * table. If an account that matches the passed username
+ * and password is found, the function returns true.
+ * Otherwise it returns false.
+ *
+ * @param string Username
+ * @param string Password
+ * @param boolean If true password is secured using a md5 hash
+ * the frontend and auth are responsible for making sure the container supports
+ * challenge response password authentication
+ * @return mixed Error object or boolean
+ */
+ function fetchData($username, $password, $isChallengeResponse=false)
+ {
+ $this->log('Auth_Container_MDB::fetchData() called.', AUTH_LOG_DEBUG);
+ // Prepare for a database query
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ //Check if db_fields contains a *, if so assume all columns are selected
+ if (is_string($this->options['db_fields'])
+ && strstr($this->options['db_fields'], '*')) {
+ $sql_from = '*';
+ } else {
+ $sql_from = $this->options['final_usernamecol'].
+ ", ".$this->options['final_passwordcol'];
+
+ if (strlen($fields = $this->_quoteDBFields()) > 0) {
+ $sql_from .= ', '.$fields;
+ }
+ }
+
+ $query = sprintf("SELECT %s FROM %s WHERE %s = %s",
+ $sql_from,
+ $this->options['final_table'],
+ $this->options['final_usernamecol'],
+ $this->db->getTextValue($username)
+ );
+
+ // check if there is an optional parameter db_where
+ if ($this->options['db_where'] != '') {
+ // there is one, so add it to the query
+ $query .= " AND ".$this->options['db_where'];
+ }
+
+ $this->log('Running SQL against MDB: '.$query, AUTH_LOG_DEBUG);
+
+ $res = $this->db->getRow($query, null, null, null, MDB_FETCHMODE_ASSOC);
+
+ if (MDB::isError($res) || PEAR::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->getCode());
+ }
+ if (!is_array($res)) {
+ $this->activeUser = '';
+ return false;
+ }
+
+ // Perform trimming here before the hashing
+ $password = trim($password, "\r\n");
+ $res[$this->options['passwordcol']] = trim($res[$this->options['passwordcol']], "\r\n");
+
+ // If using Challenge Response md5 the pass with the secret
+ if ($isChallengeResponse) {
+ $res[$this->options['passwordcol']] =
+ md5($res[$this->options['passwordcol']].$this->_auth_obj->session['loginchallenege']);
+ // UGLY cannot avoid without modifying verifyPassword
+ if ($this->options['cryptType'] == 'md5') {
+ $res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]);
+ }
+ }
+
+ if ($this->verifyPassword($password,
+ $res[$this->options['passwordcol']],
+ $this->options['cryptType'])) {
+ // Store additional field values in the session
+ foreach ($res as $key => $value) {
+ if ($key == $this->options['passwordcol'] ||
+ $key == $this->options['usernamecol']) {
+ continue;
+ }
+
+ $this->log('Storing additional field: '.$key, AUTH_LOG_DEBUG);
+ // Use reference to the auth object if exists
+ // This is because the auth session variable can change so a static
+ // call to setAuthData does not make sense
+ $this->_auth_obj->setAuthData($key, $value);
+ }
+ return true;
+ }
+
+ $this->activeUser = $res[$this->options['usernamecol']];
+ return false;
+ }
+
+ // }}}
+ // {{{ listUsers()
+
+ /**
+ * Returns a list of users from the container
+ *
+ * @return mixed array|PEAR_Error
+ * @access public
+ */
+ function listUsers()
+ {
+ $this->log('Auth_Container_MDB::listUsers() called.', AUTH_LOG_DEBUG);
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ $retVal = array();
+
+ //Check if db_fields contains a *, if so assume all columns are selected
+ if ( is_string($this->options['db_fields'])
+ && strstr($this->options['db_fields'], '*')) {
+ $sql_from = '*';
+ } else {
+ $sql_from = $this->options['final_usernamecol']
+ .', '.$this->options['final_passwordcol'];
+
+ if (strlen($fields = $this->_quoteDBFields()) > 0) {
+ $sql_from .= ', '.$fields;
+ }
+ }
+
+ $query = sprintf('SELECT %s FROM %s',
+ $sql_from,
+ $this->options['final_table']
+ );
+
+ // check if there is an optional parameter db_where
+ if ($this->options['db_where'] != '') {
+ // there is one, so add it to the query
+ $query .= " WHERE ".$this->options['db_where'];
+ }
+
+ $this->log('Running SQL against MDB: '.$query, AUTH_LOG_DEBUG);
+
+ $res = $this->db->getAll($query, null, null, null, MDB_FETCHMODE_ASSOC);
+
+ if (MDB::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->getCode());
+ } else {
+ foreach ($res as $user) {
+ $user['username'] = $user[$this->options['usernamecol']];
+ $retVal[] = $user;
+ }
+ }
+ $this->log('Found '.count($retVal).' users.', AUTH_LOG_DEBUG);
+ return $retVal;
+ }
+
+ // }}}
+ // {{{ addUser()
+
+ /**
+ * Add user to the storage container
+ *
+ * @access public
+ * @param string Username
+ * @param string Password
+ * @param mixed Additional information that are stored in the DB
+ *
+ * @return mixed True on success, otherwise error object
+ */
+ function addUser($username, $password, $additional = "")
+ {
+ $this->log('Auth_Container_MDB::addUser() called.', AUTH_LOG_DEBUG);
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ if (isset($this->options['cryptType']) && $this->options['cryptType'] == 'none') {
+ $cryptFunction = 'strval';
+ } elseif (isset($this->options['cryptType']) && function_exists($this->options['cryptType'])) {
+ $cryptFunction = $this->options['cryptType'];
+ } else {
+ $cryptFunction = 'md5';
+ }
+
+ $password = $cryptFunction($password);
+
+ $additional_key = '';
+ $additional_value = '';
+
+ if (is_array($additional)) {
+ foreach ($additional as $key => $value) {
+ if ($this->options['auto_quote']) {
+ $additional_key .= ', ' . $this->db->quoteIdentifier($key);
+ } else {
+ $additional_key .= ', ' . $key;
+ }
+ $additional_value .= ', ' . $this->db->getTextValue($value);
+ }
+ }
+
+ $query = sprintf("INSERT INTO %s (%s, %s%s) VALUES (%s, %s%s)",
+ $this->options['final_table'],
+ $this->options['final_usernamecol'],
+ $this->options['final_passwordcol'],
+ $additional_key,
+ $this->db->getTextValue($username),
+ $this->db->getTextValue($password),
+ $additional_value
+ );
+
+ $this->log('Running SQL against MDB: '.$query, AUTH_LOG_DEBUG);
+
+ $res = $this->query($query);
+
+ if (MDB::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->code);
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ removeUser()
+
+ /**
+ * Remove user from the storage container
+ *
+ * @access public
+ * @param string Username
+ *
+ * @return mixed True on success, otherwise error object
+ */
+ function removeUser($username)
+ {
+ $this->log('Auth_Container_MDB::removeUser() called.', AUTH_LOG_DEBUG);
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ $query = sprintf("DELETE FROM %s WHERE %s = %s",
+ $this->options['final_table'],
+ $this->options['final_usernamecol'],
+ $this->db->getTextValue($username)
+ );
+
+ // check if there is an optional parameter db_where
+ if ($this->options['db_where'] != '') {
+ // there is one, so add it to the query
+ $query .= " AND ".$this->options['db_where'];
+ }
+
+ $this->log('Running SQL against MDB: '.$query, AUTH_LOG_DEBUG);
+
+ $res = $this->query($query);
+
+ if (MDB::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->code);
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ changePassword()
+
+ /**
+ * Change password for user in the storage container
+ *
+ * @param string Username
+ * @param string The new password (plain text)
+ */
+ function changePassword($username, $password)
+ {
+ $this->log('Auth_Container_MDB::changePassword() called.', AUTH_LOG_DEBUG);
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ if (isset($this->options['cryptType']) && $this->options['cryptType'] == 'none') {
+ $cryptFunction = 'strval';
+ } elseif (isset($this->options['cryptType']) && function_exists($this->options['cryptType'])) {
+ $cryptFunction = $this->options['cryptType'];
+ } else {
+ $cryptFunction = 'md5';
+ }
+
+ $password = $cryptFunction($password);
+
+ $query = sprintf("UPDATE %s SET %s = %s WHERE %s = %s",
+ $this->options['final_table'],
+ $this->options['final_passwordcol'],
+ $this->db->getTextValue($password),
+ $this->options['final_usernamecol'],
+ $this->db->getTextValue($username)
+ );
+
+ // check if there is an optional parameter db_where
+ if ($this->options['db_where'] != '') {
+ // there is one, so add it to the query
+ $query .= " AND ".$this->options['db_where'];
+ }
+
+ $this->log('Running SQL against MDB: '.$query, AUTH_LOG_DEBUG);
+
+ $res = $this->query($query);
+
+ if (MDB::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->code);
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ supportsChallengeResponse()
+
+ /**
+ * Determine if this container supports
+ * password authentication with challenge response
+ *
+ * @return bool
+ * @access public
+ */
+ function supportsChallengeResponse()
+ {
+ return in_array($this->options['cryptType'], array('md5', 'none', ''));
+ }
+
+ // }}}
+ // {{{ getCryptType()
+
+ /**
+ * Returns the selected crypt type for this container
+ *
+ * @return string Function used to crypt the password
+ */
+ function getCryptType()
+ {
+ return $this->options['cryptType'];
+ }
+
+ // }}}
+
+}
+?>
diff --git a/includes/pear/Auth/Container/MDB2.php b/includes/pear/Auth/Container/MDB2.php
new file mode 100644
index 0000000..a3c26b7
--- /dev/null
+++ b/includes/pear/Auth/Container/MDB2.php
@@ -0,0 +1,624 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for use against PEAR MDB2
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Lorenzo Alberton <l.alberton@quipo.it>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: MDB2.php 256753 2008-04-04 07:57:02Z aashley $
+ * @link http://pear.php.net/package/Auth
+ * @since File available since Release 1.3.0
+ */
+
+/**
+ * Include Auth_Container base class
+ */
+require_once 'Auth/Container.php';
+/**
+ * Include PEAR MDB2 package
+ */
+require_once 'MDB2.php';
+
+/**
+ * Storage driver for fetching login data from a database
+ *
+ * This storage driver can use all databases which are supported
+ * by the PEAR MDB2 abstraction layer to fetch login data.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Lorenzo Alberton <l.alberton@quipo.it>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 256753 $
+ * @link http://pear.php.net/package/Auth
+ * @since Class available since Release 1.3.0
+ */
+class Auth_Container_MDB2 extends Auth_Container
+{
+
+ // {{{ properties
+
+ /**
+ * Additional options for the storage container
+ * @var array
+ */
+ var $options = array();
+
+ /**
+ * MDB object
+ * @var object
+ */
+ var $db = null;
+ var $dsn = '';
+
+ /**
+ * User that is currently selected from the DB.
+ * @var string
+ */
+ var $activeUser = '';
+
+ // }}}
+ // {{{ Auth_Container_MDB2() [constructor]
+
+ /**
+ * Constructor of the container class
+ *
+ * Initate connection to the database via PEAR::MDB2
+ *
+ * @param string Connection data or MDB2 object
+ * @return object Returns an error object if something went wrong
+ */
+ function Auth_Container_MDB2($dsn)
+ {
+ $this->_setDefaults();
+
+ if (is_array($dsn)) {
+ $this->_parseOptions($dsn);
+ if (empty($this->options['dsn'])) {
+ PEAR::raiseError('No connection parameters specified!');
+ }
+ } else {
+ $this->options['dsn'] = $dsn;
+ }
+ }
+
+ // }}}
+ // {{{ _connect()
+
+ /**
+ * Connect to database by using the given DSN string
+ *
+ * @access private
+ * @param mixed DSN string | array | mdb object
+ * @return mixed Object on error, otherwise bool
+ */
+ function _connect($dsn)
+ {
+ $this->log('Auth_Container_MDB2::_connect() called.', AUTH_LOG_DEBUG);
+ if (is_string($dsn) || is_array($dsn)) {
+ $this->db =& MDB2::connect($dsn, $this->options['db_options']);
+ } elseif (is_subclass_of($dsn, 'MDB2_Driver_Common')) {
+ $this->db = $dsn;
+ } elseif (is_object($dsn) && MDB2::isError($dsn)) {
+ return PEAR::raiseError($dsn->getMessage(), $dsn->code);
+ } else {
+ return PEAR::raiseError('The given dsn was not valid in file ' . __FILE__ . ' at line ' . __LINE__,
+ 41,
+ PEAR_ERROR_RETURN,
+ null,
+ null
+ );
+
+ }
+
+ if (MDB2::isError($this->db) || PEAR::isError($this->db)) {
+ return PEAR::raiseError($this->db->getMessage(), $this->db->code);
+ }
+
+ if ($this->options['auto_quote']) {
+ if (strpos('.', $this->options['table']) === false) {
+ $this->options['final_table'] = $this->db->quoteIdentifier($this->options['table'], true);
+ } else {
+ $t = explode('.', $this->options['table']);
+ for ($i = 0, $count = count($t); $i < $count; $i++)
+ $t[$i] = $this->db->quoteIdentifier($t[$i], true);
+ $this->options['final_table'] = implode('.', $t);
+ }
+ $this->options['final_usernamecol'] = $this->db->quoteIdentifier($this->options['usernamecol'], true);
+ $this->options['final_passwordcol'] = $this->db->quoteIdentifier($this->options['passwordcol'], true);
+ } else {
+ $this->options['final_table'] = $this->options['table'];
+ $this->options['final_usernamecol'] = $this->options['usernamecol'];
+ $this->options['final_passwordcol'] = $this->options['passwordcol'];
+ }
+
+ return true;
+ }
+
+ // }}}
+ // {{{ _prepare()
+
+ /**
+ * Prepare database connection
+ *
+ * This function checks if we have already opened a connection to
+ * the database. If that's not the case, a new connection is opened.
+ *
+ * @access private
+ * @return mixed True or a MDB error object.
+ */
+ function _prepare()
+ {
+ if (is_subclass_of($this->db, 'MDB2_Driver_Common')) {
+ return true;
+ }
+ return $this->_connect($this->options['dsn']);
+ }
+
+ // }}}
+ // {{{ query()
+
+ /**
+ * Prepare query to the database
+ *
+ * This function checks if we have already opened a connection to
+ * the database. If that's not the case, a new connection is opened.
+ * After that the query is passed to the database.
+ *
+ * @access public
+ * @param string Query string
+ * @return mixed a MDB_result object or MDB_OK on success, a MDB
+ * or PEAR error on failure
+ */
+ function query($query)
+ {
+ $this->log('Auth_Container_MDB2::query() called.', AUTH_LOG_DEBUG);
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return $err;
+ }
+ return $this->db->exec($query);
+ }
+
+ // }}}
+ // {{{ _setDefaults()
+
+ /**
+ * Set some default options
+ *
+ * @access private
+ * @return void
+ */
+ function _setDefaults()
+ {
+ $this->options['table'] = 'auth';
+ $this->options['usernamecol'] = 'username';
+ $this->options['passwordcol'] = 'password';
+ $this->options['dsn'] = '';
+ $this->options['db_fields'] = '';
+ $this->options['cryptType'] = 'md5';
+ $this->options['db_options'] = array();
+ $this->options['db_where'] = '';
+ $this->options['auto_quote'] = true;
+ }
+
+ // }}}
+ // {{{ _parseOptions()
+
+ /**
+ * Parse options passed to the container class
+ *
+ * @access private
+ * @param array
+ */
+ function _parseOptions($array)
+ {
+ foreach ($array as $key => $value) {
+ if (isset($this->options[$key])) {
+ $this->options[$key] = $value;
+ }
+ }
+ }
+
+ // }}}
+ // {{{ _quoteDBFields()
+
+ /**
+ * Quote the db_fields option to avoid the possibility of SQL injection.
+ *
+ * @access private
+ * @return string A properly quoted string that can be concatenated into a
+ * SELECT clause.
+ */
+ function _quoteDBFields()
+ {
+ if (isset($this->options['db_fields'])) {
+ if (is_array($this->options['db_fields'])) {
+ if ($this->options['auto_quote']) {
+ $fields = array();
+ foreach ($this->options['db_fields'] as $field) {
+ $fields[] = $this->db->quoteIdentifier($field, true);
+ }
+ return implode(', ', $fields);
+ } else {
+ return implode(', ', $this->options['db_fields']);
+ }
+ } else {
+ if (strlen($this->options['db_fields']) > 0) {
+ if ($this->options['auto_quote']) {
+ return $this->db->quoteIdentifier($this->options['db_fields'], true);
+ } else {
+ return $this->options['db_fields'];
+ }
+ }
+ }
+ }
+
+ return '';
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Get user information from database
+ *
+ * This function uses the given username to fetch
+ * the corresponding login data from the database
+ * table. If an account that matches the passed username
+ * and password is found, the function returns true.
+ * Otherwise it returns false.
+ *
+ * @param string Username
+ * @param string Password
+ * @param boolean If true password is secured using a md5 hash
+ * the frontend and auth are responsible for making sure the container supports
+ * challenge response password authentication
+ * @return mixed Error object or boolean
+ */
+ function fetchData($username, $password, $isChallengeResponse=false)
+ {
+ $this->log('Auth_Container_MDB2::fetchData() called.', AUTH_LOG_DEBUG);
+ // Prepare for a database query
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ //Check if db_fields contains a *, if so assume all columns are selected
+ if (is_string($this->options['db_fields'])
+ && strstr($this->options['db_fields'], '*')) {
+ $sql_from = '*';
+ } else {
+ $sql_from = $this->options['final_usernamecol'].
+ ", ".$this->options['final_passwordcol'];
+
+ if (strlen($fields = $this->_quoteDBFields()) > 0) {
+ $sql_from .= ', '.$fields;
+ }
+ }
+ $query = sprintf("SELECT %s FROM %s WHERE %s = %s",
+ $sql_from,
+ $this->options['final_table'],
+ $this->options['final_usernamecol'],
+ $this->db->quote($username, 'text')
+ );
+
+ // check if there is an optional parameter db_where
+ if ($this->options['db_where'] != '') {
+ // there is one, so add it to the query
+ $query .= " AND ".$this->options['db_where'];
+ }
+
+ $this->log('Running SQL against MDB2: '.$query, AUTH_LOG_DEBUG);
+
+ $res = $this->db->queryRow($query, null, MDB2_FETCHMODE_ASSOC);
+ if (MDB2::isError($res) || PEAR::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->getCode());
+ }
+ if (!is_array($res)) {
+ $this->activeUser = '';
+ return false;
+ }
+
+ // Perform trimming here before the hashing
+ $password = trim($password, "\r\n");
+ $res[$this->options['passwordcol']] = trim($res[$this->options['passwordcol']], "\r\n");
+ // If using Challenge Response md5 the pass with the secret
+ if ($isChallengeResponse) {
+ $res[$this->options['passwordcol']] =
+ md5($res[$this->options['passwordcol']].$this->_auth_obj->session['loginchallenege']);
+ // UGLY cannot avoid without modifying verifyPassword
+ if ($this->options['cryptType'] == 'md5') {
+ $res[$this->options['passwordcol']] = md5($res[$this->options['passwordcol']]);
+ }
+ }
+ if ($this->verifyPassword($password,
+ $res[$this->options['passwordcol']],
+ $this->options['cryptType'])) {
+ // Store additional field values in the session
+ foreach ($res as $key => $value) {
+ if ($key == $this->options['passwordcol'] ||
+ $key == $this->options['usernamecol']) {
+ continue;
+ }
+
+ $this->log('Storing additional field: '.$key, AUTH_LOG_DEBUG);
+
+ // Use reference to the auth object if exists
+ // This is because the auth session variable can change so a static call to setAuthData does not make sense
+ $this->_auth_obj->setAuthData($key, $value);
+ }
+ return true;
+ }
+
+ $this->activeUser = $res[$this->options['usernamecol']];
+ return false;
+ }
+
+ // }}}
+ // {{{ listUsers()
+
+ /**
+ * Returns a list of users from the container
+ *
+ * @return mixed array|PEAR_Error
+ * @access public
+ */
+ function listUsers()
+ {
+ $this->log('Auth_Container_MDB2::listUsers() called.', AUTH_LOG_DEBUG);
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ $retVal = array();
+
+ //Check if db_fields contains a *, if so assume all columns are selected
+ if ( is_string($this->options['db_fields'])
+ && strstr($this->options['db_fields'], '*')) {
+ $sql_from = '*';
+ } else {
+ $sql_from = $this->options['final_usernamecol'].
+ ", ".$this->options['final_passwordcol'];
+
+ if (strlen($fields = $this->_quoteDBFields()) > 0) {
+ $sql_from .= ', '.$fields;
+ }
+ }
+
+ $query = sprintf('SELECT %s FROM %s',
+ $sql_from,
+ $this->options['final_table']
+ );
+
+ // check if there is an optional parameter db_where
+ if ($this->options['db_where'] != '') {
+ // there is one, so add it to the query
+ $query .= " WHERE ".$this->options['db_where'];
+ }
+
+ $this->log('Running SQL against MDB2: '.$query, AUTH_LOG_DEBUG);
+
+ $res = $this->db->queryAll($query, null, MDB2_FETCHMODE_ASSOC);
+ if (MDB2::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->getCode());
+ } else {
+ foreach ($res as $user) {
+ $user['username'] = $user[$this->options['usernamecol']];
+ $retVal[] = $user;
+ }
+ }
+ $this->log('Found '.count($retVal).' users.', AUTH_LOG_DEBUG);
+ return $retVal;
+ }
+
+ // }}}
+ // {{{ addUser()
+
+ /**
+ * Add user to the storage container
+ *
+ * @access public
+ * @param string Username
+ * @param string Password
+ * @param mixed Additional information that are stored in the DB
+ *
+ * @return mixed True on success, otherwise error object
+ */
+ function addUser($username, $password, $additional = "")
+ {
+ $this->log('Auth_Container_MDB2::addUser() called.', AUTH_LOG_DEBUG);
+
+ // Prepare for a database query
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ if (isset($this->options['cryptType']) && $this->options['cryptType'] == 'none') {
+ $cryptFunction = 'strval';
+ } elseif (isset($this->options['cryptType']) && function_exists($this->options['cryptType'])) {
+ $cryptFunction = $this->options['cryptType'];
+ } else {
+ $cryptFunction = 'md5';
+ }
+
+ $password = $cryptFunction($password);
+
+ $additional_key = '';
+ $additional_value = '';
+
+ if (is_array($additional)) {
+ foreach ($additional as $key => $value) {
+ if ($this->options['auto_quote']) {
+ $additional_key .= ', ' . $this->db->quoteIdentifier($key, true);
+ } else {
+ $additional_key .= ', ' . $key;
+ }
+ $additional_value .= ', ' . $this->db->quote($value, 'text');
+ }
+ }
+
+ $query = sprintf("INSERT INTO %s (%s, %s%s) VALUES (%s, %s%s)",
+ $this->options['final_table'],
+ $this->options['final_usernamecol'],
+ $this->options['final_passwordcol'],
+ $additional_key,
+ $this->db->quote($username, 'text'),
+ $this->db->quote($password, 'text'),
+ $additional_value
+ );
+
+ $this->log('Running SQL against MDB2: '.$query, AUTH_LOG_DEBUG);
+
+ $res = $this->query($query);
+
+ if (MDB2::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->code);
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ removeUser()
+
+ /**
+ * Remove user from the storage container
+ *
+ * @access public
+ * @param string Username
+ *
+ * @return mixed True on success, otherwise error object
+ */
+ function removeUser($username)
+ {
+ $this->log('Auth_Container_MDB2::removeUser() called.', AUTH_LOG_DEBUG);
+ // Prepare for a database query
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ $query = sprintf("DELETE FROM %s WHERE %s = %s",
+ $this->options['final_table'],
+ $this->options['final_usernamecol'],
+ $this->db->quote($username, 'text')
+ );
+
+ // check if there is an optional parameter db_where
+ if ($this->options['db_where'] != '') {
+ // there is one, so add it to the query
+ $query .= " AND ".$this->options['db_where'];
+ }
+
+ $this->log('Running SQL against MDB2: '.$query, AUTH_LOG_DEBUG);
+
+ $res = $this->query($query);
+
+ if (MDB2::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->code);
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ changePassword()
+
+ /**
+ * Change password for user in the storage container
+ *
+ * @param string Username
+ * @param string The new password (plain text)
+ */
+ function changePassword($username, $password)
+ {
+ $this->log('Auth_Container_MDB2::changePassword() called.', AUTH_LOG_DEBUG);
+ // Prepare for a database query
+ $err = $this->_prepare();
+ if ($err !== true) {
+ return PEAR::raiseError($err->getMessage(), $err->getCode());
+ }
+
+ if (isset($this->options['cryptType']) && $this->options['cryptType'] == 'none') {
+ $cryptFunction = 'strval';
+ } elseif (isset($this->options['cryptType']) && function_exists($this->options['cryptType'])) {
+ $cryptFunction = $this->options['cryptType'];
+ } else {
+ $cryptFunction = 'md5';
+ }
+
+ $password = $cryptFunction($password);
+
+ $query = sprintf("UPDATE %s SET %s = %s WHERE %s = %s",
+ $this->options['final_table'],
+ $this->options['final_passwordcol'],
+ $this->db->quote($password, 'text'),
+ $this->options['final_usernamecol'],
+ $this->db->quote($username, 'text')
+ );
+
+ // check if there is an optional parameter db_where
+ if ($this->options['db_where'] != '') {
+ // there is one, so add it to the query
+ $query .= " AND ".$this->options['db_where'];
+ }
+
+ $this->log('Running SQL against MDB2: '.$query, AUTH_LOG_DEBUG);
+
+ $res = $this->query($query);
+
+ if (MDB2::isError($res)) {
+ return PEAR::raiseError($res->getMessage(), $res->code);
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ supportsChallengeResponse()
+
+ /**
+ * Determine if this container supports
+ * password authentication with challenge response
+ *
+ * @return bool
+ * @access public
+ */
+ function supportsChallengeResponse()
+ {
+ return in_array($this->options['cryptType'], array('md5', 'none', ''));
+ }
+
+ // }}}
+ // {{{ getCryptType()
+
+ /**
+ * Returns the selected crypt type for this container
+ *
+ * @return string Function used to crypt the password
+ */
+ function getCryptType()
+ {
+ return $this->options['cryptType'];
+ }
+
+ // }}}
+
+}
+?>
diff --git a/includes/pear/Auth/Container/Multiple.php b/includes/pear/Auth/Container/Multiple.php
new file mode 100644
index 0000000..08f131d
--- /dev/null
+++ b/includes/pear/Auth/Container/Multiple.php
@@ -0,0 +1,188 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for using multiple storage drivers in a fall through fashion
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: Multiple.php 289653 2009-10-15 04:50:43Z aashley $
+ * @since File available since Release 1.5.0
+ */
+
+/**
+ * Include Auth_Container base class
+ */
+require_once "Auth/Container.php";
+/**
+ * Include PEAR package for error handling
+ */
+require_once "PEAR.php";
+
+/**
+ * Storage driver for using multiple storage drivers in a fall through fashion
+ *
+ * This storage driver provides a mechanism for working through multiple
+ * storage drivers until either one allows successful login or the list is
+ * exhausted.
+ *
+ * This container takes an array of options of the following form:
+ *
+ * array(
+ * array(
+ * 'type' => <standard container type name>,
+ * 'options' => <normal array of options for container>,
+ * ),
+ * );
+ *
+ * Full example:
+ *
+ * $options = array(
+ * array(
+ * 'type' => 'DB',
+ * 'options' => array(
+ * 'dsn' => "mysql://user:password@localhost/database",
+ * ),
+ * ),
+ * array(
+ * 'type' => 'Array',
+ * 'options' => array(
+ * 'cryptType' => 'md5',
+ * 'users' => array(
+ * 'admin' => md5('password'),
+ * ),
+ * ),
+ * ),
+ * );
+ *
+ * $auth = new Auth('Multiple', $options);
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 289653 $
+ * @since File available since Release 1.5.0
+ */
+
+class Auth_Container_Multiple extends Auth_Container {
+
+ // {{{ properties
+
+ /**
+ * The options for each container
+ *
+ * @var array $options
+ */
+ var $options = array();
+
+ /**
+ * The instanciated containers
+ *
+ * @var array $containers
+ */
+ var $containers = array();
+
+ // }}}
+ // {{{ Auth_Container_Multiple()
+
+ /**
+ * Constructor for Array Container
+ *
+ * @param array $data Options for the container
+ * @return void
+ */
+ function Auth_Container_Multiple($options)
+ {
+ if (!is_array($options)) {
+ PEAR::raiseError('The options for Auth_Container_Multiple must be an array');
+ }
+ if (count($options) < 1) {
+ PEAR::raiseError('You must define at least one sub container to use in Auth_Container_Multiple');
+ }
+ foreach ($options as $option) {
+ if (!isset($option['type'])) {
+ PEAR::raiseError('No type defined for sub container');
+ }
+ }
+ $this->options = $options;
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Get user information from array
+ *
+ * This function uses the given username to fetch the corresponding
+ * login data from the array. If an account that matches the passed
+ * username and password is found, the function returns true.
+ * Otherwise it returns false.
+ *
+ * @param string Username
+ * @param string Password
+ * @return boolean|PEAR_Error Error object or boolean
+ */
+ function fetchData($user, $pass)
+ {
+ $this->log('Auth_Container_Multiple::fetchData() called.', AUTH_LOG_DEBUG);
+
+ foreach ($this->options as $key => $options) {
+
+ $this->log('Using Container '.$key.' of type '.$options['type'].'.', AUTH_LOG_DEBUG);
+
+ if (isset($this->containers[$key]) && is_a($this->containers[$key], 'Auth_Container')) {
+
+ $container = &$this->containers[$key];
+
+ } else {
+
+ $this->containers[$key] = &$this->_auth_obj->_factory($options['type'], $options['options']);
+ $this->containers[$key]->_auth_obj = &$this->_auth_obj;
+ $container = &$this->containers[$key];
+
+ }
+
+ $result = $container->fetchData($user, $pass);
+
+ if (PEAR::isError($result)) {
+
+ $this->log('Container '.$key.': '.$result->getMessage(), AUTH_LOG_DEBUG);
+ return $result;
+
+ } elseif ($result == true) {
+
+ $this->log('Container '.$key.': Authentication successful.', AUTH_LOG_DEBUG);
+ return true;
+
+ } else {
+
+ $this->log('Container '.$key.': Authentication failed.', AUTH_LOG_DEBUG);
+
+ }
+
+ }
+
+ $this->log('Auth_Container_Multiple: All containers rejected user credentials.', AUTH_LOG_DEBUG);
+
+ return false;
+
+ }
+
+ // }}}
+
+}
+
+?>
diff --git a/includes/pear/Auth/Container/NetVPOPMaild.php b/includes/pear/Auth/Container/NetVPOPMaild.php
new file mode 100644
index 0000000..634fab5
--- /dev/null
+++ b/includes/pear/Auth/Container/NetVPOPMaild.php
@@ -0,0 +1,129 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for use with a Vpopmaild server
+ *
+ * PHP versions 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Bill Shupp <hostmaster@shupp.org>
+ * @author Stefan Ekman <stekman@sedata.org>
+ * @author Martin Jansen <mj@php.net>
+ * @author Mika Tuupola <tuupola@appelsiini.net>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @link http://pear.php.net/package/Auth
+ * @since File available since Release 1.2.0
+ */
+
+/**
+ * Include Auth_Container base class
+ */
+require_once 'Auth/Container.php';
+/**
+ * Include PEAR package for error handling
+ */
+require_once 'PEAR.php';
+/**
+ * Include PEAR Net_Vpopmaild package
+ */
+require_once 'Net/Vpopmaild.php';
+
+/**
+ * Storage driver for Authentication on a Vpopmaild server.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Martin Jansen <mj@php.net>
+ * @author Mika Tuupola <tuupola@appelsiini.net>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: 1.5.4 File: $Revision: 256741 $
+ * @link http://pear.php.net/package/Auth
+ * @since Class available since Release 1.6.0
+ */
+class Auth_Container_Vpopmaild extends Auth_Container
+{
+
+ /**
+ * Vpopmaild Server
+ * @var string
+ */
+ var $server = 'localhost';
+
+ /**
+ * Vpopmaild Server port
+ * @var string
+ */
+ var $port = 89;
+
+ /**
+ * Constructor of the container class
+ *
+ * @param $server string server or server:port combination
+ * @return object Returns an error object if something went wrong
+ */
+ function Auth_Container_Vpopmaild($server=null)
+ {
+ if (isset($server) && !is_null($server)) {
+ if (is_array($server)) {
+ if (isset($server['host'])) {
+ $this->server = $server['host'];
+ }
+ if (isset($server['port'])) {
+ $this->port = $server['port'];
+ }
+ } else {
+ if (strstr($server, ':')) {
+ $serverparts = explode(':', trim($server));
+ $this->server = $serverparts[0];
+ $this->port = $serverparts[1];
+ } else {
+ $this->server = $server;
+ }
+ }
+ }
+ }
+
+ /**
+ * fetchData()
+ *
+ * Try to login to the Vpopmaild server
+ *
+ * @param string username
+ * @param string password
+ *
+ * @return boolean
+ */
+ function fetchData($username, $password)
+ {
+ $this->log('Auth_Container_Vpopmaild::fetchData() called.', AUTH_LOG_DEBUG);
+ $vpopmaild =& new Net_Vpopmaild();
+ // Connect
+ try {
+ $res = $vpopmaild->connect($this->server, $this->port, $this->method);
+ } catch (Net_Vpopmaild_FatalException $e) {
+ $this->log('Connection to Vpopmaild server failed.', AUTH_LOG_DEBUG);
+ return PEAR::raiseError($e->getMessage(), $e->getCode());
+ }
+ // Authenticate
+ try {
+ $result = $vpopmaild->clogin($username, $password);
+ $vpopmaild->quit();
+ } catch (Net_Vpopmaild_Exception $e) {
+ return PEAR::raiseError($e->getMessage(), $e->getCode());
+ }
+ return $result;
+ }
+}
+?>
diff --git a/includes/pear/Auth/Container/PEAR.php b/includes/pear/Auth/Container/PEAR.php
new file mode 100644
index 0000000..1de9b82
--- /dev/null
+++ b/includes/pear/Auth/Container/PEAR.php
@@ -0,0 +1,165 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for use against PEAR website
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Yavor Shahpasov <yavo@netsmart.com.cy>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: PEAR.php 289652 2009-10-15 04:42:18Z aashley $
+ * @link http://pear.php.net/package/Auth
+ * @since File available since Release 1.3.0
+ */
+
+/**
+ * Include PEAR HTTP_Client.
+ */
+require_once 'HTTP/Client.php';
+/**
+ * Include Auth_Container base class
+ */
+require_once 'Auth/Container.php';
+
+/**
+ * Storage driver for authenticating against PEAR website
+ *
+ * This driver provides a method for authenticating against the pear.php.net
+ * authentication system.
+ *
+ * Supports two options:
+ * - "url": The base URL with schema to authenticate against
+ * - "karma": An array of karma levels which the user needs one of.
+ * When empty, no karma level is required.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Yavor Shahpasov <yavo@netsmart.com.cy>
+ * @author Adam Ashley <aashley@php.net>
+ * @author Adam Harvey <aharvey@php.net>
+ * @copyright 2001-2007 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 289652 $
+ * @link http://pear.php.net/package/Auth
+ * @since Class available since Release 1.3.0
+ */
+class Auth_Container_Pear extends Auth_Container
+{
+ // {{{ properties
+
+ /**
+ * URL to connect to, with schema
+ *
+ * @var string
+ */
+ var $url = 'https://pear.php.net/rest-login.php/';
+
+ /**
+ * Array of karma levels the user can have.
+ * A user needs only one of the levels to succeed login.
+ * No levels mean that only username and password need to match
+ *
+ * @var array
+ */
+ var $karma = array();
+
+ // }}}
+ // {{{ Auth_Container_Pear() [constructor]
+
+ /**
+ * Constructor
+ *
+ * Accepts options "url" and "karma", see class docs.
+ *
+ * @param array $data Array of options
+ *
+ * @return void
+ */
+ function Auth_Container_Pear($data = null)
+ {
+ if (!is_array($data)) {
+ PEAR::raiseError('The options for Auth_Container_Pear must be an array');
+ }
+ if (isset($data['karma'])) {
+ if (is_array($data['karma'])) {
+ $this->karma = $data['karma'];
+ } else {
+ $this->karma = array($data['karma']);
+ }
+ }
+
+ if (isset($data['url'])) {
+ $this->url = $data['url'];
+ }
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Get user information from pear.php.net
+ *
+ * This function uses the given username and password to authenticate
+ * against the pear.php.net website
+ *
+ * @param string Username
+ * @param string Password
+ * @return mixed Error object or boolean
+ */
+ function fetchData($username, $password)
+ {
+ $this->log('Auth_Container_PEAR::fetchData() called.', AUTH_LOG_DEBUG);
+
+ $client = new HTTP_Client;
+
+ $this->log('Auth_Container_PEAR::fetchData() getting salt.', AUTH_LOG_DEBUG);
+ $code = $client->get($this->url . '/getsalt');
+ if ($code instanceof PEAR_Error) {
+ return $code;
+ }
+ if ($code != 200) {
+ return PEAR::raiseError('Bad response to salt request.', $code);
+ }
+ $resp = $client->currentResponse();
+ $salt = $resp['body'];
+
+ $this->log('Auth_Container_PEAR::fetchData() calling validate.', AUTH_LOG_DEBUG);
+ $postOptions = array(
+ 'username' => $username,
+ 'password' => md5($salt . md5($password))
+ );
+ if (is_array($this->karma) && count($this->karma) > 0) {
+ $postOptions['karma'] = implode(',', $this->karma);
+ }
+
+ $code = $client->post($this->url . '/validate', $postOptions);
+ if ($code instanceof PEAR_Error) {
+ return $code;
+ }
+ if ($code != 200) {
+ return PEAR::raiseError('Bad response to validate request.', $code);
+ }
+ $resp = $client->currentResponse();
+
+ list($code, $message) = explode(' ', $resp['body'], 2);
+ if ($code != 8) {
+ return PEAR::raiseError($message, $code);
+ }
+ return true;
+ }
+
+ // }}}
+
+}
+?>
diff --git a/includes/pear/Auth/Container/POP3.php b/includes/pear/Auth/Container/POP3.php
new file mode 100644
index 0000000..0bfab1b
--- /dev/null
+++ b/includes/pear/Auth/Container/POP3.php
@@ -0,0 +1,145 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for use against a POP3 server
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Stefan Ekman <stekman@sedata.org>
+ * @author Martin Jansen <mj@php.net>
+ * @author Mika Tuupola <tuupola@appelsiini.net>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: POP3.php 237449 2007-06-12 03:11:27Z aashley $
+ * @link http://pear.php.net/package/Auth
+ * @since File available since Release 1.2.0
+ */
+
+/**
+ * Include Auth_Container base class
+ */
+require_once 'Auth/Container.php';
+/**
+ * Include PEAR package for error handling
+ */
+require_once 'PEAR.php';
+/**
+ * Include PEAR Net_POP3 package
+ */
+require_once 'Net/POP3.php';
+
+/**
+ * Storage driver for Authentication on a POP3 server.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Martin Jansen <mj@php.net>
+ * @author Mika Tuupola <tuupola@appelsiini.net>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 237449 $
+ * @link http://pear.php.net/package/Auth
+ * @since Class available since Release 1.2.0
+ */
+class Auth_Container_POP3 extends Auth_Container
+{
+
+ // {{{ properties
+
+ /**
+ * POP3 Server
+ * @var string
+ */
+ var $server='localhost';
+
+ /**
+ * POP3 Server port
+ * @var string
+ */
+ var $port='110';
+
+ /**
+ * POP3 Authentication method
+ *
+ * Prefered POP3 authentication method. Acceptable values:
+ * Boolean TRUE - Use Net_POP3's autodetection
+ * String 'DIGEST-MD5','CRAM-MD5','LOGIN','PLAIN','APOP','USER'
+ * - Attempt this authentication style first
+ * then fallback to autodetection.
+ * @var mixed
+ */
+ var $method=true;
+
+ // }}}
+ // {{{ Auth_Container_POP3() [constructor]
+
+ /**
+ * Constructor of the container class
+ *
+ * @param $server string server or server:port combination
+ * @return object Returns an error object if something went wrong
+ */
+ function Auth_Container_POP3($server=null)
+ {
+ if (isset($server) && !is_null($server)) {
+ if (is_array($server)) {
+ if (isset($server['host'])) {
+ $this->server = $server['host'];
+ }
+ if (isset($server['port'])) {
+ $this->port = $server['port'];
+ }
+ if (isset($server['method'])) {
+ $this->method = $server['method'];
+ }
+ } else {
+ if (strstr($server, ':')) {
+ $serverparts = explode(':', trim($server));
+ $this->server = $serverparts[0];
+ $this->port = $serverparts[1];
+ } else {
+ $this->server = $server;
+ }
+ }
+ }
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Try to login to the POP3 server
+ *
+ * @param string Username
+ * @param string Password
+ * @return boolean
+ */
+ function fetchData($username, $password)
+ {
+ $this->log('Auth_Container_POP3::fetchData() called.', AUTH_LOG_DEBUG);
+ $pop3 =& new Net_POP3();
+ $res = $pop3->connect($this->server, $this->port, $this->method);
+ if (!$res) {
+ $this->log('Connection to POP3 server failed.', AUTH_LOG_DEBUG);
+ return $res;
+ }
+ $result = $pop3->login($username, $password);
+ $pop3->disconnect();
+ return $result;
+ }
+
+ // }}}
+
+}
+?>
diff --git a/includes/pear/Auth/Container/RADIUS.php b/includes/pear/Auth/Container/RADIUS.php
new file mode 100644
index 0000000..585b641
--- /dev/null
+++ b/includes/pear/Auth/Container/RADIUS.php
@@ -0,0 +1,182 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for use against RADIUS servers
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Michael Bretterklieber <michael@bretterklieber.com>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: RADIUS.php 237449 2007-06-12 03:11:27Z aashley $
+ * @link http://pear.php.net/package/Auth
+ * @since File available since Release 1.2.0
+ */
+
+/**
+ * Include Auth_Container base class
+ */
+require_once "Auth/Container.php";
+/**
+ * Include PEAR Auth_RADIUS package
+ */
+require_once "Auth/RADIUS.php";
+
+/**
+ * Storage driver for authenticating users against RADIUS servers.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Michael Bretterklieber <michael@bretterklieber.com>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 237449 $
+ * @link http://pear.php.net/package/Auth
+ * @since Class available since Release 1.2.0
+ */
+class Auth_Container_RADIUS extends Auth_Container
+{
+
+ // {{{ properties
+
+ /**
+ * Contains a RADIUS object
+ * @var object
+ */
+ var $radius;
+
+ /**
+ * Contains the authentication type
+ * @var string
+ */
+ var $authtype;
+
+ // }}}
+ // {{{ Auth_Container_RADIUS() [constructor]
+
+ /**
+ * Constructor of the container class.
+ *
+ * $options can have these keys:
+ * 'servers' an array containing an array: servername, port,
+ * sharedsecret, timeout, maxtries
+ * 'configfile' The filename of the configuration file
+ * 'authtype' The type of authentication, one of: PAP, CHAP_MD5,
+ * MSCHAPv1, MSCHAPv2, default is PAP
+ *
+ * @param $options associative array
+ * @return object Returns an error object if something went wrong
+ */
+ function Auth_Container_RADIUS($options)
+ {
+ $this->authtype = 'PAP';
+ if (isset($options['authtype'])) {
+ $this->authtype = $options['authtype'];
+ }
+ $classname = 'Auth_RADIUS_' . $this->authtype;
+ if (!class_exists($classname)) {
+ PEAR::raiseError("Unknown Authtype, please use one of: "
+ ."PAP, CHAP_MD5, MSCHAPv1, MSCHAPv2!", 41, PEAR_ERROR_DIE);
+ }
+
+ $this->radius = new $classname;
+
+ if (isset($options['configfile'])) {
+ $this->radius->setConfigfile($options['configfile']);
+ }
+
+ $servers = $options['servers'];
+ if (is_array($servers)) {
+ foreach ($servers as $server) {
+ $servername = $server[0];
+ $port = isset($server[1]) ? $server[1] : 0;
+ $sharedsecret = isset($server[2]) ? $server[2] : 'testing123';
+ $timeout = isset($server[3]) ? $server[3] : 3;
+ $maxtries = isset($server[4]) ? $server[4] : 3;
+ $this->radius->addServer($servername, $port, $sharedsecret, $timeout, $maxtries);
+ }
+ }
+
+ if (!$this->radius->start()) {
+ PEAR::raiseError($this->radius->getError(), 41, PEAR_ERROR_DIE);
+ }
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Authenticate
+ *
+ * @param string Username
+ * @param string Password
+ * @return bool true on success, false on reject
+ */
+ function fetchData($username, $password, $challenge = null)
+ {
+ $this->log('Auth_Container_RADIUS::fetchData() called.', AUTH_LOG_DEBUG);
+
+ switch($this->authtype) {
+ case 'CHAP_MD5':
+ case 'MSCHAPv1':
+ if (isset($challenge)) {
+ $this->radius->challenge = $challenge;
+ $this->radius->chapid = 1;
+ $this->radius->response = pack('H*', $password);
+ } else {
+ require_once 'Crypt/CHAP.php';
+ $classname = 'Crypt_' . $this->authtype;
+ $crpt = new $classname;
+ $crpt->password = $password;
+ $this->radius->challenge = $crpt->challenge;
+ $this->radius->chapid = $crpt->chapid;
+ $this->radius->response = $crpt->challengeResponse();
+ }
+ break;
+
+ case 'MSCHAPv2':
+ require_once 'Crypt/CHAP.php';
+ $crpt = new Crypt_MSCHAPv2;
+ $crpt->username = $username;
+ $crpt->password = $password;
+ $this->radius->challenge = $crpt->authChallenge;
+ $this->radius->peerChallenge = $crpt->peerChallenge;
+ $this->radius->chapid = $crpt->chapid;
+ $this->radius->response = $crpt->challengeResponse();
+ break;
+
+ default:
+ $this->radius->password = $password;
+ break;
+ }
+
+ $this->radius->username = $username;
+
+ $this->radius->putAuthAttributes();
+ $result = $this->radius->send();
+ if (PEAR::isError($result)) {
+ return false;
+ }
+
+ $this->radius->getAttributes();
+// just for debugging
+// $this->radius->dumpAttributes();
+
+ return $result;
+ }
+
+ // }}}
+
+}
+?>
diff --git a/includes/pear/Auth/Container/SAP.php b/includes/pear/Auth/Container/SAP.php
new file mode 100644
index 0000000..58ce168
--- /dev/null
+++ b/includes/pear/Auth/Container/SAP.php
@@ -0,0 +1,179 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for use against a SAP system using the SAPRFC PHP extension.
+ *
+ * Requires the SAPRFC ext available at http://saprfc.sourceforge.net/
+ *
+ * PHP version 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Stoyan Stefanov <ssttoo@gmail.com>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: SAP.php 302205 2010-08-14 14:08:08Z clockwerx $
+ * @link http://pear.php.net/package/Auth
+ * @since File available since Release 1.4.0
+ */
+
+/**
+ * Include Auth_Container base class
+ */
+require_once 'Auth/Container.php';
+/**
+ * Include PEAR for error handling
+ */
+require_once 'PEAR.php';
+
+/**
+ * Performs authentication against a SAP system using the SAPRFC PHP extension.
+ *
+ * When the option GETSSO2 is TRUE (default)
+ * the Single Sign-On (SSO) ticket is retrieved
+ * and stored as an Auth attribute called 'sap'
+ * in order to be reused for consecutive connections.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Stoyan Stefanov <ssttoo@gmail.com>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 302205 $
+ * @since Class available since Release 1.4.0
+ */
+class Auth_Container_SAP extends Auth_Container {
+
+ // {{{ properties
+
+ /**
+ * @var array Default options
+ */
+ var $options = array(
+ 'CLIENT' => '000',
+ 'LANG' => 'EN',
+ 'GETSSO2' => true,
+ );
+
+ // }}}
+ // {{{ Auth_Container_SAP()
+
+ /**
+ * Class constructor. Checks that required options
+ * are present and that the SAPRFC extension is loaded
+ *
+ * Options that can be passed and their defaults:
+ * <pre>
+ * array(
+ * 'ASHOST' => "",
+ * 'SYSNR' => "",
+ * 'CLIENT' => "000",
+ * 'GWHOST' =>"",
+ * 'GWSERV' =>"",
+ * 'MSHOST' =>"",
+ * 'R3NAME' =>"",
+ * 'GROUP' =>"",
+ * 'LANG' =>"EN",
+ * 'TRACE' =>"",
+ * 'GETSSO2'=> true
+ * )
+ * </pre>
+ *
+ * @param array array of options.
+ * @return void
+ */
+ function Auth_Container_SAP($options)
+ {
+ $saprfc_loaded = PEAR::loadExtension('saprfc');
+ if (!$saprfc_loaded) {
+ return PEAR::raiseError('Cannot use SAP authentication, '
+ .'SAPRFC extension not loaded!');
+ }
+ if (empty($options['R3NAME']) && empty($options['ASHOST'])) {
+ return PEAR::raiseError('R3NAME or ASHOST required for authentication');
+ }
+ $this->options = array_merge($this->options, $options);
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Performs username and password check
+ *
+ * @param string Username
+ * @param string Password
+ * @return boolean TRUE on success (valid user), FALSE otherwise
+ */
+ function fetchData($username, $password)
+ {
+ $this->log('Auth_Container_SAP::fetchData() called.', AUTH_LOG_DEBUG);
+ $connection_options = $this->options;
+ $connection_options['USER'] = $username;
+ $connection_options['PASSWD'] = $password;
+ $rfc = saprfc_open($connection_options);
+ if (!$rfc) {
+ $message = "Couldn't connect to the SAP system.";
+ $error = $this->getError();
+ if ($error['message']) {
+ $message .= ': ' . $error['message'];
+ }
+ PEAR::raiseError($message, null, null, null, @$error['all']);
+ return false;
+ } else {
+ if (!empty($this->options['GETSSO2'])) {
+ $this->log('Attempting to retrieve SSO2 ticket.', AUTH_LOG_DEBUG);
+ if ($ticket = @saprfc_get_ticket($rfc)) {
+ $this->options['MYSAPSSO2'] = $ticket;
+ unset($this->options['GETSSO2']);
+ $this->_auth_obj->setAuthData('sap', $this->options);
+ } else {
+ PEAR::raiseError("SSO ticket retrieval failed");
+ }
+ }
+ @saprfc_close($rfc);
+ return true;
+ }
+
+ }
+
+ // }}}
+ // {{{ getError()
+
+ /**
+ * Retrieves the last error from the SAP connection
+ * and returns it as an array.
+ *
+ * @return array Array of error information
+ */
+ function getError()
+ {
+
+ $error = array();
+ $sap_error = saprfc_error();
+ if (empty($err)) {
+ return $error;
+ }
+ $err = explode("n", $sap_error);
+ foreach ($err AS $line) {
+ $item = explode(':', $line);
+ $error[strtolower(trim($item[0]))] = trim($item[1]);
+ }
+ $error['all'] = $sap_error;
+ return $error;
+ }
+
+ // }}}
+
+}
+
+?>
diff --git a/includes/pear/Auth/Container/SMBPasswd.php b/includes/pear/Auth/Container/SMBPasswd.php
new file mode 100644
index 0000000..15c3e75
--- /dev/null
+++ b/includes/pear/Auth/Container/SMBPasswd.php
@@ -0,0 +1,182 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for use against Samba password files
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Michael Bretterklieber <michael@bretterklieber.com>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: SMBPasswd.php 237449 2007-06-12 03:11:27Z aashley $
+ * @link http://pear.php.net/package/Auth
+ * @since File available since Release 1.2.3
+ */
+
+/**
+ * Include PEAR File_SMBPasswd
+ */
+require_once "File/SMBPasswd.php";
+/**
+ * Include Auth_Container Base file
+ */
+require_once "Auth/Container.php";
+/**
+ * Include PEAR class for error handling
+ */
+require_once "PEAR.php";
+
+/**
+ * Storage driver for fetching login data from an SAMBA smbpasswd file.
+ *
+ * This storage container can handle SAMBA smbpasswd files.
+ *
+ * Example:
+ * $a = new Auth("SMBPasswd", '/usr/local/private/smbpasswd');
+ * $a->start();
+ * if ($a->getAuth()) {
+ * printf ("AUTH OK<br>\n");
+ * $a->logout();
+ * }
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Michael Bretterklieber <michael@bretterklieber.com>
+ * @author Adam Ashley <aashley@php.net>
+ * @package Auth
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 237449 $
+ * @link http://pear.php.net/package/Auth
+ * @since Class available since Release 1.2.3
+ */
+class Auth_Container_SMBPasswd extends Auth_Container
+{
+
+ // {{{ properties
+
+ /**
+ * File_SMBPasswd object
+ * @var object
+ */
+ var $pwfile;
+
+ // }}}
+
+ // {{{ Auth_Container_SMBPasswd() [constructor]
+
+ /**
+ * Constructor of the container class
+ *
+ * @param $filename string filename for a passwd type file
+ * @return object Returns an error object if something went wrong
+ */
+ function Auth_Container_SMBPasswd($filename)
+ {
+ $this->pwfile = new File_SMBPasswd($filename,0);
+
+ if (!$this->pwfile->load()) {
+ PEAR::raiseError("Error while reading file contents.", 41, PEAR_ERROR_DIE);
+ return;
+ }
+
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Get user information from pwfile
+ *
+ * @param string Username
+ * @param string Password
+ * @return boolean
+ */
+ function fetchData($username, $password)
+ {
+ $this->log('Auth_Container_SMBPasswd::fetchData() called.', AUTH_LOG_DEBUG);
+ return $this->pwfile->verifyAccount($username, $password);
+ }
+
+ // }}}
+ // {{{ listUsers()
+
+ function listUsers()
+ {
+ $this->log('Auth_Container_SMBPasswd::fetchData() called.', AUTH_LOG_DEBUG);
+ return $this->pwfile->getAccounts();
+ }
+
+ // }}}
+ // {{{ addUser()
+
+ /**
+ * Add a new user to the storage container
+ *
+ * @param string Username
+ * @param string Password
+ * @param array Additional information
+ *
+ * @return boolean
+ */
+ function addUser($username, $password, $additional = '')
+ {
+ $this->log('Auth_Container_SMBPasswd::addUser() called.', AUTH_LOG_DEBUG);
+ $res = $this->pwfile->addUser($user, $additional['userid'], $pass);
+ if ($res === true) {
+ return $this->pwfile->save();
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ removeUser()
+
+ /**
+ * Remove user from the storage container
+ *
+ * @param string Username
+ */
+ function removeUser($username)
+ {
+ $this->log('Auth_Container_SMBPasswd::removeUser() called.', AUTH_LOG_DEBUG);
+ $res = $this->pwfile->delUser($username);
+ if ($res === true) {
+ return $this->pwfile->save();
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ changePassword()
+
+ /**
+ * Change password for user in the storage container
+ *
+ * @param string Username
+ * @param string The new password
+ */
+ function changePassword($username, $password)
+ {
+ $this->log('Auth_Container_SMBPasswd::changePassword() called.', AUTH_LOG_DEBUG);
+ $res = $this->pwfile->modUser($username, '', $password);
+ if ($res === true) {
+ return $this->pwfile->save();
+ }
+ return $res;
+ }
+
+ // }}}
+
+}
+?>
diff --git a/includes/pear/Auth/Container/SOAP.php b/includes/pear/Auth/Container/SOAP.php
new file mode 100644
index 0000000..2e11e6b
--- /dev/null
+++ b/includes/pear/Auth/Container/SOAP.php
@@ -0,0 +1,229 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for use against a SOAP service
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Bruno Pedro <bpedro@co.sapo.pt>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: SOAP.php 237449 2007-06-12 03:11:27Z aashley $
+ * @link http://pear.php.net/package/Auth
+ * @since File available since Release 1.2.0
+ */
+
+/**
+ * Include Auth_Container base class
+ */
+require_once "Auth/Container.php";
+/**
+ * Include PEAR package for error handling
+ */
+require_once "PEAR.php";
+/**
+ * Include PEAR SOAP_Client
+ */
+require_once 'SOAP/Client.php';
+
+/**
+ * Storage driver for fetching login data from SOAP
+ *
+ * This class takes one parameter (options), where
+ * you specify the following fields: endpoint, namespace,
+ * method, encoding, usernamefield and passwordfield.
+ *
+ * You can use specify features of your SOAP service
+ * by providing its parameters in an associative manner by
+ * using the '_features' array through the options parameter.
+ *
+ * The 'matchpassword' option should be set to false if your
+ * webservice doesn't return (username,password) pairs, but
+ * instead returns error when the login is invalid.
+ *
+ * Example usage:
+ *
+ * <?php
+ *
+ * ...
+ *
+ * $options = array (
+ * 'endpoint' => 'http://your.soap.service/endpoint',
+ * 'namespace' => 'urn:/Your/Namespace',
+ * 'method' => 'get',
+ * 'encoding' => 'UTF-8',
+ * 'usernamefield' => 'login',
+ * 'passwordfield' => 'password',
+ * 'matchpasswords' => false,
+ * '_features' => array (
+ * 'example_feature' => 'example_value',
+ * 'another_example' => ''
+ * )
+ * );
+ * $auth = new Auth('SOAP', $options, 'loginFunction');
+ * $auth->start();
+ *
+ * ...
+ *
+ * ?>
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Bruno Pedro <bpedro@co.sapo.pt>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 237449 $
+ * @link http://pear.php.net/package/Auth
+ * @since Class available since Release 1.2.0
+ */
+class Auth_Container_SOAP extends Auth_Container
+{
+
+ // {{{ properties
+
+ /**
+ * Required options for the class
+ * @var array
+ * @access private
+ */
+ var $_requiredOptions = array(
+ 'endpoint',
+ 'namespace',
+ 'method',
+ 'encoding',
+ 'usernamefield',
+ 'passwordfield',
+ );
+
+ /**
+ * Options for the class
+ * @var array
+ * @access private
+ */
+ var $_options = array();
+
+ /**
+ * Optional SOAP features
+ * @var array
+ * @access private
+ */
+ var $_features = array();
+
+ /**
+ * The SOAP response
+ * @var array
+ * @access public
+ */
+ var $soapResponse = array();
+
+ /**
+ * The SOAP client
+ * @var mixed
+ * @access public
+ */
+ var $soapClient = null;
+
+ // }}}
+ // {{{ Auth_Container_SOAP() [constructor]
+
+ /**
+ * Constructor of the container class
+ *
+ * @param $options, associative array with endpoint, namespace, method,
+ * usernamefield, passwordfield and optional features
+ */
+ function Auth_Container_SOAP($options)
+ {
+ $this->_options = $options;
+ if (!isset($this->_options['matchpasswords'])) {
+ $this->_options['matchpasswords'] = true;
+ }
+ if (!empty($this->_options['_features'])) {
+ $this->_features = $this->_options['_features'];
+ unset($this->_options['_features']);
+ }
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Fetch data from SOAP service
+ *
+ * Requests the SOAP service for the given username/password
+ * combination.
+ *
+ * @param string Username
+ * @param string Password
+ * @return mixed Returns the SOAP response or false if something went wrong
+ */
+ function fetchData($username, $password)
+ {
+ $this->log('Auth_Container_SOAP::fetchData() called.', AUTH_LOG_DEBUG);
+ // check if all required options are set
+ if (array_intersect($this->_requiredOptions, array_keys($this->_options)) != $this->_requiredOptions) {
+ return false;
+ } else {
+ // create a SOAP client and set encoding
+ $this->soapClient = new SOAP_Client($this->_options['endpoint']);
+ $this->soapClient->setEncoding($this->_options['encoding']);
+ }
+
+ // set the trace option if requested
+ if (isset($this->_options['trace'])) {
+ $this->soapClient->__options['trace'] = true;
+ }
+
+ // set the timeout option if requested
+ if (isset($this->_options['timeout'])) {
+ $this->soapClient->__options['timeout'] = $this->_options['timeout'];
+ }
+
+ // assign username and password fields
+ $usernameField = new SOAP_Value($this->_options['usernamefield'],'string', $username);
+ $passwordField = new SOAP_Value($this->_options['passwordfield'],'string', $password);
+ $SOAPParams = array($usernameField, $passwordField);
+
+ // assign optional features
+ foreach ($this->_features as $fieldName => $fieldValue) {
+ $SOAPParams[] = new SOAP_Value($fieldName, 'string', $fieldValue);
+ }
+
+ // make SOAP call
+ $this->soapResponse = $this->soapClient->call(
+ $this->_options['method'],
+ $SOAPParams,
+ array('namespace' => $this->_options['namespace'])
+ );
+
+ if (!PEAR::isError($this->soapResponse)) {
+ if ($this->_options['matchpasswords']) {
+ // check if passwords match
+ if ($password == $this->soapResponse->{$this->_options['passwordfield']}) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ // }}}
+
+}
+?>
diff --git a/includes/pear/Auth/Container/SOAP5.php b/includes/pear/Auth/Container/SOAP5.php
new file mode 100644
index 0000000..698c685
--- /dev/null
+++ b/includes/pear/Auth/Container/SOAP5.php
@@ -0,0 +1,268 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for use against a SOAP service using PHP5 SoapClient
+ *
+ * PHP version 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Based upon Auth_Container_SOAP by Bruno Pedro <bpedro@co.sapo.pt>
+ * @author Marcel Oelke <puRe@rednoize.com>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: SOAP5.php 238999 2007-07-02 08:25:41Z aashley $
+ * @since File available since Release 1.4.0
+ */
+
+/**
+ * Include Auth_Container base class
+ */
+require_once "Auth/Container.php";
+/**
+ * Include PEAR package for error handling
+ */
+require_once "PEAR.php";
+
+/**
+ * Storage driver for fetching login data from SOAP using the PHP5 Builtin SOAP
+ * functions. This is a modification of the SOAP Storage driver from Bruno Pedro
+ * thats using the PEAR SOAP Package.
+ *
+ * This class takes one parameter (options), where
+ * you specify the following fields:
+ * * location and uri, or wsdl file
+ * * method to call on the SOAP service
+ * * usernamefield, the name of the parameter where the username is supplied
+ * * passwordfield, the name of the parameter where the password is supplied
+ * * matchpassword, whether to look for the password in the response from
+ * the function call or assume that no errors means user
+ * authenticated.
+ *
+ * See http://www.php.net/manual/en/ref.soap.php for further details
+ * on options for the PHP5 SoapClient which are passed through.
+ *
+ * Example usage without WSDL:
+ *
+ * <?php
+ *
+ * $options = array (
+ * 'wsdl' => NULL,
+ * 'location' => 'http://your.soap.service/endpoint',
+ * 'uri' => 'urn:/Your/Namespace',
+ * 'method' => 'checkAuth',
+ * 'usernamefield' => 'username',
+ * 'passwordfield' => 'password',
+ * 'matchpasswords' => false,
+ * '_features' => array (
+ * 'extra_parameter' => 'example_value',
+ * 'another_parameter' => 'foobar'
+ * )
+ * );
+ *
+ * $auth = new Auth('SOAP5', $options);
+ * $auth->start();
+ *
+ * ?>
+ *
+ * Example usage with WSDL:
+ *
+ * <?php
+ *
+ * $options = array (
+ * 'wsdl' => 'http://your.soap.service/wsdl',
+ * 'method' => 'checkAuth',
+ * 'usernamefield' => 'username',
+ * 'passwordfield' => 'password',
+ * 'matchpasswords' => false,
+ * '_features' => array (
+ * 'extra_parameter' => 'example_value',
+ * 'another_parameter' => 'foobar'
+ * )
+ * );
+ *
+ * $auth = new Auth('SOAP5', $options);
+ * $auth->start();
+ *
+ * ?>
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Based upon Auth_Container_SOAP by Bruno Pedro <bpedro@co.sapo.pt>
+ * @author Marcel Oelke <puRe@rednoize.com>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 238999 $
+ * @since Class available since Release 1.4.0
+ */
+class Auth_Container_SOAP5 extends Auth_Container
+{
+
+ // {{{ properties
+
+ /**
+ * Required options for the class
+ * @var array
+ * @access private
+ */
+ var $_requiredOptions = array(
+ 'location',
+ 'uri',
+ 'method',
+ 'usernamefield',
+ 'passwordfield',
+ 'wsdl',
+ );
+
+ /**
+ * Options for the class
+ * @var array
+ * @access private
+ */
+ var $_options = array();
+
+ /**
+ * Optional SOAP features
+ * @var array
+ * @access private
+ */
+ var $_features = array();
+
+ /**
+ * The SOAP response
+ * @var array
+ * @access public
+ */
+ var $soapResponse = array();
+
+ // }}}
+ // {{{ Auth_Container_SOAP5()
+
+ /**
+ * Constructor of the container class
+ *
+ * @param $options, associative array with endpoint, namespace, method,
+ * usernamefield, passwordfield and optional features
+ */
+ function Auth_Container_SOAP5($options)
+ {
+ $this->_setDefaults();
+
+ foreach ($options as $name => $value) {
+ $this->_options[$name] = $value;
+ }
+
+ if (!empty($this->_options['_features'])) {
+ $this->_features = $this->_options['_features'];
+ unset($this->_options['_features']);
+ }
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Fetch data from SOAP service
+ *
+ * Requests the SOAP service for the given username/password
+ * combination.
+ *
+ * @param string Username
+ * @param string Password
+ * @return mixed Returns the SOAP response or false if something went wrong
+ */
+ function fetchData($username, $password)
+ {
+ $this->log('Auth_Container_SOAP5::fetchData() called.', AUTH_LOG_DEBUG);
+ $result = $this->_validateOptions();
+ if (PEAR::isError($result))
+ return $result;
+
+ // create a SOAP client
+ $soapClient = new SoapClient($this->_options["wsdl"], $this->_options);
+
+ $params = array();
+ // first, assign the optional features
+ foreach ($this->_features as $fieldName => $fieldValue) {
+ $params[$fieldName] = $fieldValue;
+ }
+ // assign username and password ...
+ $params[$this->_options['usernamefield']] = $username;
+ $params[$this->_options['passwordfield']] = $password;
+
+ try {
+ $this->soapResponse = $soapClient->__soapCall($this->_options['method'], $params);
+
+ if ($this->_options['matchpasswords']) {
+ // check if passwords match
+ if ($password == $this->soapResponse[$this->_options['passwordfield']]) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return true;
+ }
+ } catch (SoapFault $e) {
+ return PEAR::raiseError("Error retrieving authentication data. Received SOAP Fault: ".$e->faultstring, $e->faultcode);
+ }
+ }
+
+ // }}}
+ // {{{ _validateOptions()
+
+ /**
+ * Validate that the options passed to the container class are enough for us to proceed
+ *
+ * @access private
+ * @param array
+ */
+ function _validateOptions()
+ {
+ if ( ( is_null($this->_options['wsdl'])
+ && is_null($this->_options['location'])
+ && is_null($this->_options['uri']))
+ || ( is_null($this->_options['wsdl'])
+ && ( is_null($this->_options['location'])
+ || is_null($this->_options['uri'])))) {
+ return PEAR::raiseError('Either a WSDL file or a location/uri pair must be specified.');
+ }
+ if (is_null($this->_options['method'])) {
+ return PEAR::raiseError('A method to call on the soap service must be specified.');
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ _setDefaults()
+
+ /**
+ * Set some default options
+ *
+ * @access private
+ * @return void
+ */
+ function _setDefaults()
+ {
+ $this->_options['wsdl'] = null;
+ $this->_options['location'] = null;
+ $this->_options['uri'] = null;
+ $this->_options['method'] = null;
+ $this->_options['usernamefield'] = 'username';
+ $this->_options['passwordfield'] = 'password';
+ $this->_options['matchpasswords'] = true;
+ }
+
+ // }}}
+
+}
+?>
diff --git a/includes/pear/Auth/Container/vpopmail.php b/includes/pear/Auth/Container/vpopmail.php
new file mode 100644
index 0000000..2350ca7
--- /dev/null
+++ b/includes/pear/Auth/Container/vpopmail.php
@@ -0,0 +1,88 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Storage driver for use against vpopmail setups
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Stanislav Grozev <tacho@orbitel.bg>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: vpopmail.php 237449 2007-06-12 03:11:27Z aashley $
+ * @link http://pear.php.net/package/Auth
+ * @since File available since Release 1.2.0
+ */
+
+/**
+ * Include Auth_Container base class
+ */
+require_once "Auth/Container.php";
+/**
+ * Include PEAR package for error handling
+ */
+require_once "PEAR.php";
+
+/**
+ * Storage driver for fetching login data from vpopmail
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Stanislav Grozev <tacho@orbitel.bg>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 237449 $
+ * @link http://pear.php.net/package/Auth
+ * @since Class available since Release 1.2.0
+ */
+class Auth_Container_vpopmail extends Auth_Container {
+
+ // {{{ Constructor
+
+ /**
+ * Constructor of the container class
+ *
+ * @return void
+ */
+ function Auth_Container_vpopmail()
+ {
+ if (!extension_loaded('vpopmail')) {
+ return PEAR::raiseError('Cannot use VPOPMail authentication, '
+ .'VPOPMail extension not loaded!', 41, PEAR_ERROR_DIE);
+ }
+ }
+
+ // }}}
+ // {{{ fetchData()
+
+ /**
+ * Get user information from vpopmail
+ *
+ * @param string Username - has to be valid email address
+ * @param string Password
+ * @return boolean
+ */
+ function fetchData($username, $password)
+ {
+ $this->log('Auth_Container_vpopmail::fetchData() called.', AUTH_LOG_DEBUG);
+ $userdata = array();
+ $userdata = preg_split("/@/", $username, 2);
+ $result = @vpopmail_auth_user($userdata[0], $userdata[1], $password);
+
+ return $result;
+ }
+
+ // }}}
+
+}
+?>
diff --git a/includes/pear/Auth/Controller.php b/includes/pear/Auth/Controller.php
new file mode 100644
index 0000000..92d9f80
--- /dev/null
+++ b/includes/pear/Auth/Controller.php
@@ -0,0 +1,302 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Auth Controller
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Yavor Shahpasov <yavo@netsmart.com.cy>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: Controller.php 237449 2007-06-12 03:11:27Z aashley $
+ * @link http://pear.php.net/package/Auth
+ * @since File available since Release 1.3.0
+ */
+
+/**
+ * Controlls access to a group of php access
+ * and redirects to a predefined login page as
+ * needed
+ *
+ * In all pages
+ * <code>
+ * include_once('Auth.php');
+ * include_once('Auth/Controller.php');
+ * $_auth = new Auth('File', 'passwd');
+ * $authController = new Auth_Controller($_auth, 'login.php', 'index.php');
+ * $authController->start();
+ * </code>
+ *
+ * In login.php
+ * <code>
+ * include_once('Auth.php');
+ * include_once('Auth/Controller.php');
+ * $_auth = new Auth('File', 'passwd');
+ * $authController = new Auth_Controller($_auth, 'login.php', 'index.php');
+ * $authController->start();
+ * if( $authController->isAuthorised() ){
+ * $authController->redirectBack();
+ * }
+ * </code>
+ *
+ * @category Authentication
+ * @author Yavor Shahpasov <yavo@netsmart.com.cy>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 237449 $
+ * @link http://pear.php.net/package/Auth
+ * @since Class available since Release 1.3.0
+ */
+class Auth_Controller
+{
+
+ // {{{ properties
+
+ /**
+ * The Auth instance this controller is managing
+ *
+ * @var object Auth
+ */
+ var $auth = null;
+
+ /**
+ * The login URL
+ * @var string
+ * */
+ var $login = null;
+
+ /**
+ * The default index page to use when the caller page is not set
+ *
+ * @var string
+ */
+ var $default = null;
+
+ /**
+ * If this is set to true after a succesfull login the
+ * Auth_Controller::redirectBack() is invoked automatically
+ *
+ * @var boolean
+ */
+ var $autoRedirectBack = false;
+
+ // }}}
+ // {{{ Auth_Controller() [constructor]
+
+ /**
+ * Constructor
+ *
+ * @param Auth An auth instance
+ * @param string The login page
+ * @param string The default page to go to if return page is not set
+ * @param array Some rules about which urls need to be sent to the login page
+ * @return void
+ * @todo Add a list of urls which need redirection
+ */
+ function Auth_Controller(&$auth_obj, $login='login.php', $default='index.php', $accessList=array())
+ {
+ $this->auth =& $auth_obj;
+ $this->_loginPage = $login;
+ $this->_defaultPage = $default;
+ @session_start();
+ if (!empty($_GET['return']) && $_GET['return'] && !strstr($_GET['return'], $this->_loginPage)) {
+ $this->auth->setAuthData('returnUrl', $_GET['return']);
+ }
+
+ if(!empty($_GET['authstatus']) && $this->auth->status == '') {
+ $this->auth->status = $_GET['authstatus'];
+ }
+ }
+
+ // }}}
+ // {{{ setAutoRedirectBack()
+
+ /**
+ * Enables auto redirection when login is done
+ *
+ * @param bool Sets the autoRedirectBack flag to this
+ * @see Auth_Controller::autoRedirectBack
+ * @return void
+ */
+ function setAutoRedirectBack($flag = true)
+ {
+ $this->autoRedirectBack = $flag;
+ }
+
+ // }}}
+ // {{{ redirectBack()
+
+ /**
+ * Redirects Back to the calling page
+ *
+ * @return void
+ */
+ function redirectBack()
+ {
+ // If redirectback go there
+ // else go to the default page
+
+ $returnUrl = $this->auth->getAuthData('returnUrl');
+ if(!$returnUrl) {
+ $returnUrl = $this->_defaultPage;
+ }
+
+ // Add some entropy to the return to make it unique
+ // avoind problems with cached pages and proxies
+ if(strpos($returnUrl, '?') === false) {
+ $returnUrl .= '?';
+ }
+ $returnUrl .= uniqid('');
+
+ // Track the auth status
+ if($this->auth->status != '') {
+ $url .= '&authstatus='.$this->auth->status;
+ }
+ header('Location:'.$returnUrl);
+ print("You could not be redirected to <a href=\"$returnUrl\">$returnUrl</a>");
+ }
+
+ // }}}
+ // {{{ redirectLogin()
+
+ /**
+ * Redirects to the login Page if not authorised
+ *
+ * put return page on the query or in auth
+ *
+ * @return void
+ */
+ function redirectLogin()
+ {
+ // Go to the login Page
+
+ // For Auth, put some check to avoid infinite redirects, this should at least exclude
+ // the login page
+
+ $url = $this->_loginPage;
+ if(strpos($url, '?') === false) {
+ $url .= '?';
+ }
+
+ if(!strstr($_SERVER['PHP_SELF'], $this->_loginPage)) {
+ $url .= 'return='.urlencode($_SERVER['PHP_SELF']);
+ }
+
+ // Track the auth status
+ if($this->auth->status != '') {
+ $url .= '&authstatus='.$this->auth->status;
+ }
+
+ header('Location:'.$url);
+ print("You could not be redirected to <a href=\"$url\">$url</a>");
+ }
+
+ // }}}
+ // {{{ start()
+
+ /**
+ * Starts the Auth Procedure
+ *
+ * If the page requires login the user is redirected to the login page
+ * otherwise the Auth::start is called to initialize Auth
+ *
+ * @return void
+ * @todo Implement an access list which specifies which urls/pages need login and which do not
+ */
+ function start()
+ {
+ // Check the accessList here
+ // ACL should be a list of urls with allow/deny
+ // If allow set allowLogin to false
+ // Some wild card matching should be implemented ?,*
+ if(!strstr($_SERVER['PHP_SELF'], $this->_loginPage) && !$this->auth->checkAuth()) {
+ $this->redirectLogin();
+ } else {
+ $this->auth->start();
+ // Logged on and on login page
+ if(strstr($_SERVER['PHP_SELF'], $this->_loginPage) && $this->auth->checkAuth()){
+ $this->autoRedirectBack ?
+ $this->redirectBack() :
+ null ;
+ }
+ }
+
+
+ }
+
+ // }}}
+ // {{{ isAuthorised()
+
+ /**
+ * Checks is the user is logged on
+ * @see Auth::checkAuth()
+ */
+ function isAuthorised()
+ {
+ return($this->auth->checkAuth());
+ }
+
+ // }}}
+ // {{{ checkAuth()
+
+ /**
+ * Proxy call to auth
+ * @see Auth::checkAuth()
+ */
+ function checkAuth()
+ {
+ return($this->auth->checkAuth());
+ }
+
+ // }}}
+ // {{{ logout()
+
+ /**
+ * Proxy call to auth
+ * @see Auth::logout()
+ */
+ function logout()
+ {
+ return($this->auth->logout());
+ }
+
+ // }}}
+ // {{{ getUsername()
+
+ /**
+ * Proxy call to auth
+ * @see Auth::getUsername()
+ */
+ function getUsername()
+ {
+ return($this->auth->getUsername());
+ }
+
+ // }}}
+ // {{{ getStatus()
+
+ /**
+ * Proxy call to auth
+ * @see Auth::getStatus()
+ */
+ function getStatus()
+ {
+ return($this->auth->getStatus());
+ }
+
+ // }}}
+
+}
+
+?>
diff --git a/includes/pear/Auth/Frontend/Html.php b/includes/pear/Auth/Frontend/Html.php
new file mode 100644
index 0000000..6719e92
--- /dev/null
+++ b/includes/pear/Auth/Frontend/Html.php
@@ -0,0 +1,142 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+
+/**
+ * Standard Html Login form
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Martin Jansen <mj@php.net>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version CVS: $Id: Html.php 237449 2007-06-12 03:11:27Z aashley $
+ * @link http://pear.php.net/package/Auth
+ * @since File available since Release 1.3.0
+ */
+
+/**
+ * Standard Html Login form
+ *
+ * @category Authentication
+ * @package Auth
+ * @author Yavor Shahpasov <yavo@netsmart.com.cy>
+ * @author Adam Ashley <aashley@php.net>
+ * @copyright 2001-2006 The PHP Group
+ * @license http://www.php.net/license/3_01.txt PHP License 3.01
+ * @version Release: @package_version@ File: $Revision: 237449 $
+ * @link http://pear.php.net/package/Auth
+ * @since Class available since Release 1.3.0
+ */
+class Auth_Frontend_Html {
+
+ // {{{ render()
+
+ /**
+ * Displays the login form
+ *
+ * @param object The calling auth instance
+ * @param string The previously used username
+ * @return void
+ */
+ function render(&$caller, $username = '') {
+ $loginOnClick = 'return true;';
+
+ // Try To Use Challene response
+ // TODO javascript might need some improvement for work on other browsers
+ if($caller->advancedsecurity && $caller->storage->supportsChallengeResponse() ) {
+
+ // Init the secret cookie
+ $caller->session['loginchallenege'] = md5(microtime());
+
+ print "\n";
+ print '<script language="JavaScript">'."\n";
+
+ include 'Auth/Frontend/md5.js';
+
+ print "\n";
+ print ' function securePassword() { '."\n";
+ print ' var pass = document.getElementById(\''.$caller->getPostPasswordField().'\');'."\n";
+ print ' var secret = document.getElementById(\'authsecret\')'."\n";
+ //print ' alert(pass);alert(secret); '."\n";
+
+ // If using md5 for password storage md5 the password before
+ // we hash it with the secret
+ // print ' alert(pass.value);';
+ if ($caller->storage->getCryptType() == 'md5' ) {
+ print ' pass.value = hex_md5(pass.value); '."\n";
+ #print ' alert(pass.value);';
+ }
+
+ print ' pass.value = hex_md5(pass.value+\''.$caller->session['loginchallenege'].'\'); '."\n";
+ // print ' alert(pass.value);';
+ print ' secret.value = 1;'."\n";
+ print ' var doLogin = document.getElementById(\'doLogin\')'."\n";
+ print ' doLogin.disabled = true;'."\n";
+ print ' return true;';
+ print ' } '."\n";
+ print '</script>'."\n";;
+ print "\n";
+
+ $loginOnClick = ' return securePassword(); ';
+ }
+
+ print '<center>'."\n";
+
+ $status = '';
+ if (!empty($caller->status) && $caller->status == AUTH_EXPIRED) {
+ $status = '<i>Your session has expired. Please login again!</i>'."\n";
+ } else if (!empty($caller->status) && $caller->status == AUTH_IDLED) {
+ $status = '<i>You have been idle for too long. Please login again!</i>'."\n";
+ } else if (!empty ($caller->status) && $caller->status == AUTH_WRONG_LOGIN) {
+ $status = '<i>Wrong login data!</i>'."\n";
+ } else if (!empty ($caller->status) && $caller->status == AUTH_SECURITY_BREACH) {
+ $status = '<i>Security problem detected. </i>'."\n";
+ }
+
+ print '<form method="post" action="'.$caller->server['PHP_SELF'].'" '
+ .'onSubmit="'.$loginOnClick.'">'."\n";
+ print '<table border="0" cellpadding="2" cellspacing="0" '
+ .'summary="login form" align="center" >'."\n";
+ print '<tr>'."\n";
+ print ' <td colspan="2" bgcolor="#eeeeee"><strong>Login </strong>'
+ .$status.'</td>'."\n";
+ print '</tr>'."\n";
+ print '<tr>'."\n";
+ print ' <td>Username:</td>'."\n";
+ print ' <td><input type="text" id="'.$caller->getPostUsernameField()
+ .'" name="'.$caller->getPostUsernameField().'" value="' . $username
+ .'" /></td>'."\n";
+ print '</tr>'."\n";
+ print '<tr>'."\n";
+ print ' <td>Password:</td>'."\n";
+ print ' <td><input type="password" id="'.$caller->getPostPasswordField()
+ .'" name="'.$caller->getPostPasswordField().'" /></td>'."\n";
+ print '</tr>'."\n";
+ print '<tr>'."\n";
+
+ //onClick=" '.$loginOnClick.' "
+ print ' <td colspan="2" bgcolor="#eeeeee"><input value="Login" '
+ .'id="doLogin" name="doLogin" type="submit" /></td>'."\n";
+ print '</tr>'."\n";
+ print '</table>'."\n";
+
+ // Might be a good idea to make the variable name variable
+ print '<input type="hidden" id="authsecret" name="authsecret" value="" />';
+ print '</form>'."\n";
+ print '</center>'."\n";
+ }
+
+ // }}}
+
+}
+
+?>
diff --git a/includes/pear/Console/Getopt.php b/includes/pear/Console/Getopt.php
new file mode 100644
index 0000000..eb65df2
--- /dev/null
+++ b/includes/pear/Console/Getopt.php
@@ -0,0 +1,360 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+/**
+ * PHP Version 5
+ *
+ * Copyright (c) 1997-2004 The PHP Group
+ *
+ * This source file is subject to version 3.0 of the PHP license,
+ * that is bundled with this package in the file LICENSE, and is
+ * available through the world-wide-web at the following url:
+ * http://www.php.net/license/3_0.txt.
+ * If you did not receive a copy of the PHP license and are unable to
+ * obtain it through the world-wide-web, please send a note to
+ * license@php.net so we can mail you a copy immediately.
+ *
+ * @category Console
+ * @package Console_Getopt
+ * @author Andrei Zmievski <andrei@php.net>
+ * @license http://www.php.net/license/3_0.txt PHP 3.0
+ * @version CVS: $Id: Getopt.php 306067 2010-12-08 00:13:31Z dufuz $
+ * @link http://pear.php.net/package/Console_Getopt
+ */
+
+require_once 'PEAR.php';
+
+/**
+ * Command-line options parsing class.
+ *
+ * @category Console
+ * @package Console_Getopt
+ * @author Andrei Zmievski <andrei@php.net>
+ * @license http://www.php.net/license/3_0.txt PHP 3.0
+ * @link http://pear.php.net/package/Console_Getopt
+ */
+class Console_Getopt
+{
+
+ /**
+ * Parses the command-line options.
+ *
+ * The first parameter to this function should be the list of command-line
+ * arguments without the leading reference to the running program.
+ *
+ * The second parameter is a string of allowed short options. Each of the
+ * option letters can be followed by a colon ':' to specify that the option
+ * requires an argument, or a double colon '::' to specify that the option
+ * takes an optional argument.
+ *
+ * The third argument is an optional array of allowed long options. The
+ * leading '--' should not be included in the option name. Options that
+ * require an argument should be followed by '=', and options that take an
+ * option argument should be followed by '=='.
+ *
+ * The return value is an array of two elements: the list of parsed
+ * options and the list of non-option command-line arguments. Each entry in
+ * the list of parsed options is a pair of elements - the first one
+ * specifies the option, and the second one specifies the option argument,
+ * if there was one.
+ *
+ * Long and short options can be mixed.
+ *
+ * Most of the semantics of this function are based on GNU getopt_long().
+ *
+ * @param array $args an array of command-line arguments
+ * @param string $short_options specifies the list of allowed short options
+ * @param array $long_options specifies the list of allowed long options
+ * @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
+ *
+ * @return array two-element array containing the list of parsed options and
+ * the non-option arguments
+ */
+ public static function getopt2($args, $short_options, $long_options = null, $skip_unknown = false)
+ {
+ return Console_Getopt::doGetopt(2, $args, $short_options, $long_options, $skip_unknown);
+ }
+
+ /**
+ * This function expects $args to start with the script name (POSIX-style).
+ * Preserved for backwards compatibility.
+ *
+ * @param array $args an array of command-line arguments
+ * @param string $short_options specifies the list of allowed short options
+ * @param array $long_options specifies the list of allowed long options
+ *
+ * @see getopt2()
+ * @return array two-element array containing the list of parsed options and
+ * the non-option arguments
+ */
+ public static function getopt($args, $short_options, $long_options = null, $skip_unknown = false)
+ {
+ return Console_Getopt::doGetopt(1, $args, $short_options, $long_options, $skip_unknown);
+ }
+
+ /**
+ * The actual implementation of the argument parsing code.
+ *
+ * @param int $version Version to use
+ * @param array $args an array of command-line arguments
+ * @param string $short_options specifies the list of allowed short options
+ * @param array $long_options specifies the list of allowed long options
+ * @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
+ *
+ * @return array
+ */
+ public static function doGetopt($version, $args, $short_options, $long_options = null, $skip_unknown = false)
+ {
+ // in case you pass directly readPHPArgv() as the first arg
+ if (PEAR::isError($args)) {
+ return $args;
+ }
+
+ if (empty($args)) {
+ return array(array(), array());
+ }
+
+ $non_opts = $opts = array();
+
+ settype($args, 'array');
+
+ if ($long_options) {
+ sort($long_options);
+ }
+
+ /*
+ * Preserve backwards compatibility with callers that relied on
+ * erroneous POSIX fix.
+ */
+ if ($version < 2) {
+ if (isset($args[0]{0}) && $args[0]{0} != '-') {
+ array_shift($args);
+ }
+ }
+
+ reset($args);
+ while (list($i, $arg) = each($args)) {
+ /* The special element '--' means explicit end of
+ options. Treat the rest of the arguments as non-options
+ and end the loop. */
+ if ($arg == '--') {
+ $non_opts = array_merge($non_opts, array_slice($args, $i + 1));
+ break;
+ }
+
+ if ($arg{0} != '-' || (strlen($arg) > 1 && $arg{1} == '-' && !$long_options)) {
+ $non_opts = array_merge($non_opts, array_slice($args, $i));
+ break;
+ } elseif (strlen($arg) > 1 && $arg{1} == '-') {
+ $error = Console_Getopt::_parseLongOption(substr($arg, 2),
+ $long_options,
+ $opts,
+ $args,
+ $skip_unknown);
+ if (PEAR::isError($error)) {
+ return $error;
+ }
+ } elseif ($arg == '-') {
+ // - is stdin
+ $non_opts = array_merge($non_opts, array_slice($args, $i));
+ break;
+ } else {
+ $error = Console_Getopt::_parseShortOption(substr($arg, 1),
+ $short_options,
+ $opts,
+ $args,
+ $skip_unknown);
+ if (PEAR::isError($error)) {
+ return $error;
+ }
+ }
+ }
+
+ return array($opts, $non_opts);
+ }
+
+ /**
+ * Parse short option
+ *
+ * @param string $arg Argument
+ * @param string[] $short_options Available short options
+ * @param string[][] &$opts
+ * @param string[] &$args
+ * @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
+ *
+ * @return void
+ */
+ public static function _parseShortOption($arg, $short_options, &$opts, &$args, $skip_unknown)
+ {
+ for ($i = 0; $i < strlen($arg); $i++) {
+ $opt = $arg{$i};
+ $opt_arg = null;
+
+ /* Try to find the short option in the specifier string. */
+ if (($spec = strstr($short_options, $opt)) === false || $arg{$i} == ':') {
+ if ($skip_unknown === true) {
+ break;
+ }
+
+ $msg = "Console_Getopt: unrecognized option -- $opt";
+ return PEAR::raiseError($msg);
+ }
+
+ if (strlen($spec) > 1 && $spec{1} == ':') {
+ if (strlen($spec) > 2 && $spec{2} == ':') {
+ if ($i + 1 < strlen($arg)) {
+ /* Option takes an optional argument. Use the remainder of
+ the arg string if there is anything left. */
+ $opts[] = array($opt, substr($arg, $i + 1));
+ break;
+ }
+ } else {
+ /* Option requires an argument. Use the remainder of the arg
+ string if there is anything left. */
+ if ($i + 1 < strlen($arg)) {
+ $opts[] = array($opt, substr($arg, $i + 1));
+ break;
+ } else if (list(, $opt_arg) = each($args)) {
+ /* Else use the next argument. */;
+ if (Console_Getopt::_isShortOpt($opt_arg)
+ || Console_Getopt::_isLongOpt($opt_arg)) {
+ $msg = "option requires an argument --$opt";
+ return PEAR::raiseError("Console_Getopt:" . $msg);
+ }
+ } else {
+ $msg = "option requires an argument --$opt";
+ return PEAR::raiseError("Console_Getopt:" . $msg);
+ }
+ }
+ }
+
+ $opts[] = array($opt, $opt_arg);
+ }
+ }
+
+ /**
+ * Checks if an argument is a short option
+ *
+ * @param string $arg Argument to check
+ *
+ * @return bool
+ */
+ private static function _isShortOpt($arg)
+ {
+ return strlen($arg) == 2 && $arg[0] == '-'
+ && preg_match('/[a-zA-Z]/', $arg[1]);
+ }
+
+ /**
+ * Checks if an argument is a long option
+ *
+ * @param string $arg Argument to check
+ *
+ * @return bool
+ */
+ private static function _isLongOpt($arg)
+ {
+ return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' &&
+ preg_match('/[a-zA-Z]+$/', substr($arg, 2));
+ }
+
+ /**
+ * Parse long option
+ *
+ * @param string $arg Argument
+ * @param string[] $long_options Available long options
+ * @param string[][] &$opts
+ * @param string[] &$args
+ *
+ * @return void|PEAR_Error
+ */
+ private static function _parseLongOption($arg, $long_options, &$opts, &$args, $skip_unknown)
+ {
+ @list($opt, $opt_arg) = explode('=', $arg, 2);
+
+ $opt_len = strlen($opt);
+
+ for ($i = 0; $i < count($long_options); $i++) {
+ $long_opt = $long_options[$i];
+ $opt_start = substr($long_opt, 0, $opt_len);
+
+ $long_opt_name = str_replace('=', '', $long_opt);
+
+ /* Option doesn't match. Go on to the next one. */
+ if ($long_opt_name != $opt) {
+ continue;
+ }
+
+ $opt_rest = substr($long_opt, $opt_len);
+
+ /* Check that the options uniquely matches one of the allowed
+ options. */
+ if ($i + 1 < count($long_options)) {
+ $next_option_rest = substr($long_options[$i + 1], $opt_len);
+ } else {
+ $next_option_rest = '';
+ }
+
+ if ($opt_rest != '' && $opt{0} != '=' &&
+ $i + 1 < count($long_options) &&
+ $opt == substr($long_options[$i+1], 0, $opt_len) &&
+ $next_option_rest != '' &&
+ $next_option_rest{0} != '=') {
+
+ $msg = "Console_Getopt: option --$opt is ambiguous";
+ return PEAR::raiseError($msg);
+ }
+
+ if (substr($long_opt, -1) == '=') {
+ if (substr($long_opt, -2) != '==') {
+ /* Long option requires an argument.
+ Take the next argument if one wasn't specified. */;
+ if (!strlen($opt_arg) && !(list(, $opt_arg) = each($args))) {
+ $msg = "Console_Getopt: option requires an argument --$opt";
+ return PEAR::raiseError($msg);
+ }
+
+ if (Console_Getopt::_isShortOpt($opt_arg)
+ || Console_Getopt::_isLongOpt($opt_arg)) {
+ $msg = "Console_Getopt: option requires an argument --$opt";
+ return PEAR::raiseError($msg);
+ }
+ }
+ } else if ($opt_arg) {
+ $msg = "Console_Getopt: option --$opt doesn't allow an argument";
+ return PEAR::raiseError($msg);
+ }
+
+ $opts[] = array('--' . $opt, $opt_arg);
+ return;
+ }
+
+ if ($skip_unknown === true) {
+ return;
+ }
+
+ return PEAR::raiseError("Console_Getopt: unrecognized option --$opt");
+ }
+
+ /**
+ * Safely read the $argv PHP array across different PHP configurations.
+ * Will take care on register_globals and register_argc_argv ini directives
+ *
+ * @return mixed the $argv PHP array or PEAR error if not registered
+ */
+ public static function readPHPArgv()
+ {
+ global $argv;
+ if (!is_array($argv)) {
+ if (!@is_array($_SERVER['argv'])) {
+ if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
+ $msg = "Could not read cmd args (register_argc_argv=Off?)";
+ return PEAR::raiseError("Console_Getopt: " . $msg);
+ }
+ return $GLOBALS['HTTP_SERVER_VARS']['argv'];
+ }
+ return $_SERVER['argv'];
+ }
+ return $argv;
+ }
+
+}
diff --git a/includes/pear/HTTP.php b/includes/pear/HTTP.php
new file mode 100644
index 0000000..6ebd6db
--- /dev/null
+++ b/includes/pear/HTTP.php
@@ -0,0 +1,548 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * HTTP
+ *
+ * PHP versions 4 and 5
+ *
+ * @category HTTP
+ * @package HTTP
+ * @author Stig Bakken <ssb@fast.no>
+ * @author Sterling Hughes <sterling@php.net>
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Richard Heyes <richard@php.net>
+ * @author Philippe Jausions <jausions@php.net>
+ * @author Michael Wallner <mike@php.net>
+ * @copyright 2002-2008 The Authors
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: HTTP.php,v 1.56 2008/08/31 20:15:43 jausions Exp $
+ * @link http://pear.php.net/package/HTTP
+ */
+
+/**
+ * Miscellaneous HTTP Utilities
+ *
+ * PEAR::HTTP provides static shorthand methods for generating HTTP dates,
+ * issueing HTTP HEAD requests, building absolute URIs, firing redirects and
+ * negotiating user preferred language.
+ *
+ * @category HTTP
+ * @package HTTP
+ * @author Stig Bakken <ssb@fast.no>
+ * @author Sterling Hughes <sterling@php.net>
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Richard Heyes <richard@php.net>
+ * @author Philippe Jausions <jausions@php.net>
+ * @author Michael Wallner <mike@php.net>
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @abstract
+ * @version Release: $Revision: 1.56 $
+ * @link http://pear.php.net/package/HTTP
+ */
+class HTTP
+{
+ /**
+ * Formats a RFC compliant GMT date HTTP header. This function honors the
+ * "y2k_compliance" php.ini directive and formats the GMT date corresponding
+ * to either RFC850 or RFC822.
+ *
+ * @param mixed $time unix timestamp or date (default = current time)
+ *
+ * @return mixed GMT date string, or false for an invalid $time parameter
+ * @access public
+ * @static
+ */
+ function Date($time = null)
+ {
+ if (!isset($time)) {
+ $time = time();
+ } elseif (!is_numeric($time) && (-1 === $time = strtotime($time))) {
+ return false;
+ }
+
+ // RFC822 or RFC850
+ $format = ini_get('y2k_compliance') ? 'D, d M Y' : 'l, d-M-y';
+
+ return gmdate($format .' H:i:s \G\M\T', $time);
+ }
+
+ /**
+ * Negotiates language with the user's browser through the Accept-Language
+ * HTTP header or the user's host address. Language codes are generally in
+ * the form "ll" for a language spoken in only one country, or "ll-CC" for a
+ * language spoken in a particular country. For example, U.S. English is
+ * "en-US", while British English is "en-UK". Portugese as spoken in
+ * Portugal is "pt-PT", while Brazilian Portugese is "pt-BR".
+ *
+ * Quality factors in the Accept-Language: header are supported, e.g.:
+ * Accept-Language: en-UK;q=0.7, en-US;q=0.6, no, dk;q=0.8
+ *
+ * <code>
+ * require_once 'HTTP.php';
+ * $langs = array(
+ * 'en' => 'locales/en',
+ * 'en-US' => 'locales/en',
+ * 'en-UK' => 'locales/en',
+ * 'de' => 'locales/de',
+ * 'de-DE' => 'locales/de',
+ * 'de-AT' => 'locales/de',
+ * );
+ * $neg = HTTP::negotiateLanguage($langs);
+ * $dir = $langs[$neg];
+ * </code>
+ *
+ * @param array $supported An associative array of supported languages,
+ * whose values must evaluate to true.
+ * @param string $default The default language to use if none is found.
+ *
+ * @return string The negotiated language result or the supplied default.
+ * @static
+ * @access public
+ */
+ function negotiateLanguage($supported, $default = 'en-US')
+ {
+ $supp = array();
+ foreach ($supported as $lang => $isSupported) {
+ if ($isSupported) {
+ $supp[strtolower($lang)] = $lang;
+ }
+ }
+
+ if (!count($supp)) {
+ return $default;
+ }
+
+ if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+ $match = HTTP::_matchAccept($_SERVER['HTTP_ACCEPT_LANGUAGE'],
+ $supp);
+ if (!is_null($match)) {
+ return $match;
+ }
+ }
+
+ if (isset($_SERVER['REMOTE_HOST'])) {
+ $lang = strtolower(end($h = explode('.', $_SERVER['REMOTE_HOST'])));
+ if (isset($supp[$lang])) {
+ return $supp[$lang];
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Negotiates charset with the user's browser through the Accept-Charset
+ * HTTP header.
+ *
+ * Quality factors in the Accept-Charset: header are supported, e.g.:
+ * Accept-Language: en-UK;q=0.7, en-US;q=0.6, no, dk;q=0.8
+ *
+ * <code>
+ * require_once 'HTTP.php';
+ * $charsets = array(
+ * 'UTF-8',
+ * 'ISO-8859-1',
+ * );
+ * $charset = HTTP::negotiateCharset($charsets);
+ * </code>
+ *
+ * @param array $supported An array of supported charsets
+ * @param string $default The default charset to use if none is found.
+ *
+ * @return string The negotiated language result or the supplied default.
+ * @static
+ * @author Philippe Jausions <jausions@php.net>
+ * @access public
+ * @since 1.4.1
+ */
+ function negotiateCharset($supported, $default = 'ISO-8859-1')
+ {
+ $supp = array();
+ foreach ($supported as $charset) {
+ $supp[strtolower($charset)] = $charset;
+ }
+
+ if (!count($supp)) {
+ return $default;
+ }
+
+ if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) {
+ $match = HTTP::_matchAccept($_SERVER['HTTP_ACCEPT_CHARSET'],
+ $supp);
+ if (!is_null($match)) {
+ return $match;
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Negotiates content type with the user's browser through the Accept
+ * HTTP header.
+ *
+ * Quality factors in the Accept: header are supported, e.g.:
+ * Accept: application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8
+ *
+ * <code>
+ * require_once 'HTTP.php';
+ * $contentType = array(
+ * 'application/xhtml+xml',
+ * 'application/xml',
+ * 'text/html',
+ * 'text/plain',
+ * );
+ * $mime = HTTP::negotiateContentType($contentType);
+ * </code>
+ *
+ * @param array $supported An associative array of supported MIME types.
+ * @param string $default The default type to use if none match.
+ *
+ * @return string The negotiated MIME type result or the supplied default.
+ * @static
+ * @author Philippe Jausions <jausions@php.net>
+ * @access public
+ * @since 1.4.1
+ */
+ function negotiateMimeType($supported, $default)
+ {
+ $supp = array();
+ foreach ($supported as $type) {
+ $supp[strtolower($type)] = $type;
+ }
+
+ if (!count($supp)) {
+ return $default;
+ }
+
+ if (isset($_SERVER['HTTP_ACCEPT'])) {
+ $accepts = HTTP::_sortAccept($_SERVER['HTTP_ACCEPT']);
+
+ foreach ($accepts as $type => $q) {
+ if (substr($type, -2) != '/*') {
+ if (isset($supp[$type])) {
+ return $supp[$type];
+ }
+ continue;
+ }
+ if ($type == '*/*') {
+ return array_shift($supp);
+ }
+ list($general, $specific) = explode('/', $type);
+ $general .= '/';
+ $len = strlen($general);
+ foreach ($supp as $mime => $t) {
+ if (strncasecmp($general, $mime, $len) == 0) {
+ return $t;
+ }
+ }
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Parses a weighed "Accept" HTTP header and matches it against a list
+ * of supported options
+ *
+ * @param string $header The HTTP "Accept" header to parse
+ * @param array $supported A list of supported values
+ *
+ * @return string|NULL a matched option, or NULL if no match
+ * @access private
+ * @static
+ */
+ function _matchAccept($header, $supported)
+ {
+ $matches = HTTP::_sortAccept($header);
+ foreach ($matches as $key => $q) {
+ if (isset($supported[$key])) {
+ return $supported[$key];
+ }
+ }
+ // If any (i.e. "*") is acceptable, return the first supported format
+ if (isset($matches['*'])) {
+ return array_shift($supported);
+ }
+ return null;
+ }
+
+ /**
+ * Parses and sorts a weighed "Accept" HTTP header
+ *
+ * @param string $header The HTTP "Accept" header to parse
+ *
+ * @return array a sorted list of "accept" options
+ * @access private
+ * @static
+ */
+ function _sortAccept($header)
+ {
+ $matches = array();
+ foreach (explode(',', $header) as $option) {
+ $option = array_map('trim', explode(';', $option));
+
+ $l = strtolower($option[0]);
+ if (isset($option[1])) {
+ $q = (float) str_replace('q=', '', $option[1]);
+ } else {
+ $q = null;
+ // Assign default low weight for generic values
+ if ($l == '*/*') {
+ $q = 0.01;
+ } elseif (substr($l, -1) == '*') {
+ $q = 0.02;
+ }
+ }
+ // Unweighted values, get high weight by their position in the
+ // list
+ $matches[$l] = isset($q) ? $q : 1000 - count($matches);
+ }
+ arsort($matches, SORT_NUMERIC);
+ return $matches;
+ }
+
+ /**
+ * Sends a "HEAD" HTTP command to a server and returns the headers
+ * as an associative array.
+ *
+ * Example output could be:
+ * <code>
+ * Array
+ * (
+ * [response_code] => 200 // The HTTP response code
+ * [response] => HTTP/1.1 200 OK // The full HTTP response string
+ * [Date] => Fri, 11 Jan 2002 01:41:44 GMT
+ * [Server] => Apache/1.3.20 (Unix) PHP/4.1.1
+ * [X-Powered-By] => PHP/4.1.1
+ * [Connection] => close
+ * [Content-Type] => text/html
+ * )
+ * </code>
+ *
+ * @param string $url A valid URL, e.g.: http://pear.php.net/credits.php
+ * @param integer $timeout Timeout in seconds (default = 10)
+ *
+ * @return array Returns associative array of response headers on success
+ * or PEAR error on failure.
+ * @static
+ * @access public
+ * @see HTTP_Client::head()
+ * @see HTTP_Request
+ */
+ function head($url, $timeout = 10)
+ {
+ $p = parse_url($url);
+ if (!isset($p['scheme'])) {
+ $p = parse_url(HTTP::absoluteURI($url));
+ } elseif ($p['scheme'] != 'http') {
+ return HTTP::raiseError('Unsupported protocol: '. $p['scheme']);
+ }
+
+ $port = isset($p['port']) ? $p['port'] : 80;
+
+ if (!$fp = @fsockopen($p['host'], $port, $eno, $estr, $timeout)) {
+ return HTTP::raiseError("Connection error: $estr ($eno)");
+ }
+
+ $path = !empty($p['path']) ? $p['path'] : '/';
+ $path .= !empty($p['query']) ? '?' . $p['query'] : '';
+
+ fputs($fp, "HEAD $path HTTP/1.0\r\n");
+ fputs($fp, 'Host: ' . $p['host'] . ':' . $port . "\r\n");
+ fputs($fp, "Connection: close\r\n\r\n");
+
+ $response = rtrim(fgets($fp, 4096));
+ if (preg_match("|^HTTP/[^\s]*\s(.*?)\s|", $response, $status)) {
+ $headers['response_code'] = $status[1];
+ }
+ $headers['response'] = $response;
+
+ while ($line = fgets($fp, 4096)) {
+ if (!trim($line)) {
+ break;
+ }
+ if (($pos = strpos($line, ':')) !== false) {
+ $header = substr($line, 0, $pos);
+ $value = trim(substr($line, $pos + 1));
+
+ $headers[$header] = $value;
+ }
+ }
+ fclose($fp);
+ return $headers;
+ }
+
+ /**
+ * This function redirects the client. This is done by issuing
+ * a "Location" header and exiting if wanted. If you set $rfc2616 to true
+ * HTTP will output a hypertext note with the location of the redirect.
+ *
+ * @param string $url URL where the redirect should go to.
+ * @param bool $exit Whether to exit immediately after redirection.
+ * @param bool $rfc2616 Wheter to output a hypertext note where we're
+ * redirecting to (Redirecting to
+ * <a href="...">...</a>.)
+ *
+ * @return boolean Returns TRUE on succes (or exits) or FALSE if headers
+ * have already been sent.
+ * @static
+ * @access public
+ */
+ function redirect($url, $exit = true, $rfc2616 = false)
+ {
+ if (headers_sent()) {
+ return false;
+ }
+
+ $url = HTTP::absoluteURI($url);
+ header('Location: '. $url);
+
+ if ($rfc2616 && isset($_SERVER['REQUEST_METHOD'])
+ && $_SERVER['REQUEST_METHOD'] != 'HEAD') {
+ echo '
+<p>Redirecting to: <a href="'.str_replace('"', '%22', $url).'">'
+ .htmlspecialchars($url).'</a>.</p>
+<script type="text/javascript">
+//<![CDATA[
+if (location.replace == null) {
+ location.replace = location.assign;
+}
+location.replace("'.str_replace('"', '\\"', $url).'");
+// ]]>
+</script>';
+ }
+ if ($exit) {
+ exit;
+ }
+ return true;
+ }
+
+ /**
+ * This function returns the absolute URI for the partial URL passed.
+ * The current scheme (HTTP/HTTPS), host server, port, current script
+ * location are used if necessary to resolve any relative URLs.
+ *
+ * Offsets potentially created by PATH_INFO are taken care of to resolve
+ * relative URLs to the current script.
+ *
+ * You can choose a new protocol while resolving the URI. This is
+ * particularly useful when redirecting a web browser using relative URIs
+ * and to switch from HTTP to HTTPS, or vice-versa, at the same time.
+ *
+ * @param string $url Absolute or relative URI the redirect should
+ * go to.
+ * @param string $protocol Protocol to use when redirecting URIs.
+ * @param integer $port A new port number.
+ *
+ * @return string The absolute URI.
+ * @author Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @static
+ * @access public
+ */
+ function absoluteURI($url = null, $protocol = null, $port = null)
+ {
+ // filter CR/LF
+ $url = str_replace(array("\r", "\n"), ' ', $url);
+
+ // Mess around protocol and port with already absolute URIs
+ if (preg_match('!^([a-z0-9]+)://!i', $url)) {
+ if (empty($protocol) && empty($port)) {
+ return $url;
+ }
+ if (!empty($protocol)) {
+ $url = $protocol .':'. end($array = explode(':', $url, 2));
+ }
+ if (!empty($port)) {
+ $url = preg_replace('!^(([a-z0-9]+)://[^/:]+)(:[\d]+)?!i',
+ '\1:'. $port, $url);
+ }
+ return $url;
+ }
+
+ $host = 'localhost';
+ if (!empty($_SERVER['HTTP_HOST'])) {
+ list($host) = explode(':', $_SERVER['HTTP_HOST']);
+ } elseif (!empty($_SERVER['SERVER_NAME'])) {
+ list($host) = explode(':', $_SERVER['SERVER_NAME']);
+ }
+
+ if (empty($protocol)) {
+ if (isset($_SERVER['HTTPS']) && !strcasecmp($_SERVER['HTTPS'], 'on')) {
+ $protocol = 'https';
+ } else {
+ $protocol = 'http';
+ }
+ if (!isset($port) || $port != intval($port)) {
+ $port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
+ }
+ }
+
+ if ($protocol == 'http' && $port == 80) {
+ unset($port);
+ }
+ if ($protocol == 'https' && $port == 443) {
+ unset($port);
+ }
+
+ $server = $protocol.'://'.$host.(isset($port) ? ':'.$port : '');
+
+ $uriAll = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI']
+ : $_SERVER['PHP_SELF'];
+ if (false !== ($q = strpos($uriAll, '?'))) {
+ $uriBase = substr($uriAll, 0, $q);
+ } else {
+ $uriBase = $uriAll;
+ }
+ if (!strlen($url) || $url{0} == '#') {
+ $url = $uriAll.$url;
+ } elseif ($url{0} == '?') {
+ $url = $uriBase.$url;
+ }
+ if ($url{0} == '/') {
+ return $server . $url;
+ }
+
+ // Adjust for PATH_INFO if needed
+ if (isset($_SERVER['PATH_INFO']) && strlen($_SERVER['PATH_INFO'])) {
+ $path = dirname(substr($uriBase, 0,
+ -strlen($_SERVER['PATH_INFO'])));
+ } else {
+ /**
+ * Fixes bug #12672 PHP_SELF ending on / causes incorrect redirects
+ *
+ * @link http://pear.php.net/bugs/12672
+ */
+ $path = dirname($uriBase.'-');
+ }
+
+ if (substr($path = strtr($path, '\\', '/'), -1) != '/') {
+ $path .= '/';
+ }
+
+ return $server . $path . $url;
+ }
+
+ /**
+ * Raise Error
+ *
+ * Lazy raising of PEAR_Errors.
+ *
+ * @param mixed $error Error
+ * @param integer $code Error code
+ *
+ * @return object PEAR_Error
+ * @static
+ * @access protected
+ */
+ function raiseError($error = null, $code = null)
+ {
+ include_once 'PEAR.php';
+ return PEAR::raiseError($error, $code);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/HTTP/Download.php b/includes/pear/HTTP/Download.php
new file mode 100644
index 0000000..8cfc348
--- /dev/null
+++ b/includes/pear/HTTP/Download.php
@@ -0,0 +1,1243 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * HTTP::Download
+ *
+ * PHP versions 4 and 5
+ *
+ * @category HTTP
+ * @package HTTP_Download
+ * @author Michael Wallner <mike@php.net>
+ * @copyright 2003-2005 Michael Wallner
+ * @license BSD, revised
+ * @version CVS: $Id: Download.php 304423 2010-10-15 13:36:46Z clockwerx $
+ * @link http://pear.php.net/package/HTTP_Download
+ */
+
+// {{{ includes
+/**
+ * Requires PEAR
+ */
+require_once 'PEAR.php';
+
+/**
+ * Requires HTTP_Header
+ */
+require_once 'HTTP/Header.php';
+// }}}
+
+// {{{ constants
+/**#@+ Use with HTTP_Download::setContentDisposition() **/
+/**
+ * Send data as attachment
+ */
+define('HTTP_DOWNLOAD_ATTACHMENT', 'attachment');
+/**
+ * Send data inline
+ */
+define('HTTP_DOWNLOAD_INLINE', 'inline');
+/**#@-**/
+
+/**#@+ Use with HTTP_Download::sendArchive() **/
+/**
+ * Send as uncompressed tar archive
+ */
+define('HTTP_DOWNLOAD_TAR', 'TAR');
+/**
+ * Send as gzipped tar archive
+ */
+define('HTTP_DOWNLOAD_TGZ', 'TGZ');
+/**
+ * Send as bzip2 compressed tar archive
+ */
+define('HTTP_DOWNLOAD_BZ2', 'BZ2');
+/**
+ * Send as zip archive
+ */
+define('HTTP_DOWNLOAD_ZIP', 'ZIP');
+/**#@-**/
+
+/**#@+
+ * Error constants
+ */
+define('HTTP_DOWNLOAD_E_HEADERS_SENT', -1);
+define('HTTP_DOWNLOAD_E_NO_EXT_ZLIB', -2);
+define('HTTP_DOWNLOAD_E_NO_EXT_MMAGIC', -3);
+define('HTTP_DOWNLOAD_E_INVALID_FILE', -4);
+define('HTTP_DOWNLOAD_E_INVALID_PARAM', -5);
+define('HTTP_DOWNLOAD_E_INVALID_RESOURCE', -6);
+define('HTTP_DOWNLOAD_E_INVALID_REQUEST', -7);
+define('HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE', -8);
+define('HTTP_DOWNLOAD_E_INVALID_ARCHIVE_TYPE', -9);
+/**#@-**/
+// }}}
+
+/**
+ * Send HTTP Downloads/Responses.
+ *
+ * With this package you can handle (hidden) downloads.
+ * It supports partial downloads, resuming and sending
+ * raw data ie. from database BLOBs.
+ *
+ * <i>ATTENTION:</i>
+ * You shouldn't use this package together with ob_gzhandler or
+ * zlib.output_compression enabled in your php.ini, especially
+ * if you want to send already gzipped data!
+ *
+ * @access public
+ * @version $Revision: 304423 $
+ */
+class HTTP_Download
+{
+ // {{{ protected member variables
+ /**
+ * Path to file for download
+ *
+ * @see HTTP_Download::setFile()
+ * @access protected
+ * @var string
+ */
+ var $file = '';
+
+ /**
+ * Data for download
+ *
+ * @see HTTP_Download::setData()
+ * @access protected
+ * @var string
+ */
+ var $data = null;
+
+ /**
+ * Resource handle for download
+ *
+ * @see HTTP_Download::setResource()
+ * @access protected
+ * @var int
+ */
+ var $handle = null;
+
+ /**
+ * Whether to gzip the download
+ *
+ * @access protected
+ * @var bool
+ */
+ var $gzip = false;
+
+ /**
+ * Whether to allow caching of the download on the clients side
+ *
+ * @access protected
+ * @var bool
+ */
+ var $cache = true;
+
+ /**
+ * Size of download
+ *
+ * @access protected
+ * @var int
+ */
+ var $size = 0;
+
+ /**
+ * Last modified
+ *
+ * @access protected
+ * @var int
+ */
+ var $lastModified = 0;
+
+ /**
+ * HTTP headers
+ *
+ * @access protected
+ * @var array
+ */
+ var $headers = array(
+ 'Content-Type' => 'application/x-octetstream',
+ 'Pragma' => 'cache',
+ 'Cache-Control' => 'public, must-revalidate, max-age=0',
+ 'Accept-Ranges' => 'bytes',
+ 'X-Sent-By' => 'PEAR::HTTP::Download'
+ );
+
+ /**
+ * HTTP_Header
+ *
+ * @access protected
+ * @var object
+ */
+ var $HTTP = null;
+
+ /**
+ * ETag
+ *
+ * @access protected
+ * @var string
+ */
+ var $etag = '';
+
+ /**
+ * Buffer Size
+ *
+ * @access protected
+ * @var int
+ */
+ var $bufferSize = 2097152;
+
+ /**
+ * Throttle Delay
+ *
+ * @access protected
+ * @var float
+ */
+ var $throttleDelay = 0;
+
+ /**
+ * Sent Bytes
+ *
+ * @access public
+ * @var int
+ */
+ var $sentBytes = 0;
+
+ /**
+ * Startup error
+ *
+ * @var PEAR_Error
+ * @access protected
+ */
+ var $_error = null;
+ // }}}
+
+ // {{{ constructor
+ /**
+ * Constructor
+ *
+ * Set supplied parameters.
+ *
+ * @access public
+ * @param array $params associative array of parameters
+ * <strong>one of:</strong>
+ * <ul>
+ * <li>'file' => path to file for download</li>
+ * <li>'data' => raw data for download</li>
+ * <li>'resource' => resource handle for download</li>
+ * </ul>
+ * <strong>and any of:</strong>
+ * <ul>
+ * <li>'cache' => whether to allow cs caching</li>
+ * <li>'gzip' => whether to gzip the download</li>
+ * <li>'lastmodified' => unix timestamp</li>
+ * <li>'contenttype' => content type of download</li>
+ * <li>'contentdisposition' => content disposition</li>
+ * <li>'buffersize' => amount of bytes to buffer</li>
+ * <li>'throttledelay' => amount of secs to sleep</li>
+ * <li>'cachecontrol' => cache privacy and validity</li>
+ * </ul>
+ *
+ * 'Content-Disposition' is not HTTP compliant, but most browsers
+ * follow this header, so it was borrowed from MIME standard.
+ *
+ * It looks like this:
+ * "Content-Disposition: attachment; filename=example.tgz".
+ *
+ * @see HTTP_Download::setContentDisposition()
+ */
+ function HTTP_Download($params = array())
+ {
+ $this->HTTP = &new HTTP_Header;
+ $this->_error = $this->setParams($params);
+ }
+ // }}}
+
+ // {{{ public methods
+ /**
+ * Set parameters
+ *
+ * Set supplied parameters through its accessor methods.
+ *
+ * @access public
+ * @return mixed Returns true on success or PEAR_Error on failure.
+ * @param array $params associative array of parameters
+ *
+ * @see HTTP_Download::HTTP_Download()
+ */
+ function setParams($params)
+ {
+ $error = $this->_getError();
+ if ($error !== null) {
+ return $error;
+ }
+ foreach((array) $params as $param => $value){
+ $method = 'set'. $param;
+
+ if (!method_exists($this, $method)) {
+ return PEAR::raiseError(
+ "Method '$method' doesn't exist.",
+ HTTP_DOWNLOAD_E_INVALID_PARAM
+ );
+ }
+
+ $e = call_user_func_array(array(&$this, $method), (array) $value);
+
+ if (PEAR::isError($e)) {
+ return $e;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Set path to file for download
+ *
+ * The Last-Modified header will be set to files filemtime(), actually.
+ * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_FILE) if file doesn't exist.
+ * Sends HTTP 404 or 403 status if $send_error is set to true.
+ *
+ * @access public
+ * @return mixed Returns true on success or PEAR_Error on failure.
+ * @param string $file path to file for download
+ * @param bool $send_error whether to send HTTP/404 or 403 if
+ * the file wasn't found or is not readable
+ */
+ function setFile($file, $send_error = true)
+ {
+ $error = $this->_getError();
+ if ($error !== null) {
+ return $error;
+ }
+ $file = realpath($file);
+ if (!is_file($file)) {
+ if ($send_error) {
+ $this->HTTP->sendStatusCode(404);
+ }
+ return PEAR::raiseError(
+ "File '$file' not found.",
+ HTTP_DOWNLOAD_E_INVALID_FILE
+ );
+ }
+ if (!is_readable($file)) {
+ if ($send_error) {
+ $this->HTTP->sendStatusCode(403);
+ }
+ return PEAR::raiseError(
+ "Cannot read file '$file'.",
+ HTTP_DOWNLOAD_E_INVALID_FILE
+ );
+ }
+ $this->setLastModified(filemtime($file));
+ $this->file = $file;
+ $this->size = filesize($file);
+ return true;
+ }
+
+ /**
+ * Set data for download
+ *
+ * Set $data to null if you want to unset this.
+ *
+ * @access public
+ * @return void
+ * @param $data raw data to send
+ */
+ function setData($data = null)
+ {
+ $this->data = $data;
+ $this->size = strlen($data);
+ }
+
+ /**
+ * Set resource for download
+ *
+ * The resource handle supplied will be closed after sending the download.
+ * Returns a PEAR_Error (HTTP_DOWNLOAD_E_INVALID_RESOURCE) if $handle
+ * is no valid resource. Set $handle to null if you want to unset this.
+ *
+ * @access public
+ * @return mixed Returns true on success or PEAR_Error on failure.
+ * @param int $handle resource handle
+ */
+ function setResource($handle = null)
+ {
+ $error = $this->_getError();
+ if ($error !== null) {
+ return $error;
+ }
+ if (!isset($handle)) {
+ $this->handle = null;
+ $this->size = 0;
+ return true;
+ }
+
+ if (is_resource($handle)) {
+ $this->handle = $handle;
+ $filestats = fstat($handle);
+ $this->size = isset($filestats['size']) ? $filestats['size']
+ : -1;
+ return true;
+ }
+
+ return PEAR::raiseError(
+ "Handle '$handle' is no valid resource.",
+ HTTP_DOWNLOAD_E_INVALID_RESOURCE
+ );
+ }
+
+ /**
+ * Whether to gzip the download
+ *
+ * Returns a PEAR_Error (HTTP_DOWNLOAD_E_NO_EXT_ZLIB)
+ * if ext/zlib is not available/loadable.
+ *
+ * @access public
+ * @return mixed Returns true on success or PEAR_Error on failure.
+ * @param bool $gzip whether to gzip the download
+ */
+ function setGzip($gzip = false)
+ {
+ $error = $this->_getError();
+ if ($error !== null) {
+ return $error;
+ }
+ if ($gzip && !PEAR::loadExtension('zlib')){
+ return PEAR::raiseError(
+ 'GZIP compression (ext/zlib) not available.',
+ HTTP_DOWNLOAD_E_NO_EXT_ZLIB
+ );
+ }
+ $this->gzip = (bool) $gzip;
+ return true;
+ }
+
+ /**
+ * Whether to allow caching
+ *
+ * If set to true (default) we'll send some headers that are commonly
+ * used for caching purposes like ETag, Cache-Control and Last-Modified.
+ *
+ * If caching is disabled, we'll send the download no matter if it
+ * would actually be cached at the client side.
+ *
+ * @access public
+ * @return void
+ * @param bool $cache whether to allow caching
+ */
+ function setCache($cache = true)
+ {
+ $this->cache = (bool) $cache;
+ }
+
+ /**
+ * Whether to allow proxies to cache
+ *
+ * If set to 'private' proxies shouldn't cache the response.
+ * This setting defaults to 'public' and affects only cached responses.
+ *
+ * @access public
+ * @return bool
+ * @param string $cache private or public
+ * @param int $maxage maximum age of the client cache entry
+ */
+ function setCacheControl($cache = 'public', $maxage = 0)
+ {
+ switch ($cache = strToLower($cache))
+ {
+ case 'private':
+ case 'public':
+ $this->headers['Cache-Control'] =
+ $cache .', must-revalidate, max-age='. abs($maxage);
+ return true;
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * Set ETag
+ *
+ * Sets a user-defined ETag for cache-validation. The ETag is usually
+ * generated by HTTP_Download through its payload information.
+ *
+ * @access public
+ * @return void
+ * @param string $etag Entity tag used for strong cache validation.
+ */
+ function setETag($etag = null)
+ {
+ $this->etag = (string) $etag;
+ }
+
+ /**
+ * Set Size of Buffer
+ *
+ * The amount of bytes specified as buffer size is the maximum amount
+ * of data read at once from resources or files. The default size is 2M
+ * (2097152 bytes). Be aware that if you enable gzip compression and
+ * you set a very low buffer size that the actual file size may grow
+ * due to added gzip headers for each sent chunk of the specified size.
+ *
+ * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_PARAM) if $size is not
+ * greater than 0 bytes.
+ *
+ * @access public
+ * @return mixed Returns true on success or PEAR_Error on failure.
+ * @param int $bytes Amount of bytes to use as buffer.
+ */
+ function setBufferSize($bytes = 2097152)
+ {
+ $error = $this->_getError();
+ if ($error !== null) {
+ return $error;
+ }
+ if (0 >= $bytes) {
+ return PEAR::raiseError(
+ 'Buffer size must be greater than 0 bytes ('. $bytes .' given)',
+ HTTP_DOWNLOAD_E_INVALID_PARAM);
+ }
+ $this->bufferSize = abs($bytes);
+ return true;
+ }
+
+ /**
+ * Set Throttle Delay
+ *
+ * Set the amount of seconds to sleep after each chunck that has been
+ * sent. One can implement some sort of throttle through adjusting the
+ * buffer size and the throttle delay. With the following settings
+ * HTTP_Download will sleep a second after each 25 K of data sent.
+ *
+ * <code>
+ * Array(
+ * 'throttledelay' => 1,
+ * 'buffersize' => 1024 * 25,
+ * )
+ * </code>
+ *
+ * Just be aware that if gzipp'ing is enabled, decreasing the chunk size
+ * too much leads to proportionally increased network traffic due to added
+ * gzip header and bottom bytes around each chunk.
+ *
+ * @access public
+ * @return void
+ * @param float $seconds Amount of seconds to sleep after each
+ * chunk that has been sent.
+ */
+ function setThrottleDelay($seconds = 0)
+ {
+ $this->throttleDelay = abs($seconds) * 1000;
+ }
+
+ /**
+ * Set "Last-Modified"
+ *
+ * This is usually determined by filemtime() in HTTP_Download::setFile()
+ * If you set raw data for download with HTTP_Download::setData() and you
+ * want do send an appropiate "Last-Modified" header, you should call this
+ * method.
+ *
+ * @access public
+ * @return void
+ * @param int unix timestamp
+ */
+ function setLastModified($last_modified)
+ {
+ $this->lastModified = $this->headers['Last-Modified'] = (int) $last_modified;
+ }
+
+ /**
+ * Set Content-Disposition header
+ *
+ * @see HTTP_Download::HTTP_Download
+ *
+ * @access public
+ * @return void
+ * @param string $disposition whether to send the download
+ * inline or as attachment
+ * @param string $file_name the filename to display in
+ * the browser's download window
+ *
+ * <b>Example:</b>
+ * <code>
+ * $HTTP_Download->setContentDisposition(
+ * HTTP_DOWNLOAD_ATTACHMENT,
+ * 'download.tgz'
+ * );
+ * </code>
+ */
+ function setContentDisposition( $disposition = HTTP_DOWNLOAD_ATTACHMENT,
+ $file_name = null)
+ {
+ $cd = $disposition;
+ if (isset($file_name)) {
+ $cd .= '; filename="' . $file_name . '"';
+ } elseif ($this->file) {
+ $cd .= '; filename="' . basename($this->file) . '"';
+ }
+ $this->headers['Content-Disposition'] = $cd;
+ }
+
+ /**
+ * Set content type of the download
+ *
+ * Default content type of the download will be 'application/x-octetstream'.
+ * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE) if
+ * $content_type doesn't seem to be valid.
+ *
+ * @access public
+ * @return mixed Returns true on success or PEAR_Error on failure.
+ * @param string $content_type content type of file for download
+ */
+ function setContentType($content_type = 'application/x-octetstream')
+ {
+ $error = $this->_getError();
+ if ($error !== null) {
+ return $error;
+ }
+ if (!preg_match('/^[a-z]+\w*\/[a-z]+[\w.;= -]*$/', $content_type)) {
+ return PEAR::raiseError(
+ "Invalid content type '$content_type' supplied.",
+ HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE
+ );
+ }
+ $this->headers['Content-Type'] = $content_type;
+ return true;
+ }
+
+ /**
+ * Guess content type of file
+ *
+ * First we try to use PEAR::MIME_Type, if installed, to detect the content
+ * type, else we check if ext/mime_magic is loaded and properly configured.
+ *
+ * Returns PEAR_Error if:
+ * o if PEAR::MIME_Type failed to detect a proper content type
+ * (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE)
+ * o ext/magic.mime is not installed, or not properly configured
+ * (HTTP_DOWNLOAD_E_NO_EXT_MMAGIC)
+ * o mime_content_type() couldn't guess content type or returned
+ * a content type considered to be bogus by setContentType()
+ * (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE)
+ *
+ * @access public
+ * @return mixed Returns true on success or PEAR_Error on failure.
+ */
+ function guessContentType()
+ {
+ $error = $this->_getError();
+ if ($error !== null) {
+ return $error;
+ }
+ if (class_exists('MIME_Type') || @include_once 'MIME/Type.php') {
+ if (PEAR::isError($mime_type = MIME_Type::autoDetect($this->file))) {
+ return PEAR::raiseError($mime_type->getMessage(),
+ HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE);
+ }
+ return $this->setContentType($mime_type);
+ }
+ if (!function_exists('mime_content_type')) {
+ return PEAR::raiseError(
+ 'This feature requires ext/mime_magic!',
+ HTTP_DOWNLOAD_E_NO_EXT_MMAGIC
+ );
+ }
+ if (!is_file(ini_get('mime_magic.magicfile'))) {
+ return PEAR::raiseError(
+ 'ext/mime_magic is loaded but not properly configured!',
+ HTTP_DOWNLOAD_E_NO_EXT_MMAGIC
+ );
+ }
+ if (!$content_type = @mime_content_type($this->file)) {
+ return PEAR::raiseError(
+ 'Couldn\'t guess content type with mime_content_type().',
+ HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE
+ );
+ }
+ return $this->setContentType($content_type);
+ }
+
+ /**
+ * Send
+ *
+ * Returns PEAR_Error if:
+ * o HTTP headers were already sent (HTTP_DOWNLOAD_E_HEADERS_SENT)
+ * o HTTP Range was invalid (HTTP_DOWNLOAD_E_INVALID_REQUEST)
+ *
+ * @access public
+ * @return mixed Returns true on success or PEAR_Error on failure.
+ * @param bool $autoSetContentDisposition Whether to set the
+ * Content-Disposition header if it isn't already.
+ */
+ function send($autoSetContentDisposition = true)
+ {
+ $error = $this->_getError();
+ if ($error !== null) {
+ return $error;
+ }
+ if (headers_sent()) {
+ return PEAR::raiseError(
+ 'Headers already sent.',
+ HTTP_DOWNLOAD_E_HEADERS_SENT
+ );
+ }
+
+ if (!ini_get('safe_mode')) {
+ @set_time_limit(0);
+ }
+
+ if ($autoSetContentDisposition &&
+ !isset($this->headers['Content-Disposition'])) {
+ $this->setContentDisposition();
+ }
+
+ if ($this->cache) {
+ $this->headers['ETag'] = $this->generateETag();
+ if ($this->isCached()) {
+ $this->HTTP->sendStatusCode(304);
+ $this->sendHeaders();
+ return true;
+ }
+ } else {
+ unset($this->headers['Last-Modified']);
+ }
+
+ if (ob_get_level()) {
+ while (@ob_end_clean());
+ }
+
+ if ($this->gzip) {
+ @ob_start('ob_gzhandler');
+ } else {
+ ob_start();
+ }
+
+ $this->sentBytes = 0;
+
+ // Known content length?
+ $end = ($this->size >= 0) ? max($this->size - 1, 0) : '*';
+
+ if ($end != '*' && $this->isRangeRequest()) {
+ $chunks = $this->getChunks();
+ if (empty($chunks)) {
+ $this->HTTP->sendStatusCode(200);
+ $chunks = array(array(0, $end));
+
+ } elseif (PEAR::isError($chunks)) {
+ ob_end_clean();
+ $this->HTTP->sendStatusCode(416);
+ return $chunks;
+
+ } else {
+ $this->HTTP->sendStatusCode(206);
+ }
+ } else {
+ $this->HTTP->sendStatusCode(200);
+ $chunks = array(array(0, $end));
+ if (!$this->gzip && count(ob_list_handlers()) < 2 && $end != '*') {
+ $this->headers['Content-Length'] = $this->size;
+ }
+ }
+
+ $this->sendChunks($chunks);
+
+ ob_end_flush();
+ flush();
+ return true;
+ }
+
+ /**
+ * Static send
+ *
+ * @see HTTP_Download::HTTP_Download()
+ * @see HTTP_Download::send()
+ *
+ * @static
+ * @access public
+ * @return mixed Returns true on success or PEAR_Error on failure.
+ * @param array $params associative array of parameters
+ * @param bool $guess whether HTTP_Download::guessContentType()
+ * should be called
+ */
+ function staticSend($params, $guess = false)
+ {
+ $d = &new HTTP_Download();
+ $e = $d->setParams($params);
+ if (PEAR::isError($e)) {
+ return $e;
+ }
+ if ($guess) {
+ $e = $d->guessContentType();
+ if (PEAR::isError($e)) {
+ return $e;
+ }
+ }
+ return $d->send();
+ }
+
+ /**
+ * Send a bunch of files or directories as an archive
+ *
+ * Example:
+ * <code>
+ * require_once 'HTTP/Download.php';
+ * HTTP_Download::sendArchive(
+ * 'myArchive.tgz',
+ * '/var/ftp/pub/mike',
+ * HTTP_DOWNLOAD_TGZ,
+ * '',
+ * '/var/ftp/pub'
+ * );
+ * </code>
+ *
+ * @see Archive_Tar::createModify()
+ * @deprecated use HTTP_Download_Archive::send()
+ * @static
+ * @access public
+ * @return mixed Returns true on success or PEAR_Error on failure.
+ * @param string $name name the sent archive should have
+ * @param mixed $files files/directories
+ * @param string $type archive type
+ * @param string $add_path path that should be prepended to the files
+ * @param string $strip_path path that should be stripped from the files
+ */
+ function sendArchive( $name,
+ $files,
+ $type = HTTP_DOWNLOAD_TGZ,
+ $add_path = '',
+ $strip_path = '')
+ {
+ require_once 'HTTP/Download/Archive.php';
+ return HTTP_Download_Archive::send($name, $files, $type,
+ $add_path, $strip_path);
+ }
+ // }}}
+
+ // {{{ protected methods
+ /**
+ * Generate ETag
+ *
+ * @access protected
+ * @return string
+ */
+ function generateETag()
+ {
+ if (!$this->etag) {
+ if ($this->data) {
+ $md5 = md5($this->data);
+ } else {
+ $mtime = time();
+ $ino = 0;
+ $size = mt_rand();
+ extract(is_resource($this->handle) ? fstat($this->handle)
+ : stat($this->file));
+ $md5 = md5($mtime .'='. $ino .'='. $size);
+ }
+ $this->etag = '"' . $md5 . '-' . crc32($md5) . '"';
+ }
+ return $this->etag;
+ }
+
+ /**
+ * Send multiple chunks
+ *
+ * @access protected
+ * @return mixed Returns true on success or PEAR_Error on failure.
+ * @param array $chunks
+ */
+ function sendChunks($chunks)
+ {
+ if (count($chunks) == 1) {
+ return $this->sendChunk(current($chunks));
+ }
+
+ $bound = uniqid('HTTP_DOWNLOAD-', true);
+ $cType = $this->headers['Content-Type'];
+ $this->headers['Content-Type'] =
+ 'multipart/byteranges; boundary=' . $bound;
+ $this->sendHeaders();
+ foreach ($chunks as $chunk){
+ $this->sendChunk($chunk, $cType, $bound);
+ }
+ #echo "\r\n--$bound--\r\n";
+ return true;
+ }
+
+ /**
+ * Send chunk of data
+ *
+ * @access protected
+ * @return mixed Returns true on success or PEAR_Error on failure.
+ * @param array $chunk start and end offset of the chunk to send
+ * @param string $cType actual content type
+ * @param string $bound boundary for multipart/byteranges
+ */
+ function sendChunk($chunk, $cType = null, $bound = null)
+ {
+ list($offset, $lastbyte) = $chunk;
+ $length = ($lastbyte - $offset) + 1;
+
+ $range = $offset . '-' . $lastbyte . '/'
+ . (($this->size >= 0) ? $this->size : '*');
+
+ if (isset($cType, $bound)) {
+ echo "\r\n--$bound\r\n",
+ "Content-Type: $cType\r\n",
+ "Content-Range: bytes $range\r\n\r\n";
+ } else {
+ if ($lastbyte != '*' && $this->isRangeRequest()) {
+ $this->headers['Content-Length'] = $length;
+ $this->headers['Content-Range'] = 'bytes '. $range;
+ }
+ $this->sendHeaders();
+ }
+
+ if ($this->data) {
+ while (($length -= $this->bufferSize) > 0) {
+ $this->flush(substr($this->data, $offset, $this->bufferSize));
+ $this->throttleDelay and $this->sleep();
+ $offset += $this->bufferSize;
+ }
+ if ($length) {
+ $this->flush(substr($this->data, $offset, $this->bufferSize + $length));
+ }
+ } else {
+ if (!is_resource($this->handle)) {
+ $this->handle = fopen($this->file, 'rb');
+ }
+ fseek($this->handle, $offset);
+ if ($lastbyte == '*') {
+ while (!feof($this->handle)) {
+ $this->flush(fread($this->handle, $this->bufferSize));
+ $this->throttleDelay and $this->sleep();
+ }
+ } else {
+ while (($length -= $this->bufferSize) > 0) {
+ $this->flush(fread($this->handle, $this->bufferSize));
+ $this->throttleDelay and $this->sleep();
+ }
+ if ($length) {
+ $this->flush(fread($this->handle, $this->bufferSize + $length));
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Get chunks to send
+ *
+ * @access protected
+ * @return array Chunk list or PEAR_Error on invalid range request
+ * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
+ */
+ function getChunks()
+ {
+ $end = ($this->size >= 0) ? max($this->size - 1, 0) : '*';
+
+ // Trying to handle ranges on content with unknown length is too
+ // big of a mess (impossible to determine if a range is valid)
+ if ($end == '*') {
+ return array();
+ }
+
+ $ranges = $this->getRanges();
+ if (empty($ranges)) {
+ return array();
+ }
+
+ $parts = array();
+ $satisfiable = false;
+ foreach (explode(',', $ranges) as $chunk){
+ list($o, $e) = explode('-', trim($chunk));
+
+ // If the last-byte-pos value is present, it MUST be greater than
+ // or equal to the first-byte-pos in that byte-range-spec, or the
+ // byte- range-spec is syntactically invalid. The recipient of a
+ // byte-range- set that includes one or more syntactically invalid
+ // byte-range-spec values MUST ignore the header field that
+ // includes that byte-range- set.
+ if ($e !== '' && $o !== '' && $e < $o) {
+ return array();
+ }
+
+ // If the last-byte-pos value is absent, or if the value is
+ // greater than or equal to the current length of the entity-body,
+ // last-byte-pos is taken to be equal to one less than the current
+ // length of the entity- body in bytes.
+ if ($e === '' || $e > $end) {
+ $e = $end;
+ }
+
+ // A suffix-byte-range-spec is used to specify the suffix of the
+ // entity-body, of a length given by the suffix-length value. (That
+ // is, this form specifies the last N bytes of an entity-body.) If
+ // the entity is shorter than the specified suffix-length, the
+ // entire entity-body is used.
+ if ($o === '') {
+ // If a syntactically valid byte-range-set includes at least
+ // one suffix-byte-range-spec with a non-zero suffix-length,
+ // then the byte-range-set is satisfiable.
+ $satisfiable |= ($e != 0);
+
+ $o = max($this->size - $e, 0);
+ $e = $end;
+
+ } elseif ($o <= $end) {
+ // If a syntactically valid byte-range-set includes at least
+ // one byte- range-spec whose first-byte-pos is less than the
+ // current length of the entity-body, then the byte-range-set
+ // is satisfiable.
+ $satisfiable = true;
+ } else {
+ continue;
+ }
+
+ $parts[] = array($o, $e);
+ }
+
+ // If the byte-range-set is unsatisfiable, the server SHOULD return a
+ // response with a status of 416 (Requested range not satisfiable).
+ if (!$satisfiable) {
+ $error = PEAR::raiseError('Error processing range request',
+ HTTP_DOWNLOAD_E_INVALID_REQUEST);
+ return $error;
+ }
+ //$this->sortChunks($parts);
+ return $this->mergeChunks($parts);
+ }
+
+ /**
+ * Sorts the ranges to be in ascending order
+ *
+ * @param array &$chunks ranges to sort
+ *
+ * @return void
+ * @access protected
+ * @static
+ * @author Philippe Jausions <jausions@php.net>
+ */
+ function sortChunks(&$chunks)
+ {
+ $sortFunc = create_function('$a,$b',
+ 'if ($a[0] == $b[0]) {
+ if ($a[1] == $b[1]) {
+ return 0;
+ }
+ return (($a[1] != "*" && $a[1] < $b[1])
+ || $b[1] == "*") ? -1 : 1;
+ }
+
+ return ($a[0] < $b[0]) ? -1 : 1;');
+
+ usort($chunks, $sortFunc);
+ }
+
+ /**
+ * Merges consecutive chunks to avoid overlaps
+ *
+ * @param array $chunks Ranges to merge
+ *
+ * @return array merged ranges
+ * @access protected
+ * @static
+ * @author Philippe Jausions <jausions@php.net>
+ */
+ function mergeChunks($chunks)
+ {
+ do {
+ $count = count($chunks);
+ $merged = array(current($chunks));
+ $j = 0;
+ for ($i = 1; $i < count($chunks); ++$i) {
+ list($o, $e) = $chunks[$i];
+ if ($merged[$j][1] == '*') {
+ if ($merged[$j][0] <= $o) {
+ continue;
+ } elseif ($e == '*' || $merged[$j][0] <= $e) {
+ $merged[$j][0] = min($merged[$j][0], $o);
+ } else {
+ $merged[++$j] = $chunks[$i];
+ }
+ } elseif ($merged[$j][0] <= $o && $o <= $merged[$j][1]) {
+ $merged[$j][1] = ($e == '*') ? '*' : max($e, $merged[$j][1]);
+ } elseif ($merged[$j][0] <= $e && $e <= $merged[$j][1]) {
+ $merged[$j][0] = min($o, $merged[$j][0]);
+ } else {
+ $merged[++$j] = $chunks[$i];
+ }
+ }
+ if ($count == count($merged)) {
+ break;
+ }
+ $chunks = $merged;
+ } while (true);
+ return $merged;
+ }
+
+ /**
+ * Check if range is requested
+ *
+ * @access protected
+ * @return bool
+ */
+ function isRangeRequest()
+ {
+ if (!isset($_SERVER['HTTP_RANGE']) || !count($this->getRanges())) {
+ return false;
+ }
+ return $this->isValidRange();
+ }
+
+ /**
+ * Get range request
+ *
+ * @access protected
+ * @return array
+ */
+ function getRanges()
+ {
+ return preg_match('/^bytes=((\d+-|\d+-\d+|-\d+)(, ?(\d+-|\d+-\d+|-\d+))*)$/',
+ @$_SERVER['HTTP_RANGE'], $matches) ? $matches[1] : array();
+ }
+
+ /**
+ * Check if entity is cached
+ *
+ * @access protected
+ * @return bool
+ */
+ function isCached()
+ {
+ return (
+ (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
+ $this->lastModified == strtotime(current($a = explode(
+ ';', $_SERVER['HTTP_IF_MODIFIED_SINCE'])))) ||
+ (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
+ $this->compareAsterisk('HTTP_IF_NONE_MATCH', $this->etag))
+ );
+ }
+
+ /**
+ * Check if entity hasn't changed
+ *
+ * @access protected
+ * @return bool
+ */
+ function isValidRange()
+ {
+ if (isset($_SERVER['HTTP_IF_MATCH']) &&
+ !$this->compareAsterisk('HTTP_IF_MATCH', $this->etag)) {
+ return false;
+ }
+ if (isset($_SERVER['HTTP_IF_RANGE']) &&
+ $_SERVER['HTTP_IF_RANGE'] !== $this->etag &&
+ strtotime($_SERVER['HTTP_IF_RANGE']) !== $this->lastModified) {
+ return false;
+ }
+ if (isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE'])) {
+ $lm = current($a = explode(';', $_SERVER['HTTP_IF_UNMODIFIED_SINCE']));
+ if (strtotime($lm) !== $this->lastModified) {
+ return false;
+ }
+ }
+ if (isset($_SERVER['HTTP_UNLESS_MODIFIED_SINCE'])) {
+ $lm = current($a = explode(';', $_SERVER['HTTP_UNLESS_MODIFIED_SINCE']));
+ if (strtotime($lm) !== $this->lastModified) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Compare against an asterisk or check for equality
+ *
+ * @access protected
+ * @return bool
+ * @param string key for the $_SERVER array
+ * @param string string to compare
+ */
+ function compareAsterisk($svar, $compare)
+ {
+ foreach (array_map('trim', explode(',', $_SERVER[$svar])) as $request) {
+ if ($request === '*' || $request === $compare) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Send HTTP headers
+ *
+ * @access protected
+ * @return void
+ */
+ function sendHeaders()
+ {
+ foreach ($this->headers as $header => $value) {
+ $this->HTTP->setHeader($header, $value);
+ }
+ $this->HTTP->sendHeaders();
+ /* NSAPI won't output anything if we did this */
+ if (strncasecmp(PHP_SAPI, 'nsapi', 5)) {
+ if (ob_get_level()) {
+ ob_flush();
+ }
+ flush();
+ }
+ }
+
+ /**
+ * Flush
+ *
+ * @access protected
+ * @return void
+ * @param string $data
+ */
+ function flush($data = '')
+ {
+ if ($dlen = strlen($data)) {
+ $this->sentBytes += $dlen;
+ echo $data;
+ }
+ ob_flush();
+ flush();
+ }
+
+ /**
+ * Sleep
+ *
+ * @access protected
+ * @return void
+ */
+ function sleep()
+ {
+ if (OS_WINDOWS) {
+ com_message_pump($this->throttleDelay);
+ } else {
+ usleep($this->throttleDelay * 1000);
+ }
+ }
+
+ /**
+ * Returns and clears startup error
+ *
+ * @return NULL|PEAR_Error startup error if one exists
+ * @access protected
+ */
+ function _getError()
+ {
+ $error = null;
+ if (PEAR::isError($this->_error)) {
+ $error = $this->_error;
+ $this->_error = null;
+ }
+ return $error;
+ }
+ // }}}
+}
+?>
diff --git a/includes/pear/HTTP/Download/Archive.php b/includes/pear/HTTP/Download/Archive.php
new file mode 100644
index 0000000..4eadbd7
--- /dev/null
+++ b/includes/pear/HTTP/Download/Archive.php
@@ -0,0 +1,122 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * HTTP::Download::Archive
+ *
+ * PHP versions 4 and 5
+ *
+ * @category HTTP
+ * @package HTTP_Download
+ * @author Michael Wallner <mike@php.net>
+ * @copyright 2003-2005 Michael Wallner
+ * @license BSD, revisewd
+ * @version CVS: $Id: Archive.php 304423 2010-10-15 13:36:46Z clockwerx $
+ * @link http://pear.php.net/package/HTTP_Download
+ */
+
+/**
+ * Requires HTTP_Download
+ */
+require_once 'HTTP/Download.php';
+
+/**
+ * Requires System
+ */
+require_once 'System.php';
+
+/**
+ * HTTP_Download_Archive
+ *
+ * Helper class for sending Archives.
+ *
+ * @access public
+ * @version $Revision: 304423 $
+ */
+class HTTP_Download_Archive
+{
+ /**
+ * Send a bunch of files or directories as an archive
+ *
+ * Example:
+ * <code>
+ * require_once 'HTTP/Download/Archive.php';
+ * HTTP_Download_Archive::send(
+ * 'myArchive.tgz',
+ * '/var/ftp/pub/mike',
+ * HTTP_DOWNLOAD_BZ2,
+ * '',
+ * '/var/ftp/pub'
+ * );
+ * </code>
+ *
+ * @see Archive_Tar::createModify()
+ * @static
+ * @access public
+ * @return mixed Returns true on success or PEAR_Error on failure.
+ * @param string $name name the sent archive should have
+ * @param mixed $files files/directories
+ * @param string $type archive type
+ * @param string $add_path path that should be prepended to the files
+ * @param string $strip_path path that should be stripped from the files
+ */
+ function send($name, $files, $type = HTTP_DOWNLOAD_TGZ, $add_path = '', $strip_path = '')
+ {
+ $tmp = System::mktemp();
+
+ switch ($type = strToUpper($type))
+ {
+ case HTTP_DOWNLOAD_TAR:
+ include_once 'Archive/Tar.php';
+ $arc = &new Archive_Tar($tmp);
+ $content_type = 'x-tar';
+ break;
+
+ case HTTP_DOWNLOAD_TGZ:
+ include_once 'Archive/Tar.php';
+ $arc = &new Archive_Tar($tmp, 'gz');
+ $content_type = 'x-gzip';
+ break;
+
+ case HTTP_DOWNLOAD_BZ2:
+ include_once 'Archive/Tar.php';
+ $arc = &new Archive_Tar($tmp, 'bz2');
+ $content_type = 'x-bzip2';
+ break;
+
+ case HTTP_DOWNLOAD_ZIP:
+ include_once 'Archive/Zip.php';
+ $arc = &new Archive_Zip($tmp);
+ $content_type = 'x-zip';
+ break;
+
+ default:
+ return PEAR::raiseError(
+ 'Archive type not supported: ' . $type,
+ HTTP_DOWNLOAD_E_INVALID_ARCHIVE_TYPE
+ );
+ }
+
+ if ($type == HTTP_DOWNLOAD_ZIP) {
+ $options = array( 'add_path' => $add_path,
+ 'remove_path' => $strip_path);
+ if (!$arc->create($files, $options)) {
+ return PEAR::raiseError('Archive creation failed.');
+ }
+ } else {
+ if (!$e = $arc->createModify($files, $add_path, $strip_path)) {
+ return PEAR::raiseError('Archive creation failed.');
+ }
+ if (PEAR::isError($e)) {
+ return $e;
+ }
+ }
+ unset($arc);
+
+ $dl = &new HTTP_Download(array('file' => $tmp));
+ $dl->setContentType('application/' . $content_type);
+ $dl->setContentDisposition(HTTP_DOWNLOAD_ATTACHMENT, $name);
+ return $dl->send();
+ }
+}
+?>
diff --git a/includes/pear/HTTP/Download/PgLOB.php b/includes/pear/HTTP/Download/PgLOB.php
new file mode 100644
index 0000000..d1c989e
--- /dev/null
+++ b/includes/pear/HTTP/Download/PgLOB.php
@@ -0,0 +1,177 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * HTTP::Download::PgLOB
+ *
+ * PHP versions 4 and 5
+ *
+ * @category HTTP
+ * @package HTTP_Download
+ * @author Michael Wallner <mike@php.net>
+ * @copyright 2003-2005 Michael Wallner
+ * @license BSD, revised
+ * @version CVS: $Id: PgLOB.php 304423 2010-10-15 13:36:46Z clockwerx $
+ * @link http://pear.php.net/package/HTTP_Download
+ */
+
+$GLOBALS['_HTTP_Download_PgLOB_Connection'] = null;
+stream_register_wrapper('pglob', 'HTTP_Download_PgLOB');
+
+/**
+ * PgSQL large object stream interface for HTTP_Download
+ *
+ * Usage:
+ * <code>
+ * require_once 'HTTP/Download.php';
+ * require_once 'HTTP/Download/PgLOB.php';
+ * $db = &DB::connect('pgsql://user:pass@host/db');
+ * // or $db = pg_connect(...);
+ * $lo = HTTP_Download_PgLOB::open($db, 12345);
+ * $dl = &new HTTP_Download;
+ * $dl->setResource($lo);
+ * $dl->send()
+ * </code>
+ *
+ * @access public
+ * @version $Revision: 304423 $
+ */
+class HTTP_Download_PgLOB
+{
+ /**
+ * Set Connection
+ *
+ * @static
+ * @access public
+ * @return bool
+ * @param mixed $conn
+ */
+ function setConnection($conn)
+ {
+ if (is_a($conn, 'DB_Common')) {
+ $conn = $conn->dbh;
+ } elseif ( is_a($conn, 'MDB_Common') ||
+ is_a($conn, 'MDB2_Driver_Common')) {
+ $conn = $conn->connection;
+ }
+ if ($isResource = is_resource($conn)) {
+ $GLOBALS['_HTTP_Download_PgLOB_Connection'] = $conn;
+ }
+ return $isResource;
+ }
+
+ /**
+ * Get Connection
+ *
+ * @static
+ * @access public
+ * @return resource
+ */
+ function getConnection()
+ {
+ if (is_resource($GLOBALS['_HTTP_Download_PgLOB_Connection'])) {
+ return $GLOBALS['_HTTP_Download_PgLOB_Connection'];
+ }
+ return null;
+ }
+
+ /**
+ * Open
+ *
+ * @static
+ * @access public
+ * @return resource
+ * @param mixed $conn
+ * @param int $loid
+ * @param string $mode
+ */
+ function open($conn, $loid, $mode = 'rb')
+ {
+ HTTP_Download_PgLOB::setConnection($conn);
+ return fopen('pglob:///'. $loid, $mode);
+ }
+
+ /**#@+
+ * Stream Interface Implementation
+ * @internal
+ */
+ var $ID = 0;
+ var $size = 0;
+ var $conn = null;
+ var $handle = null;
+
+ function stream_open($path, $mode)
+ {
+ if (!$this->conn = HTTP_Download_PgLOB::getConnection()) {
+ return false;
+ }
+ if (!preg_match('/(\d+)/', $path, $matches)) {
+ return false;
+ }
+ $this->ID = $matches[1];
+
+ if (!pg_query($this->conn, 'BEGIN')) {
+ return false;
+ }
+
+ $this->handle = pg_lo_open($this->conn, $this->ID, $mode);
+ if (!is_resource($this->handle)) {
+ return false;
+ }
+
+ // fetch size of lob
+ pg_lo_seek($this->handle, 0, PGSQL_SEEK_END);
+ $this->size = (int) pg_lo_tell($this->handle);
+ pg_lo_seek($this->handle, 0, PGSQL_SEEK_SET);
+
+ return true;
+ }
+
+ function stream_read($length)
+ {
+ return pg_lo_read($this->handle, $length);
+ }
+
+ function stream_seek($offset, $whence = SEEK_SET)
+ {
+ return pg_lo_seek($this->handle, $offset, $whence);
+ }
+
+ function stream_tell()
+ {
+ return pg_lo_tell($this->handle);
+ }
+
+ function stream_eof()
+ {
+ return pg_lo_tell($this->handle) >= $this->size;
+ }
+
+ function stream_flush()
+ {
+ return true;
+ }
+
+ function stream_stat()
+ {
+ return array('size' => $this->size, 'ino' => $this->ID);
+ }
+
+ function stream_write($data)
+ {
+ return pg_lo_write($this->handle, $data);
+ }
+
+ function stream_close()
+ {
+ if (pg_lo_close($this->handle)) {
+ return pg_query($this->conn, 'COMMIT');
+ } else {
+ pg_query($this->conn ,'ROLLBACK');
+ return false;
+ }
+ }
+ /**#@-*/
+}
+
+?>
diff --git a/includes/pear/HTTP/Header.php b/includes/pear/HTTP/Header.php
new file mode 100644
index 0000000..14026ad
--- /dev/null
+++ b/includes/pear/HTTP/Header.php
@@ -0,0 +1,537 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * HTTP::Header
+ *
+ * PHP versions 4 and 5
+ *
+ * @category HTTP
+ * @package HTTP_Header
+ * @author Wolfram Kriesing <wk@visionp.de>
+ * @author Davey Shafik <davey@php.net>
+ * @author Michael Wallner <mike@php.net>
+ * @copyright 2003-2005 The Authors
+ * @license BSD, revised
+ * @version CVS: $Id: Header.php 304418 2010-10-15 13:18:02Z clockwerx $
+ * @link http://pear.php.net/package/HTTP_Header
+ */
+
+/**
+ * Requires HTTP
+ */
+require_once 'HTTP.php';
+
+/**#@+
+ * Information Codes
+ */
+define('HTTP_HEADER_STATUS_100', '100 Continue');
+define('HTTP_HEADER_STATUS_101', '101 Switching Protocols');
+define('HTTP_HEADER_STATUS_102', '102 Processing');
+define('HTTP_HEADER_STATUS_INFORMATIONAL',1);
+/**#@-*/
+
+/**#+
+ * Success Codes
+ */
+define('HTTP_HEADER_STATUS_200', '200 OK');
+define('HTTP_HEADER_STATUS_201', '201 Created');
+define('HTTP_HEADER_STATUS_202', '202 Accepted');
+define('HTTP_HEADER_STATUS_203', '203 Non-Authoritative Information');
+define('HTTP_HEADER_STATUS_204', '204 No Content');
+define('HTTP_HEADER_STATUS_205', '205 Reset Content');
+define('HTTP_HEADER_STATUS_206', '206 Partial Content');
+define('HTTP_HEADER_STATUS_207', '207 Multi-Status');
+define('HTTP_HEADER_STATUS_SUCCESSFUL',2);
+/**#@-*/
+
+/**#@+
+ * Redirection Codes
+ */
+define('HTTP_HEADER_STATUS_300', '300 Multiple Choices');
+define('HTTP_HEADER_STATUS_301', '301 Moved Permanently');
+define('HTTP_HEADER_STATUS_302', '302 Found');
+define('HTTP_HEADER_STATUS_303', '303 See Other');
+define('HTTP_HEADER_STATUS_304', '304 Not Modified');
+define('HTTP_HEADER_STATUS_305', '305 Use Proxy');
+define('HTTP_HEADER_STATUS_306', '306 (Unused)');
+define('HTTP_HEADER_STATUS_307', '307 Temporary Redirect');
+define('HTTP_HEADER_STATUS_REDIRECT',3);
+/**#@-*/
+
+/**#@+
+ * Error Codes
+ */
+define('HTTP_HEADER_STATUS_400', '400 Bad Request');
+define('HTTP_HEADER_STATUS_401', '401 Unauthorized');
+define('HTTP_HEADER_STATUS_402', '402 Payment Granted');
+define('HTTP_HEADER_STATUS_403', '403 Forbidden');
+define('HTTP_HEADER_STATUS_404', '404 File Not Found');
+define('HTTP_HEADER_STATUS_405', '405 Method Not Allowed');
+define('HTTP_HEADER_STATUS_406', '406 Not Acceptable');
+define('HTTP_HEADER_STATUS_407', '407 Proxy Authentication Required');
+define('HTTP_HEADER_STATUS_408', '408 Request Time-out');
+define('HTTP_HEADER_STATUS_409', '409 Conflict');
+define('HTTP_HEADER_STATUS_410', '410 Gone');
+define('HTTP_HEADER_STATUS_411', '411 Length Required');
+define('HTTP_HEADER_STATUS_412', '412 Precondition Failed');
+define('HTTP_HEADER_STATUS_413', '413 Request Entity Too Large');
+define('HTTP_HEADER_STATUS_414', '414 Request-URI Too Large');
+define('HTTP_HEADER_STATUS_415', '415 Unsupported Media Type');
+define('HTTP_HEADER_STATUS_416', '416 Requested range not satisfiable');
+define('HTTP_HEADER_STATUS_417', '417 Expectation Failed');
+define('HTTP_HEADER_STATUS_422', '422 Unprocessable Entity');
+define('HTTP_HEADER_STATUS_423', '423 Locked');
+define('HTTP_HEADER_STATUS_424', '424 Failed Dependency');
+define('HTTP_HEADER_STATUS_CLIENT_ERROR',4);
+/**#@-*/
+
+/**#@+
+ * Server Errors
+ */
+define('HTTP_HEADER_STATUS_500', '500 Internal Server Error');
+define('HTTP_HEADER_STATUS_501', '501 Not Implemented');
+define('HTTP_HEADER_STATUS_502', '502 Bad Gateway');
+define('HTTP_HEADER_STATUS_503', '503 Service Unavailable');
+define('HTTP_HEADER_STATUS_504', '504 Gateway Time-out');
+define('HTTP_HEADER_STATUS_505', '505 HTTP Version not supported');
+define('HTTP_HEADER_STATUS_507', '507 Insufficient Storage');
+define('HTTP_HEADER_STATUS_SERVER_ERROR',5);
+/**#@-*/
+
+/**
+ * HTTP_Header
+ *
+ * @package HTTP_Header
+ * @category HTTP
+ * @access public
+ * @version $Revision: 304418 $
+ */
+class HTTP_Header extends HTTP
+{
+ /**
+ * Default Headers
+ *
+ * The values that are set as default, are the same as PHP sends by default.
+ *
+ * @var array
+ * @access private
+ */
+ var $_headers = array(
+ 'content-type' => 'text/html',
+ 'pragma' => 'no-cache',
+ 'cache-control' => 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'
+ );
+
+ /**
+ * HTTP version
+ *
+ * @var string
+ * @access private
+ */
+ var $_httpVersion = '1.0';
+
+ /**
+ * @var bool
+ * @access public
+ */
+ var $prettify = false;
+
+ /**
+ * Constructor
+ *
+ * Sets HTTP version.
+ *
+ * @access public
+ * @return object HTTP_Header
+ */
+ function HTTP_Header()
+ {
+ if (isset($_SERVER['SERVER_PROTOCOL'])) {
+ $this->setHttpVersion(substr($_SERVER['SERVER_PROTOCOL'], -3));
+ }
+ }
+
+ /**
+ * Set HTTP version
+ *
+ * @access public
+ * @return bool Returns true on success or false if version doesn't
+ * match 1.0 or 1.1 (note: 1 will result in 1.0)
+ * @param mixed $version HTTP version, either 1.0 or 1.1
+ */
+ function setHttpVersion($version)
+ {
+ $version = round((float) $version, 1);
+ if ($version < 1.0 || $version > 1.1) {
+ return false;
+ }
+ $this->_httpVersion = sprintf('%0.1f', $version);
+ return true;
+ }
+
+ /**
+ * Get HTTP version
+ *
+ * @access public
+ * @return string
+ */
+ function getHttpVersion()
+ {
+ return $this->_httpVersion;
+ }
+
+ /**
+ * Set Header
+ *
+ * The default value for the Last-Modified header will be current
+ * date and atime if $value is omitted.
+ *
+ * @access public
+ * @return bool Returns true on success or false if $key was empty or
+ * $value was not of an scalar type.
+ * @param string $key The name of the header.
+ * @param string $value The value of the header. (NULL to unset header)
+ */
+ function setHeader($key, $value = null)
+ {
+ if (empty($key) || (isset($value) && !is_scalar($value))) {
+ return false;
+ }
+
+ $key = strToLower($key);
+ if ($key == 'last-modified') {
+ if (!isset($value)) {
+ $value = HTTP::Date(time());
+ } elseif (is_numeric($value)) {
+ $value = HTTP::Date($value);
+ }
+ }
+
+ if (isset($value)) {
+ $this->_headers[$key] = $value;
+ } else {
+ unset($this->_headers[$key]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Get Header
+ *
+ * If $key is omitted, all stored headers will be returned.
+ *
+ * @access public
+ * @return mixed Returns string value of the requested header,
+ * array values of all headers or false if header $key
+ * is not set.
+ * @param string $key The name of the header to fetch.
+ */
+ function getHeader($key = null)
+ {
+ if (!isset($key)) {
+ return $this->_headers;
+ }
+
+ $key = strToLower($key);
+
+ if (!isset($this->_headers[$key])) {
+ return false;
+ }
+
+ return $this->_headers[$key];
+ }
+
+ /**
+ * Send Headers
+ *
+ * Send out the header that you set via setHeader().
+ *
+ * @access public
+ * @return bool Returns true on success or false if headers are already
+ * sent.
+ * @param array $keys Headers to (not) send, see $include.
+ * @param array $include If true only $keys matching headers will be
+ * sent, if false only header not matching $keys will be
+ * sent.
+ */
+ function sendHeaders($keys = array(), $include = true)
+ {
+ if (headers_sent()) {
+ return false;
+ }
+
+ if (count($keys)) {
+ array_change_key_case($keys, CASE_LOWER);
+ foreach ($this->_headers as $key => $value) {
+ if ($include ? in_array($key, $keys) : !in_array($key, $keys)) {
+ header(($this->prettify ? uctitle($key) : $key) .': '. $value);
+ }
+ }
+ } else {
+ foreach ($this->_headers as $header => $value) {
+ header(($this->prettify ? uctitle($header) : $header) .': '. $value);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Send Satus Code
+ *
+ * Send out the given HTTP-Status code. Use this for example when you
+ * want to tell the client this page is cached, then you would call
+ * sendStatusCode(304).
+ *
+ * @see HTTP_Header_Cache::exitIfCached()
+ *
+ * @access public
+ * @return bool Returns true on success or false if headers are already
+ * sent.
+ * @param int $code The status code to send, i.e. 404, 304, 200, etc.
+ */
+ function sendStatusCode($code)
+ {
+ if (headers_sent()) {
+ return false;
+ }
+
+ if ($code == (int) $code && defined('HTTP_HEADER_STATUS_'. $code)) {
+ $code = constant('HTTP_HEADER_STATUS_'. $code);
+ }
+
+ if (strncasecmp(PHP_SAPI, 'cgi', 3)) {
+ header('HTTP/'. $this->_httpVersion .' '. $code);
+ } else {
+ header('Status: '. $code);
+ }
+ return true;
+ }
+
+ /**
+ * Date to Timestamp
+ *
+ * Converts dates like
+ * Mon, 31 Mar 2003 15:26:34 GMT
+ * Tue, 15 Nov 1994 12:45:26 GMT
+ * into a timestamp, strtotime() didn't do it in older versions.
+ *
+ * @deprecated Use PHPs strtotime() instead.
+ * @access public
+ * @return mixed Returns int unix timestamp or false if the date doesn't
+ * seem to be a valid GMT date.
+ * @param string $date The GMT date.
+ */
+ function dateToTimestamp($date)
+ {
+ static $months = array(
+ null => 0, 'Jan' => 1, 'Feb' => 2, 'Mar' => 3, 'Apr' => 4,
+ 'May' => 5, 'Jun' => 6, 'Jul' => 7, 'Aug' => 8, 'Sep' => 9,
+ 'Oct' => 10, 'Nov' => 11, 'Dec' => 12
+ );
+
+ if (-1 < $timestamp = strToTime($date)) {
+ return $timestamp;
+ }
+
+ if (!preg_match('~[^,]*,\s(\d+)\s(\w+)\s(\d+)\s(\d+):(\d+):(\d+).*~',
+ $date, $m)) {
+ return false;
+ }
+
+ // [0] => Mon, 31 Mar 2003 15:42:55 GMT
+ // [1] => 31 [2] => Mar [3] => 2003 [4] => 15 [5] => 42 [6] => 55
+ return mktime($m[4], $m[5], $m[6], $months[$m[2]], $m[1], $m[3]);
+ }
+
+ /**
+ * Redirect
+ *
+ * This function redirects the client. This is done by issuing a Location
+ * header and exiting. Additionally to HTTP::redirect() you can also add
+ * parameters to the url.
+ *
+ * If you dont need parameters to be added, simply use HTTP::redirect()
+ * otherwise use HTTP_Header::redirect().
+ *
+ * @see HTTP::redirect()
+ * @author Wolfram Kriesing <wk@visionp.de>
+ * @access public
+ * @return void
+ * @param string $url The URL to redirect to, if none is given it
+ * redirects to the current page.
+ * @param array $param Array of query string parameters to add; usually
+ * a set of key => value pairs; if an array entry consists
+ * only of an value it is used as key and the respective
+ * value is fetched from $GLOBALS[$value]
+ * @param bool $session Whether the session name/id should be added
+ */
+ function redirect($url = null, $param = array(), $session = false)
+ {
+ if (!isset($url)) {
+ $url = $_SERVER['PHP_SELF'];
+ }
+
+ $qs = array();
+
+ if ($session) {
+ $qs[] = session_name() .'='. session_id();
+ }
+
+ if (is_array($param) && count($param)) {
+ if (count($param)) {
+ foreach ($param as $key => $val) {
+ if (is_string($key)) {
+ $qs[] = urlencode($key) .'='. urlencode($val);
+ } else {
+ $qs[] = urlencode($val) .'='. urlencode(@$GLOBALS[$val]);
+ }
+ }
+ }
+ }
+
+ if ($qstr = implode('&', $qs)) {
+ $purl = parse_url($url);
+ $url .= (isset($purl['query']) ? '&' : '?') . $qstr;
+ }
+
+ parent::redirect($url);
+ }
+
+ /**#@+
+ * @author Davey Shafik <davey@php.net>
+ * @param int $http_code HTTP Code to check
+ * @access public
+ */
+
+ /**
+ * Return HTTP Status Code Type
+ *
+ * @return int|false
+ */
+ function getStatusType($http_code)
+ {
+ if(is_int($http_code) && defined('HTTP_HEADER_STATUS_' .$http_code) || defined($http_code)) {
+ $type = substr($http_code,0,1);
+ switch ($type) {
+ case HTTP_HEADER_STATUS_INFORMATIONAL:
+ case HTTP_HEADER_STATUS_SUCCESSFUL:
+ case HTTP_HEADER_STATUS_REDIRECT:
+ case HTTP_HEADER_STATUS_CLIENT_ERROR:
+ case HTTP_HEADER_STATUS_SERVER_ERROR:
+ return $type;
+ break;
+ default:
+ return false;
+ break;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Return Status Code Message
+ *
+ * @return string|false
+ */
+ function getStatusText($http_code)
+ {
+ if ($this->getStatusType($http_code)) {
+ if (is_int($http_code) && defined('HTTP_HEADER_STATUS_' .$http_code)) {
+ return substr(constant('HTTP_HEADER_STATUS_' .$http_code),4);
+ } else {
+ return substr($http_code,4);
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if HTTP Status code is Information (1xx)
+ *
+ * @return boolean
+ */
+ function isInformational($http_code)
+ {
+ if ($status_type = $this->getStatusType($http_code)) {
+ return $status_type{0} == HTTP_HEADER_STATUS_INFORMATIONAL;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if HTTP Status code is Successful (2xx)
+ *
+ * @return boolean
+ */
+ function isSuccessful($http_code)
+ {
+ if ($status_type = $this->getStatusType($http_code)) {
+ return $status_type{0} == HTTP_HEADER_STATUS_SUCCESSFUL;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if HTTP Status code is a Redirect (3xx)
+ *
+ * @return boolean
+ */
+ function isRedirect($http_code)
+ {
+ if ($status_type = $this->getStatusType($http_code)) {
+ return $status_type{0} == HTTP_HEADER_STATUS_REDIRECT;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if HTTP Status code is a Client Error (4xx)
+ *
+ * @return boolean
+ */
+ function isClientError($http_code)
+ {
+ if ($status_type = $this->getStatusType($http_code)) {
+ return $status_type{0} == HTTP_HEADER_STATUS_CLIENT_ERROR;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if HTTP Status code is Server Error (5xx)
+ *
+ * @return boolean
+ */
+ function isServerError($http_code)
+ {
+ if ($status_type = $this->getStatusType($http_code)) {
+ return $status_type{0} == HTTP_HEADER_STATUS_SERVER_ERROR;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if HTTP Status code is Server OR Client Error (4xx or 5xx)
+ *
+ * @return boolean
+ */
+ function isError($http_code)
+ {
+ if ($status_type = $this->getStatusType($http_code)) {
+ return (($status_type == HTTP_HEADER_STATUS_CLIENT_ERROR) || ($status_type == HTTP_HEADER_STATUS_SERVER_ERROR)) ? true : false;
+ } else {
+ return false;
+ }
+ }
+ /**#@-*/
+}
+?>
diff --git a/includes/pear/HTTP/Header/Cache.php b/includes/pear/HTTP/Header/Cache.php
new file mode 100644
index 0000000..f6ae867
--- /dev/null
+++ b/includes/pear/HTTP/Header/Cache.php
@@ -0,0 +1,238 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * HTTP::Header::Cache
+ *
+ * PHP versions 4 and 5
+ *
+ * @category HTTP
+ * @package HTTP_Header
+ * @author Wolfram Kriesing <wk@visionp.de>
+ * @author Michael Wallner <mike@php.net>
+ * @copyright 2003-2005 The Authors
+ * @license BSD, revised
+ * @version CVS: $Id: Cache.php 304418 2010-10-15 13:18:02Z clockwerx $
+ * @link http://pear.php.net/package/HTTP_Header
+ */
+
+/**
+ * Requires HTTP_Header
+ */
+require_once 'HTTP/Header.php';
+
+/**
+ * HTTP_Header_Cache
+ *
+ * This package provides methods to easier handle caching of HTTP pages. That
+ * means that the pages can be cached at the client (user agent or browser) and
+ * your application only needs to send "hey client you already have the pages".
+ *
+ * Which is done by sending the HTTP-Status "304 Not Modified", so that your
+ * application load and the network traffic can be reduced, since you only need
+ * to send the complete page once. This is really an advantage e.g. for
+ * generated style sheets, or simply pages that do only change rarely.
+ *
+ * Usage:
+ * <code>
+ * require_once 'HTTP/Header/Cache.php';
+ * $httpCache = new HTTP_Header_Cache(4, 'weeks');
+ * $httpCache->sendHeaders();
+ * // your code goes here
+ * </code>
+ *
+ * @package HTTP_Header
+ * @category HTTP
+ * @access public
+ * @version $Revision: 304418 $
+ */
+class HTTP_Header_Cache extends HTTP_Header
+{
+ /**
+ * Constructor
+ *
+ * Set the amount of time to cache.
+ *
+ * @access public
+ * @return object HTTP_Header_Cache
+ * @param int $expires
+ * @param string $unit
+ */
+ function HTTP_Header_Cache($expires = 0, $unit = 'seconds')
+ {
+ parent::HTTP_Header();
+ $this->setHeader('Pragma', 'cache');
+ $this->setHeader('Last-Modified', $this->getCacheStart());
+ $this->setHeader('Cache-Control', 'private, must-revalidate, max-age=0');
+
+ if ($expires) {
+ if (!$this->isOlderThan($expires, $unit)) {
+ $this->exitCached();
+ }
+ $this->setHeader('Last-Modified', time());
+ }
+ }
+
+ /**
+ * Get Cache Start
+ *
+ * Returns the unix timestamp of the If-Modified-Since HTTP header or the
+ * current time if the header was not sent by the client.
+ *
+ * @access public
+ * @return int unix timestamp
+ */
+ function getCacheStart()
+ {
+ if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && !$this->isPost()) {
+ return strtotime(current($array = explode(';',
+ $_SERVER['HTTP_IF_MODIFIED_SINCE'])));
+ }
+ return time();
+ }
+
+ /**
+ * Is Older Than
+ *
+ * You can call it like this:
+ * <code>
+ * $httpCache->isOlderThan(1, 'day');
+ * $httpCache->isOlderThan(47, 'days');
+ *
+ * $httpCache->isOlderThan(1, 'week');
+ * $httpCache->isOlderThan(3, 'weeks');
+ *
+ * $httpCache->isOlderThan(1, 'hour');
+ * $httpCache->isOlderThan(5, 'hours');
+ *
+ * $httpCache->isOlderThan(1, 'minute');
+ * $httpCache->isOlderThan(15, 'minutes');
+ *
+ * $httpCache->isOlderThan(1, 'second');
+ * $httpCache->isOlderThan(15);
+ * </code>
+ *
+ * If you specify something greater than "weeks" as time untit, it just
+ * works approximatly, because a month is taken to consist of 4.3 weeks.
+ *
+ * @access public
+ * @return bool Returns true if requested page is older than specified.
+ * @param int $time The amount of time.
+ * @param string $unit The unit of the time amount - (year[s], month[s],
+ * week[s], day[s], hour[s], minute[s], second[s]).
+ */
+ function isOlderThan($time = 0, $unit = 'seconds')
+ {
+ if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) || $this->isPost()) {
+ return true;
+ }
+ if (!$time) {
+ return false;
+ }
+
+ switch (strtolower($unit))
+ {
+ case 'year':
+ case 'years':
+ $time *= 12;
+ case 'month':
+ case 'months':
+ $time *= 4.3;
+ case 'week':
+ case 'weeks':
+ $time *= 7;
+ case 'day':
+ case 'days':
+ $time *= 24;
+ case 'hour':
+ case 'hours':
+ $time *= 60;
+ case 'minute':
+ case 'minutes':
+ $time *= 60;
+ }
+
+ return (time() - $this->getCacheStart()) > $time;
+ }
+
+ /**
+ * Is Cached
+ *
+ * Check whether we can consider to be cached on the client side.
+ *
+ * @access public
+ * @return bool Whether the page/resource is considered to be cached.
+ * @param int $lastModified Unix timestamp of last modification.
+ */
+ function isCached($lastModified = 0)
+ {
+ if ($this->isPost()) {
+ return false;
+ }
+ if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && !$lastModified) {
+ return true;
+ }
+ if (!$seconds = time() - $lastModified) {
+ return false;
+ }
+ return !$this->isOlderThan($seconds);
+ }
+
+ /**
+ * Is Post
+ *
+ * Check if request method is "POST".
+ *
+ * @access public
+ * @return bool
+ */
+ function isPost()
+ {
+ return isset($_SERVER['REQUEST_METHOD']) and
+ 'POST' == $_SERVER['REQUEST_METHOD'];
+ }
+
+ /**
+ * Exit If Cached
+ *
+ * Exit with "HTTP 304 Not Modified" if we consider to be cached.
+ *
+ * @access public
+ * @return void
+ * @param int $lastModified Unix timestamp of last modification.
+ */
+ function exitIfCached($lastModified = 0)
+ {
+ if ($this->isCached($lastModified)) {
+ $this->exitCached();
+ }
+ }
+
+ /**
+ * Exit Cached
+ *
+ * Exit with "HTTP 304 Not Modified".
+ *
+ * @access public
+ * @return void
+ */
+ function exitCached()
+ {
+ $this->sendHeaders();
+ $this->sendStatusCode(304);
+ exit;
+ }
+
+ /**
+ * Set Last Modified
+ *
+ * @access public
+ * @return void
+ * @param int $lastModified The unix timestamp of last modification.
+ */
+ function setLastModified($lastModified = null)
+ {
+ $this->setHeader('Last-Modified', $lastModified);
+ }
+}
+?>
diff --git a/includes/pear/Image/GraphViz.php b/includes/pear/Image/GraphViz.php
new file mode 100644
index 0000000..d7de7c5
--- /dev/null
+++ b/includes/pear/Image/GraphViz.php
@@ -0,0 +1,1005 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Image_GraphViz
+ *
+ * PHP version 4 and 5
+ *
+ * Copyright (c) 2001-2007, Dr. Volker Göbbels <vmg@arachnion.de> and
+ * Sebastian Bergmann <sb@sebastian-bergmann.de>. All rights reserved.
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Image
+ * @package Image_GraphViz
+ * @author Dr. Volker Göbbels <vmg@arachnion.de>
+ * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
+ * @author Karsten Dambekalns <k.dambekalns@fishfarm.de>
+ * @author Michael Lively Jr. <mlively@ft11.net>
+ * @author Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @copyright 2001-2007 Dr. Volker Göbbels <vmg@arachnion.de> and Sebastian Bergmann <sb@sebastian-bergmann.de>
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: GraphViz.php 304688 2010-10-24 05:21:17Z clockwerx $
+ * @link http://pear.php.net/package/Image_GraphViz
+ * @link http://www.graphviz.org/
+ * @since File available since Release 0.1.0
+ */
+
+/**
+ * Required PEAR classes
+ */
+require_once 'System.php';
+
+/**
+ * Interface to AT&T's GraphViz tools.
+ *
+ * The GraphViz class allows for the creation of and to work with directed
+ * and undirected graphs and their visualization with AT&T's GraphViz tools.
+ *
+ * <code>
+ * <?php
+ * require_once 'Image/GraphViz.php';
+ *
+ * $graph = new Image_GraphViz();
+ *
+ * $graph->addNode(
+ * 'Node1',
+ * array(
+ * 'URL' => 'http://link1',
+ * 'label' => 'This is a label',
+ * 'shape' => 'box'
+ * )
+ * );
+ *
+ * $graph->addNode(
+ * 'Node2',
+ * array(
+ * 'URL' => 'http://link2',
+ * 'fontsize' => '14'
+ * )
+ * );
+ *
+ * $graph->addNode(
+ * 'Node3',
+ * array(
+ * 'URL' => 'http://link3',
+ * 'fontsize' => '20'
+ * )
+ * );
+ *
+ * $graph->addEdge(
+ * array(
+ * 'Node1' => 'Node2'
+ * ),
+ * array(
+ * 'label' => 'Edge Label'
+ * )
+ * );
+ *
+ * $graph->addEdge(
+ * array(
+ * 'Node1' => 'Node2'
+ * ),
+ * array(
+ * 'color' => 'red'
+ * )
+ * );
+ *
+ * $graph->image();
+ * ?>
+ * </code>
+ *
+ * @category Image
+ * @package Image_GraphViz
+ * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
+ * @author Dr. Volker Göbbels <vmg@arachnion.de>
+ * @author Karsten Dambekalns <k.dambekalns@fishfarm.de>
+ * @author Michael Lively Jr. <mlively@ft11.net>
+ * @author Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @copyright 2001-2007 Dr. Volker Göbbels <vmg@arachnion.de> and Sebastian Bergmann <sb@sebastian-bergmann.de>
+ * @license http://www.php.net/license/3_0.txt The PHP License, Version 3.0
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Image_GraphViz
+ * @link http://www.graphviz.org/
+ * @since Class available since Release 0.1
+ */
+class Image_GraphViz
+{
+ /**
+ * Base path to GraphViz commands
+ *
+ * @var string
+ */
+ var $binPath = '';
+
+ /**
+ * Path to GraphViz/dot command
+ *
+ * @var string
+ */
+ var $dotCommand = 'dot';
+
+ /**
+ * Path to GraphViz/neato command
+ *
+ * @var string
+ */
+ var $neatoCommand = 'neato';
+
+ /**
+ * Representation of the graph
+ *
+ * @var array
+ */
+ var $graph = array('edgesFrom' => array(),
+ 'nodes' => array(),
+ 'attributes' => array(),
+ 'directed' => true,
+ 'clusters' => array(),
+ 'subgraphs' => array(),
+ 'name' => 'G',
+ 'strict' => true,
+ );
+
+ /**
+ * Whether to return PEAR_Error instance on failures instead of FALSE
+ *
+ * @var boolean
+ */
+ protected $_returnFalseOnError = true;
+
+ /**
+ * Constructor.
+ *
+ * Setting the name of the Graph is useful for including multiple image
+ * maps on one page. If not set, the graph will be named 'G'.
+ *
+ * @param boolean $directed Directed (TRUE) or undirected (FALSE) graph.
+ * Note: You MUST pass a boolean, and not just
+ * an expression that evaluates to TRUE or
+ * FALSE (i.e. NULL, empty string, 0 will NOT
+ * work)
+ * @param array $attributes Attributes of the graph
+ * @param string $name Name of the Graph
+ * @param boolean $strict Whether to collapse multiple edges between
+ * same nodes
+ * @param boolean $returnError Set to TRUE to return PEAR_Error instances
+ * on failures instead of FALSE
+ */
+ public function Image_GraphViz($directed = true, $attributes = array(),
+ $name = 'G', $strict = true, $returnError = false)
+ {
+ $this->setDirected($directed);
+ $this->setAttributes($attributes);
+ $this->graph['name'] = $name;
+ $this->graph['strict'] = (boolean)$strict;
+
+ $this->_returnFalseOnError = !$returnError;
+ }
+
+ /**
+ * Outputs image of the graph in a given format
+ *
+ * This methods send HTTP headers
+ *
+ * @param string $format Format of the output image. This may be one
+ * of the formats supported by GraphViz.
+ * @param string $command "dot" or "neato"
+ *
+ * @return boolean TRUE on success, FALSE or PEAR_Error otherwise
+ */
+ public function image($format = 'svg', $command = null)
+ {
+ $file = $this->saveParsedGraph();
+ if (!$file || PEAR::isError($file)) {
+ return $file;
+ }
+
+ $outputfile = $file . '.' . $format;
+
+ $rendered = $this->renderDotFile($file, $outputfile, $format,
+ $command);
+ if ($rendered !== true) {
+ return $rendered;
+ }
+
+ $sendContentLengthHeader = true;
+
+ switch (strtolower($format)) {
+ case 'gif':
+ case 'png':
+ case 'bmp':
+ case 'jpeg':
+ case 'tiff':
+ header('Content-Type: image/' . $format);
+ break;
+
+ case 'tif':
+ header('Content-Type: image/tiff');
+ break;
+
+ case 'jpg':
+ header('Content-Type: image/jpeg');
+ break;
+
+ case 'ico':
+ header('Content-Type: image/x-icon');
+ break;
+
+ case 'wbmp':
+ header('Content-Type: image/vnd.wap.wbmp');
+ break;
+
+ case 'pdf':
+ header('Content-Type: application/pdf');
+ break;
+
+ case 'mif':
+ header('Content-Type: application/vnd.mif');
+ break;
+
+ case 'vrml':
+ header('Content-Type: application/x-vrml');
+ break;
+
+ case 'svg':
+ header('Content-Type: image/svg+xml');
+ break;
+
+ case 'plain':
+ case 'plain-ext':
+ header('Content-Type: text/plain');
+ break;
+
+ default:
+ header('Content-Type: application/octet-stream');
+ $sendContentLengthHeader = false;
+ }
+
+ if ($sendContentLengthHeader) {
+ header('Content-Length: ' . filesize($outputfile));
+ }
+
+ $return = true;
+ if (readfile($outputfile) === false) {
+ $return = false;
+ }
+ @unlink($outputfile);
+
+ return $return;
+ }
+
+ /**
+ * Returns image (data) of the graph in a given format.
+ *
+ * @param string $format Format of the output image. This may be one
+ * of the formats supported by GraphViz.
+ * @param string $command "dot" or "neato"
+ *
+ * @return string The image (data) created by GraphViz, FALSE or PEAR_Error
+ * on error
+ * @since Method available since Release 1.1.0
+ */
+ public function fetch($format = 'svg', $command = null)
+ {
+ $file = $this->saveParsedGraph();
+ if (!$file || PEAR::isError($file)) {
+ return $file;
+ }
+
+ $outputfile = $file . '.' . $format;
+
+ $rendered = $this->renderDotFile($file, $outputfile, $format,
+ $command);
+ if ($rendered !== true) {
+ return $rendered;
+ }
+
+ @unlink($file);
+
+ $fp = fopen($outputfile, 'rb');
+
+ if (!$fp) {
+ if ($this->_returnFalseOnError) {
+ return false;
+ }
+ $error = PEAR::raiseError('Could not read rendered file');
+ return $error;
+ }
+
+ $data = fread($fp, filesize($outputfile));
+ fclose($fp);
+ @unlink($outputfile);
+
+ return $data;
+ }
+
+ /**
+ * Renders a given dot file into a given format.
+ *
+ * @param string $dotfile The absolute path of the dot file to use.
+ * @param string $outputfile The absolute path of the file to save to.
+ * @param string $format Format of the output image. This may be one
+ * of the formats supported by GraphViz.
+ * @param string $command "dot" or "neato"
+ *
+ * @return boolean TRUE if the file was saved, FALSE or PEAR_Error
+ * otherwise.
+ */
+ public function renderDotFile($dotfile, $outputfile, $format = 'svg',
+ $command = null)
+ {
+ if (!file_exists($dotfile)) {
+ if ($this->_returnFalseOnError) {
+ return false;
+ }
+ $error = PEAR::raiseError('Could not find dot file');
+ return $error;
+ }
+
+ $oldmtime = file_exists($outputfile) ? filemtime($outputfile) : 0;
+
+ switch ($command) {
+ case 'dot':
+ case 'neato':
+ break;
+ default:
+ $command = $this->graph['directed'] ? 'dot' : 'neato';
+ }
+ $command_orig = $command;
+
+ $command = $this->binPath.(($command == 'dot') ? $this->dotCommand
+ : $this->neatoCommand);
+
+ $command .= ' -T'.escapeshellarg($format)
+ .' -o'.escapeshellarg($outputfile)
+ .' '.escapeshellarg($dotfile)
+ .' 2>&1';
+ exec($command, $msg, $return_val);
+
+ clearstatcache();
+ if (file_exists($outputfile) && filemtime($outputfile) > $oldmtime
+ && $return_val == 0) {
+ return true;
+ } elseif ($this->_returnFalseOnError) {
+ return false;
+ }
+ $error = PEAR::raiseError($command_orig.' command failed: '
+ .implode("\n", $msg));
+ return $error;
+ }
+
+ /**
+ * Adds a cluster to the graph.
+ *
+ * A cluster is a subgraph with a rectangle around it.
+ *
+ * @param string $id ID.
+ * @param array $title Title.
+ * @param array $attributes Attributes of the cluster.
+ * @param string $group ID of group to nest cluster into
+ *
+ * @return void
+ * @see addSubgraph()
+ */
+ public function addCluster($id, $title, $attributes = array(), $group = 'default')
+ {
+ $this->graph['clusters'][$id]['title'] = $title;
+ $this->graph['clusters'][$id]['attributes'] = $attributes;
+ $this->graph['clusters'][$id]['embedIn'] = $group;
+ }
+
+ /**
+ * Adds a subgraph to the graph.
+ *
+ * @param string $id ID.
+ * @param array $title Title.
+ * @param array $attributes Attributes of the cluster.
+ * @param string $group ID of group to nest subgraph into
+ *
+ * @return void
+ */
+ public function addSubgraph($id, $title, $attributes = array(), $group = 'default')
+ {
+ $this->graph['subgraphs'][$id]['title'] = $title;
+ $this->graph['subgraphs'][$id]['attributes'] = $attributes;
+ $this->graph['subgraphs'][$id]['embedIn'] = $group;
+ }
+
+ /**
+ * Adds a note to the graph.
+ *
+ * @param string $name Name of the node.
+ * @param array $attributes Attributes of the node.
+ * @param string $group Group of the node.
+ *
+ * @return void
+ */
+ public function addNode($name, $attributes = array(), $group = 'default')
+ {
+ $this->graph['nodes'][$group][$name] = $attributes;
+ }
+
+ /**
+ * Removes a node from the graph.
+ *
+ * This method doesn't remove edges associated with the node.
+ *
+ * @param string $name Name of the node to be removed.
+ * @param string $group Group of the node.
+ *
+ * @return void
+ */
+ public function removeNode($name, $group = 'default')
+ {
+ if (isset($this->graph['nodes'][$group][$name])) {
+ unset($this->graph['nodes'][$group][$name]);
+ }
+ }
+
+ /**
+ * Adds an edge to the graph.
+ *
+ * Examples:
+ * <code>
+ * $g->addEdge(array('node1' => 'node2'));
+ * $attr = array(
+ * 'label' => '+1',
+ * 'style' => 'dashed',
+ * );
+ * $g->addEdge(array('node3' => 'node4'), $attr);
+ *
+ * // With port specification
+ * $g->addEdge(array('node5' => 'node6'), $attr, array('node6' => 'portA'));
+ * $g->addEdge(array('node7' => 'node8'), null, array('node7' => 'portC',
+ * 'node8' => 'portD'));
+ * </code>
+ *
+ * @param array $edge Start => End node of the edge.
+ * @param array $attributes Attributes of the edge.
+ * @param array $ports Start node => port, End node => port
+ *
+ * @return integer an edge ID that can be used with {@link removeEdge()}
+ */
+ public function addEdge($edge, $attributes = array(), $ports = array())
+ {
+ if (!is_array($edge)) {
+ return;
+ }
+
+ $from = key($edge);
+ $to = $edge[$from];
+ $info = array();
+
+ if (is_array($ports)) {
+ if (array_key_exists($from, $ports)) {
+ $info['portFrom'] = $ports[$from];
+ }
+
+ if (array_key_exists($to, $ports)) {
+ $info['portTo'] = $ports[$to];
+ }
+ }
+
+ if (is_array($attributes)) {
+ $info['attributes'] = $attributes;
+ }
+
+ if (!empty($this->graph['strict'])) {
+ if (!isset($this->graph['edgesFrom'][$from][$to][0])) {
+ $this->graph['edgesFrom'][$from][$to][0] = $info;
+ } else {
+ $this->graph['edgesFrom'][$from][$to][0] = array_merge($this->graph['edgesFrom'][$from][$to][0], $info);
+ }
+ } else {
+ $this->graph['edgesFrom'][$from][$to][] = $info;
+ }
+
+ return count($this->graph['edgesFrom'][$from][$to]) - 1;
+ }
+
+ /**
+ * Removes an edge from the graph.
+ *
+ * @param array $edge Start and End node of the edge to be removed.
+ * @param integer $id specific edge ID (only usefull when multiple edges
+ * exist between the same 2 nodes)
+ *
+ * @return void
+ */
+ public function removeEdge($edge, $id = null)
+ {
+ if (!is_array($edge)) {
+ return;
+ }
+
+ $from = key($edge);
+ $to = $edge[$from];
+
+ if (!is_null($id)) {
+ if (isset($this->graph['edgesFrom'][$from][$to][$id])) {
+ unset($this->graph['edgesFrom'][$from][$to][$id]);
+
+ if (count($this->graph['edgesFrom'][$from][$to]) == 0) {
+ unset($this->graph['edgesFrom'][$from][$to]);
+ }
+ }
+ } elseif (isset($this->graph['edgesFrom'][$from][$to])) {
+ unset($this->graph['edgesFrom'][$from][$to]);
+ }
+ }
+
+ /**
+ * Adds attributes to the graph.
+ *
+ * @param array $attributes Attributes to be added to the graph.
+ *
+ * @return void
+ */
+ public function addAttributes($attributes)
+ {
+ if (is_array($attributes)) {
+ $this->graph['attributes'] = array_merge($this->graph['attributes'], $attributes);
+ }
+ }
+
+ /**
+ * Sets attributes of the graph.
+ *
+ * @param array $attributes Attributes to be set for the graph.
+ *
+ * @return void
+ */
+ public function setAttributes($attributes)
+ {
+ if (is_array($attributes)) {
+ $this->graph['attributes'] = $attributes;
+ }
+ }
+
+ /**
+ * Escapes an (attribute) array
+ *
+ * Detects if an attribute is <html>, contains double-quotes, etc...
+ *
+ * @param array $input input to escape
+ *
+ * @return array input escaped
+ */
+ protected function _escapeArray($input)
+ {
+ $output = array();
+
+ foreach ((array)$input as $k => $v) {
+ switch ($k) {
+ case 'label':
+ case 'headlabel':
+ case 'taillabel':
+ $v = $this->_escape($v, true);
+ break;
+ default:
+ $v = $this->_escape($v);
+ $k = $this->_escape($k);
+ }
+
+ $output[$k] = $v;
+ }
+
+ return $output;
+ }
+
+ /**
+ * Returns a safe "ID" in DOT syntax
+ *
+ * @param string $input string to use as "ID"
+ * @param boolean $html whether to attempt detecting HTML-like content
+ *
+ * @return string
+ */
+ protected function _escape($input, $html = false)
+ {
+ switch (strtolower($input)) {
+ case 'node':
+ case 'edge':
+ case 'graph':
+ case 'digraph':
+ case 'subgraph':
+ case 'strict':
+ return '"'.$input.'"';
+ }
+
+ if (is_bool($input)) {
+ return ($input) ? 'true' : 'false';
+ }
+
+ if ($html && (strpos($input, '</') !== false
+ || strpos($input, '/>') !== false)) {
+ return '<'.$input.'>';
+ }
+
+ if (preg_match('/^([a-z_][a-z_0-9]*|-?(\.[0-9]+|[0-9]+(\.[0-9]*)?))$/i',
+ $input)) {
+ return $input;
+ }
+
+ return '"'.str_replace(array("\r\n", "\n", "\r", '"'),
+ array('\n', '\n', '\n', '\"'), $input).'"';
+ }
+
+ /**
+ * Sets directed/undirected flag for the graph.
+ *
+ * Note: You MUST pass a boolean, and not just an expression that evaluates
+ * to TRUE or FALSE (i.e. NULL, empty string, 0 will not work)
+ *
+ * @param boolean $directed Directed (TRUE) or undirected (FALSE) graph.
+ *
+ * @return void
+ */
+ public function setDirected($directed)
+ {
+ if (is_bool($directed)) {
+ $this->graph['directed'] = $directed;
+ }
+ }
+
+ /**
+ * Loads a graph from a file in Image_GraphViz format
+ *
+ * @param string $file File to load graph from.
+ *
+ * @return void
+ */
+ public function load($file)
+ {
+ if ($serializedGraph = implode('', @file($file))) {
+ $g = unserialize($serializedGraph);
+
+ if (!is_array($g)) {
+ return;
+ }
+
+ // Convert old storage format to new one
+ $defaults = array('edgesFrom' => array(),
+ 'nodes' => array(),
+ 'attributes' => array(),
+ 'directed' => true,
+ 'clusters' => array(),
+ 'subgraphs' => array(),
+ 'name' => 'G',
+ 'strict' => true,
+ );
+
+ $this->graph = array_merge($defaults, $g);
+
+ if (isset($this->graph['edges'])) {
+ foreach ($this->graph['edges'] as $id => $nodes) {
+ $attr = (isset($this->graph['edgeAttributes'][$id]))
+ ? $this->graph['edgeAttributes'][$id]
+ : array();
+
+ $this->addEdge($nodes, $attr);
+ }
+
+ unset($this->graph['edges']);
+ unset($this->graph['edgeAttributes']);
+ }
+ }
+ }
+
+ /**
+ * Save graph to file in Image_GraphViz format
+ *
+ * This saves the serialized version of the instance, not the
+ * rendered graph.
+ *
+ * @param string $file File to save the graph to.
+ *
+ * @return string File the graph was saved to, FALSE or PEAR_Error on
+ * failure.
+ */
+ public function save($file = '')
+ {
+ $serializedGraph = serialize($this->graph);
+
+ if (empty($file)) {
+ $file = System::mktemp('graph_');
+ }
+
+ if ($fp = @fopen($file, 'wb')) {
+ @fputs($fp, $serializedGraph);
+ @fclose($fp);
+
+ return $file;
+ }
+
+ if ($this->_returnFalseOnError) {
+ return false;
+ }
+ $error = PEAR::raiseError('Could not save serialized graph instance');
+ return $error;
+ }
+
+ /**
+ * Returns a list of sub-groups for a given parent group
+ *
+ * @param string $parent Group ID
+ *
+ * @return array list of group IDs
+ */
+ protected function _getSubgraphs($parent)
+ {
+ $subgraphs = array();
+ foreach ($this->graph['clusters'] as $id => $info) {
+ if ($info['embedIn'] === $parent) {
+ $subgraphs[] = $id;
+ }
+ }
+ foreach ($this->graph['subgraphs'] as $id => $info) {
+ if ($info['embedIn'] === $parent) {
+ $subgraphs[] = $id;
+ }
+ }
+ return $subgraphs;
+ }
+
+ /**
+ * Returns a list of cluster/subgraph IDs
+ *
+ * @return array
+ */
+ protected function _getGroups()
+ {
+ $groups = array_merge(array_keys($this->graph['clusters']),
+ array_keys($this->graph['subgraphs']));
+ return array_unique($groups);
+ }
+
+ /**
+ * Returns a list of top groups
+ *
+ * @return array
+ */
+ protected function _getTopGraphs()
+ {
+ $top = array();
+ $groups = $this->_getGroups();
+
+ foreach ($groups as $id) {
+ $isTop = ($id === 'default');
+ if (isset($this->graph['clusters'][$id])
+ && $this->graph['clusters'][$id]['embedIn'] === 'default') {
+ $isTop = true;
+ }
+ if (isset($this->graph['subgraphs'][$id])
+ && $this->graph['subgraphs'][$id]['embedIn'] === 'default') {
+ $isTop = true;
+ }
+ if ($isTop) {
+ $top[] = $id;
+ }
+ }
+
+ return array_unique($top);
+ }
+
+ /**
+ * Parses the graph into GraphViz markup.
+ *
+ * @return string GraphViz markup
+ */
+ public function parse()
+ {
+ $parsedGraph = (empty($this->graph['strict'])) ? '' : 'strict ';
+ $parsedGraph .= (empty($this->graph['directed'])) ? 'graph ' : 'digraph ';
+ $parsedGraph .= $this->_escape($this->graph['name'])." {\n";
+
+ $indent = ' ';
+
+ $attr = $this->_escapeArray($this->graph['attributes']);
+
+ foreach ($attr as $key => $value) {
+ $parsedGraph .= $indent.$key.'='.$value.";\n";
+ }
+
+ $groups = $this->_getGroups();
+ foreach ($this->graph['nodes'] as $group => $nodes) {
+ if (!in_array($group, $groups)) {
+ $parsedGraph .= $this->_nodes($nodes, $indent);
+ }
+ }
+ $tops = $this->_getTopGraphs();
+ foreach ($tops as $group) {
+ $parsedGraph .= $this->_subgraph($group, $indent);
+ }
+
+ if (!empty($this->graph['directed'])) {
+ $separator = ' -> ';
+ } else {
+ $separator = ' -- ';
+ }
+
+ foreach ($this->graph['edgesFrom'] as $from => $toNodes) {
+ $from = $this->_escape($from);
+
+ foreach ($toNodes as $to => $edges) {
+ $to = $this->_escape($to);
+
+ foreach ($edges as $info) {
+ $f = $from;
+ $t = $to;
+
+ if (array_key_exists('portFrom', $info)) {
+ $f .= ':'.$this->_escape($info['portFrom']);
+ }
+
+ if (array_key_exists('portTo', $info)) {
+ $t .= ':'.$this->_escape($info['portTo']);
+ }
+
+ $parsedGraph .= $indent.$f.$separator.$t;
+
+ if (!empty($info['attributes'])) {
+ $attributeList = array();
+
+ foreach ($this->_escapeArray($info['attributes']) as $key => $value) {
+ switch ($key) {
+ case 'lhead':
+ case 'ltail':
+ if (strncasecmp($value, 'cluster', 7)) {
+ $value = 'cluster_'.$value;
+ }
+ break;
+ }
+ $attributeList[] = $key.'='.$value;
+ }
+
+ $parsedGraph .= ' [ '.implode(',', $attributeList).' ]';
+ }
+
+ $parsedGraph .= ";\n";
+ }
+ }
+ }
+
+ return $parsedGraph . "}\n";
+ }
+
+ /**
+ * Output nodes
+ *
+ * @param array $nodes nodes list
+ * @param string $indent space indentation
+ *
+ * @return string output
+ */
+ protected function _nodes($nodes, $indent)
+ {
+ $parsedGraph = '';
+ foreach ($nodes as $node => $attributes) {
+ $parsedGraph .= $indent.$this->_escape($node);
+
+ $attributeList = array();
+
+ foreach ($this->_escapeArray($attributes) as $key => $value) {
+ $attributeList[] = $key.'='.$value;
+ }
+
+ if (!empty($attributeList)) {
+ $parsedGraph .= ' [ '.implode(',', $attributeList).' ]';
+ }
+
+ $parsedGraph .= ";\n";
+ }
+ return $parsedGraph;
+ }
+
+ /**
+ * Generates output for a group
+ *
+ * @return string output
+ */
+ protected function _subgraph($group, &$indent)
+ {
+ $parsedGraph = '';
+ $nodes = $this->graph['nodes'][$group];
+
+ if ($group !== 'default') {
+ $type = null;
+ $_group = $this->_escape($group);
+
+ if (isset($this->graph['clusters'][$group])) {
+ $type = 'clusters';
+ if (strncasecmp($group, 'cluster', 7)) {
+ $_group = $this->_escape('cluster_'.$group);
+ }
+ } elseif (isset($this->graph['subgraphs'][$group])) {
+ $type = 'subgraphs';
+ }
+ $parsedGraph .= $indent.'subgraph '.$_group." {\n";
+
+ $indent .= ' ';
+
+ if ($type !== null && isset($this->graph[$type][$group])) {
+ $cluster = $this->graph[$type][$group];
+ $_attr = $this->_escapeArray($cluster['attributes']);
+
+ $attr = array();
+ foreach ($_attr as $key => $value) {
+ $attr[] = $key.'='.$value;
+ }
+
+ if (strlen($cluster['title'])) {
+ $attr[] = 'label='
+ .$this->_escape($cluster['title'], true);
+ }
+
+ if ($attr) {
+ $parsedGraph .= $indent.'graph [ '.implode(',', $attr)
+ ." ];\n";
+ }
+ }
+ }
+
+ $parsedGraph .= $this->_nodes($nodes, $indent);
+
+ foreach ($this->_getSubgraphs($group) as $_group) {
+ $parsedGraph .= $this->_subgraph($_group, $indent);
+ }
+
+ if ($group !== 'default') {
+ $indent = substr($indent, 0, -4);
+
+ $parsedGraph .= $indent."}\n";
+ }
+
+ return $parsedGraph;
+ }
+
+ /**
+ * Saves GraphViz markup to file (in DOT language)
+ *
+ * @param string $file File to write the GraphViz markup to.
+ *
+ * @return string File to which the GraphViz markup was written, FALSE or
+ * or PEAR_Error on failure.
+ */
+ public function saveParsedGraph($file = '')
+ {
+ $parsedGraph = $this->parse();
+
+ if (!empty($parsedGraph)) {
+ if (empty($file)) {
+ $file = System::mktemp('graph_');
+ }
+
+ if ($fp = @fopen($file, 'wb')) {
+ @fputs($fp, $parsedGraph);
+ @fclose($fp);
+
+ return $file;
+ }
+ }
+
+ if ($this->_returnFalseOnError) {
+ return false;
+ }
+ $error = PEAR::raiseError('Could not save graph');
+ return $error;
+ }
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * c-hanging-comment-ender-p: nil
+ * End:
+ */
+?>
diff --git a/includes/pear/MIME/Type.php b/includes/pear/MIME/Type.php
new file mode 100644
index 0000000..77491d2
--- /dev/null
+++ b/includes/pear/MIME/Type.php
@@ -0,0 +1,598 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+/**
+ * Part of MIME_Type
+ *
+ * PHP version 4 and 5
+ *
+ * @category File
+ * @package MIME_Type
+ * @author Ian Eure <ieure@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/MIME_Type
+ */
+
+require_once 'PEAR.php';
+
+$_fileCmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd');
+$_fileCmd = 'file';
+
+/**
+ * Class for working with MIME types
+ *
+ * @category MIME
+ * @package MIME_Type
+ * @author Ian Eure <ieure@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @version@
+ * @link http://pear.php.net/package/MIME_Type
+ */
+class MIME_Type
+{
+ /**
+ * The MIME media type
+ *
+ * @var string
+ */
+ var $media = '';
+
+ /**
+ * The MIME media sub-type
+ *
+ * @var string
+ */
+ var $subType = '';
+
+ /**
+ * Optional MIME parameters
+ *
+ * @var array
+ */
+ var $parameters = array();
+
+ /**
+ * List of valid media types.
+ * A media type is the string in front of the slash.
+ * The media type of "text/xml" would be "text".
+ *
+ * @var array
+ */
+ var $validMediaTypes = array(
+ 'text',
+ 'image',
+ 'audio',
+ 'video',
+ 'application',
+ 'multipart',
+ 'message'
+ );
+
+ /**
+ * If the finfo functions shall be used when they are available
+ *
+ * @var boolean
+ */
+ var $useFinfo = true;
+
+ /**
+ * If mime_content_type shall be used when available
+ *
+ * @var boolean
+ */
+ var $useMimeContentType = true;
+
+ /**
+ * If the file command shall be used when available
+ *
+ * @var boolean
+ */
+ var $useFileCmd = true;
+
+ /**
+ * If the in-built file extension detection shall be used
+ *
+ * @var boolean
+ */
+ var $useExtension = true;
+
+ /**
+ * Path to the "magic" file database.
+ * If NULL, the default one is used
+ *
+ * @var string
+ */
+ var $magicFile = null;
+
+
+ /**
+ * Constructor.
+ *
+ * If $type is set, if will be parsed and the appropriate class vars set.
+ * If not, you get an empty class.
+ * This is useful, but not quite as useful as parsing a type.
+ *
+ * @param string $type MIME type
+ *
+ * @return void
+ */
+ function MIME_Type($type = false)
+ {
+ if ($type) {
+ $this->parse($type);
+ }
+ }
+
+
+ /**
+ * Parse a mime-type and set the class variables.
+ *
+ * @param string $type MIME type to parse
+ *
+ * @return boolean True if the type has been parsed, false if not
+ */
+ function parse($type)
+ {
+ if ($type instanceof PEAR_Error) {
+ return false;
+ }
+
+ $this->media = $this->getMedia($type);
+ $this->subType = $this->getSubType($type);
+ $this->parameters = array();
+ if (MIME_Type::hasParameters($type)) {
+ include_once 'MIME/Type/Parameter.php';
+ foreach (MIME_Type::getParameters($type) as $param) {
+ $param = new MIME_Type_Parameter($param);
+ $this->parameters[$param->name] = $param;
+ }
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Does this type have any parameters?
+ *
+ * @param string $type MIME type to check
+ *
+ * @return boolean true if $type has parameters, false otherwise
+ * @static
+ */
+ function hasParameters($type)
+ {
+ if (strstr($type, ';')) {
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Get a MIME type's parameters
+ *
+ * @param string $type MIME type to get parameters of
+ *
+ * @return array $type's parameters
+ * @static
+ */
+ function getParameters($type)
+ {
+ $params = array();
+ $tmp = explode(';', $type);
+ for ($i = 1; $i < count($tmp); $i++) {
+ $params[] = trim($tmp[$i]);
+ }
+ return $params;
+ }
+
+
+ /**
+ * Strip parameters from a MIME type string.
+ *
+ * @param string $type MIME type string
+ *
+ * @return string MIME type with parameters removed
+ * @static
+ */
+ function stripParameters($type)
+ {
+ if (strstr($type, ';')) {
+ return substr($type, 0, strpos($type, ';'));
+ }
+ return $type;
+ }
+
+
+ /**
+ * Removes comments from a media type, subtype or parameter.
+ *
+ * @param string $string String to strip comments from
+ * @param string &$comment Comment is stored in there.
+ * Do not set it to NULL if you want the comment.
+ *
+ * @return string String without comments
+ * @static
+ */
+ function stripComments($string, &$comment)
+ {
+ if (strpos($string, '(') === false) {
+ return $string;
+ }
+
+ $inquote = false;
+ $escaped = false;
+ $incomment = 0;
+ $newstring = '';
+
+ for ($n = 0; $n < strlen($string); $n++) {
+ if ($escaped) {
+ if ($incomment == 0) {
+ $newstring .= $string[$n];
+ } else if ($comment !== null) {
+ $comment .= $string[$n];
+ }
+ $escaped = false;
+ } else if ($string[$n] == '\\') {
+ $escaped = true;
+ } else if (!$inquote && $incomment > 0 && $string[$n] == ')') {
+ $incomment--;
+ if ($incomment == 0 && $comment !== null) {
+ $comment .= ' ';
+ }
+ } else if (!$inquote && $string[$n] == '(') {
+ $incomment++;
+ } else if ($string[$n] == '"') {
+ if ($inquote) {
+ $inquote = false;
+ } else {
+ $inquote = true;
+ }
+ } else if ($incomment == 0) {
+ $newstring .= $string[$n];
+ } else if ($comment !== null) {
+ $comment .= $string[$n];
+ }
+ }
+
+ if ($comment !== null) {
+ $comment = trim($comment);
+ }
+
+ return $newstring;
+ }
+
+
+ /**
+ * Get a MIME type's media
+ * Note: 'media' refers to the portion before the first slash
+ *
+ * @param string $type MIME type to get media of
+ *
+ * @return string $type's media
+ * @static
+ */
+ function getMedia($type)
+ {
+ $tmp = explode('/', $type);
+ return strtolower(trim(MIME_Type::stripComments($tmp[0], $null)));
+ }
+
+
+ /**
+ * Get a MIME type's subtype
+ *
+ * @param string $type MIME type to get subtype of
+ *
+ * @return string $type's subtype, null if invalid mime type
+ * @static
+ */
+ function getSubType($type)
+ {
+ $tmp = explode('/', $type);
+ if (!isset($tmp[1])) {
+ return null;
+ }
+ $tmp = explode(';', $tmp[1]);
+ return strtolower(trim(MIME_Type::stripComments($tmp[0], $null)));
+ }
+
+
+ /**
+ * Create a textual MIME type from object values
+ *
+ * This function performs the opposite function of parse().
+ *
+ * @return string MIME type string
+ */
+ function get()
+ {
+ $type = strtolower($this->media . '/' . $this->subType);
+ if (count($this->parameters)) {
+ foreach ($this->parameters as $key => $null) {
+ $type .= '; ' . $this->parameters[$key]->get();
+ }
+ }
+ return $type;
+ }
+
+
+ /**
+ * Is this type experimental?
+ *
+ * Note: Experimental types are denoted by a leading 'x-' in the media or
+ * subtype, e.g. text/x-vcard or x-world/x-vrml.
+ *
+ * @param string $type MIME type to check
+ *
+ * @return boolean true if $type is experimental, false otherwise
+ * @static
+ */
+ function isExperimental($type)
+ {
+ if (substr(MIME_Type::getMedia($type), 0, 2) == 'x-'
+ || substr(MIME_Type::getSubType($type), 0, 2) == 'x-'
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Is this a vendor MIME type?
+ *
+ * Note: Vendor types are denoted with a leading 'vnd. in the subtype.
+ *
+ * @param string $type MIME type to check
+ *
+ * @return boolean true if $type is a vendor type, false otherwise
+ * @static
+ */
+ function isVendor($type)
+ {
+ if (substr(MIME_Type::getSubType($type), 0, 4) == 'vnd.') {
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Is this a wildcard type?
+ *
+ * @param string $type MIME type to check
+ *
+ * @return boolean true if $type is a wildcard, false otherwise
+ * @static
+ */
+ function isWildcard($type)
+ {
+ if ($type == '*/*' || MIME_Type::getSubtype($type) == '*') {
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Perform a wildcard match on a MIME type
+ *
+ * Example:
+ * MIME_Type::wildcardMatch('image/*', 'image/png')
+ *
+ * @param string $card Wildcard to check against
+ * @param string $type MIME type to check
+ *
+ * @return boolean true if there was a match, false otherwise
+ * @static
+ */
+ function wildcardMatch($card, $type)
+ {
+ if (!MIME_Type::isWildcard($card)) {
+ return false;
+ }
+
+ if ($card == '*/*') {
+ return true;
+ }
+
+ if (MIME_Type::getMedia($card) == MIME_Type::getMedia($type)) {
+ return true;
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Add a parameter to this type
+ *
+ * @param string $name Attribute name
+ * @param string $value Attribute value
+ * @param string $comment Comment for this parameter
+ *
+ * @return void
+ */
+ function addParameter($name, $value, $comment = false)
+ {
+ $tmp = new MIME_Type_Parameter();
+
+ $tmp->name = $name;
+ $tmp->value = $value;
+ $tmp->comment = $comment;
+ $this->parameters[$name] = $tmp;
+ }
+
+
+ /**
+ * Remove a parameter from this type
+ *
+ * @param string $name Parameter name
+ *
+ * @return void
+ */
+ function removeParameter($name)
+ {
+ unset($this->parameters[$name]);
+ }
+
+
+ /**
+ * Autodetect a file's MIME-type
+ *
+ * This function may be called staticly.
+ *
+ * @param string $file Path to the file to get the type of
+ * @param bool $params Append MIME parameters if true
+ *
+ * @return string $file's MIME-type on success, PEAR_Error otherwise
+ *
+ * @since 1.0.0beta1
+ * @static
+ */
+ function autoDetect($file, $params = false)
+ {
+ $isStatic = !(isset($this) && get_class($this) == __CLASS__);
+ if ($isStatic) {
+ $t = new MIME_Type();
+ return $t->_autoDetect($file, $params);
+ } else {
+ $type = $this->_autoDetect($file, $params);
+ if (!$type instanceof PEAR_Error) {
+ $this->parse($type);
+ }
+ return $type;
+ }
+ }
+
+ /**
+ * Autodetect a file's MIME-type
+ *
+ * @param string $file Path to the file to get the type of
+ * @param bool $params Append MIME parameters if true
+ *
+ * @return string $file's MIME-type on success, PEAR_Error otherwise
+ *
+ * @since 1.3.0
+ *
+ * @internal Tries to use fileinfo extension at first. If that
+ * does not work, mime_magic is used. If this is also not available
+ * or does not succeed, "file" command is tried to be executed with
+ * System_Command. When that fails, too, then we use our in-built
+ * extension-to-mimetype-mapping list.
+ */
+ function _autoDetect($file, $params = false)
+ {
+ // Sanity checks
+ if (!file_exists($file)) {
+ return PEAR::raiseError("File \"$file\" doesn't exist");
+ }
+
+ if (!is_readable($file)) {
+ return PEAR::raiseError("File \"$file\" is not readable");
+ }
+
+ if ($this->useFinfo && function_exists('finfo_file')) {
+ $finfo = finfo_open(FILEINFO_MIME, $this->magicFile);
+ if ($finfo) {
+ $type = finfo_file($finfo, $file);
+ finfo_close($finfo);
+ if ($type !== false && $type !== '') {
+ return MIME_Type::_handleDetection($type, $params);
+ }
+ }
+ }
+
+ if ($this->useMimeContentType && function_exists('mime_content_type')) {
+ $type = mime_content_type($file);
+ if ($type !== false && $type !== '') {
+ return MIME_Type::_handleDetection($type, $params);
+ }
+ }
+
+ if ($this->useFileCmd) {
+ @include_once 'System/Command.php';
+ if (class_exists('System_Command')) {
+ $type = MIME_Type::_fileAutoDetect($file);
+ if ($type !== false && $type !== '') {
+ return MIME_Type::_handleDetection($type, $params);
+ }
+ }
+ }
+
+ if ($this->useExtension) {
+ include_once 'MIME/Type/Extension.php';
+ $mte = new MIME_Type_Extension();
+ return $mte->getMIMEType($file);
+ }
+
+ return PEAR::raiseError("Sorry, couldn't determine file type.");
+ }
+
+
+ /**
+ * Handles a detected MIME type and modifies it if necessary.
+ *
+ * @param string $type MIME Type of a file
+ * @param bool $params Append MIME parameters if true
+ *
+ * @return string $file's MIME-type on success, PEAR_Error otherwise
+ * @static
+ */
+ function _handleDetection($type, $params)
+ {
+ // _fileAutoDetect() may have returned an error.
+ if (PEAR::isError($type)) {
+ return $type;
+ }
+
+ // Don't return an empty string
+ if (!$type || !strlen($type)) {
+ return PEAR::raiseError("Sorry, couldn't determine file type.");
+ }
+
+ // Strip parameters if present & requested
+ if (MIME_Type::hasParameters($type) && !$params) {
+ $type = MIME_Type::stripParameters($type);
+ }
+
+ return $type;
+ }
+
+
+ /**
+ * Autodetect a file's MIME-type with 'file' and System_Command
+ *
+ * This function may be called staticly.
+ *
+ * @param string $file Path to the file to get the type of
+ *
+ * @return string $file's MIME-type
+ *
+ * @since 1.0.0beta1
+ * @static
+ */
+ function _fileAutoDetect($file)
+ {
+ $cmd = new System_Command();
+ $magic = '';
+
+ // Make sure we have the 'file' command.
+ $fileCmd = PEAR::getStaticProperty('MIME_Type', 'fileCmd');
+ if (!$cmd->which($fileCmd)) {
+ unset($cmd);
+ return PEAR::raiseError("Can't find file command \"{$fileCmd}\"");
+ }
+ if (strlen($this->magicFile)) {
+ $magic = '--magic-file ' . escapeshellarg($this->magicFile);
+ }
+
+ $cmd->pushCommand($fileCmd, $magic, "-bi " . escapeshellarg($file));
+ $res = $cmd->execute();
+ unset($cmd);
+
+ return $res;
+ }
+
+} \ No newline at end of file
diff --git a/includes/pear/MIME/Type/Extension.php b/includes/pear/MIME/Type/Extension.php
new file mode 100644
index 0000000..7f3da7a
--- /dev/null
+++ b/includes/pear/MIME/Type/Extension.php
@@ -0,0 +1,292 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+/**
+ * Part of MIME_Type
+ *
+ * PHP version 4 and 5
+ *
+ * @category File
+ * @package MIME_Type
+ * @author Christian Schmidt <schmidt@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/MIME_Type
+ */
+
+require_once 'PEAR.php';
+
+/**
+ * Class for mapping file extensions to MIME types.
+ *
+ * @category MIME
+ * @package MIME_Type
+ * @author Christian Schmidt <schmidt@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @version@
+ * @link http://pear.php.net/package/MIME_Type
+ */
+class MIME_Type_Extension
+{
+ /**
+ * Mapping between file extension and MIME type.
+ *
+ * @internal The array is sorted alphabetically by value and with primary
+ * extension first. Be careful about not adding duplicate keys - PHP
+ * silently ignores duplicates. The following command can be used for
+ * checking for duplicates:
+ * grep "=> '" Extension.php | cut -d\' -f2 | sort | uniq -d
+ * application/octet-stream is generally used as fallback when no other
+ * MIME-type can be found, but the array does not contain a lot of such
+ * unknown extension. One entry exists, though, to allow detection of
+ * file extension for this MIME-type.
+ *
+ * @var array
+ */
+ var $extensionToType = array (
+ 'ez' => 'application/andrew-inset',
+ 'atom' => 'application/atom+xml',
+ 'jar' => 'application/java-archive',
+ 'hqx' => 'application/mac-binhex40',
+ 'cpt' => 'application/mac-compactpro',
+ 'mathml' => 'application/mathml+xml',
+ 'doc' => 'application/msword',
+ 'dat' => 'application/octet-stream',
+ 'oda' => 'application/oda',
+ 'ogg' => 'application/ogg',
+ 'pdf' => 'application/pdf',
+ 'ai' => 'application/postscript',
+ 'eps' => 'application/postscript',
+ 'ps' => 'application/postscript',
+ 'rdf' => 'application/rdf+xml',
+ 'rss' => 'application/rss+xml',
+ 'smi' => 'application/smil',
+ 'smil' => 'application/smil',
+ 'gram' => 'application/srgs',
+ 'grxml' => 'application/srgs+xml',
+ 'kml' => 'application/vnd.google-earth.kml+xml',
+ 'kmz' => 'application/vnd.google-earth.kmz',
+ 'mif' => 'application/vnd.mif',
+ 'xul' => 'application/vnd.mozilla.xul+xml',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlb' => 'application/vnd.ms-excel',
+ 'xlt' => 'application/vnd.ms-excel',
+ 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
+ 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
+ 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
+ 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
+ 'docm' => 'application/vnd.ms-word.document.macroEnabled.12',
+ 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
+ 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
+ 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
+ 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
+ 'potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'pps' => 'application/vnd.ms-powerpoint',
+ 'odc' => 'application/vnd.oasis.opendocument.chart',
+ 'odb' => 'application/vnd.oasis.opendocument.database',
+ 'odf' => 'application/vnd.oasis.opendocument.formula',
+ 'odg' => 'application/vnd.oasis.opendocument.graphics',
+ 'otg' => 'application/vnd.oasis.opendocument.graphics-template',
+ 'odi' => 'application/vnd.oasis.opendocument.image',
+ 'odp' => 'application/vnd.oasis.opendocument.presentation',
+ 'otp' => 'application/vnd.oasis.opendocument.presentation-template',
+ 'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
+ 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
+ 'odt' => 'application/vnd.oasis.opendocument.text',
+ 'odm' => 'application/vnd.oasis.opendocument.text-master',
+ 'ott' => 'application/vnd.oasis.opendocument.text-template',
+ 'oth' => 'application/vnd.oasis.opendocument.text-web',
+ 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
+ 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+ 'vsd' => 'application/vnd.visio',
+ 'wbxml' => 'application/vnd.wap.wbxml',
+ 'wmlc' => 'application/vnd.wap.wmlc',
+ 'wmlsc' => 'application/vnd.wap.wmlscriptc',
+ 'vxml' => 'application/voicexml+xml',
+ 'bcpio' => 'application/x-bcpio',
+ 'vcd' => 'application/x-cdlink',
+ 'pgn' => 'application/x-chess-pgn',
+ 'cpio' => 'application/x-cpio',
+ 'csh' => 'application/x-csh',
+ 'dcr' => 'application/x-director',
+ 'dir' => 'application/x-director',
+ 'dxr' => 'application/x-director',
+ 'dvi' => 'application/x-dvi',
+ 'spl' => 'application/x-futuresplash',
+ 'tgz' => 'application/x-gtar',
+ 'gtar' => 'application/x-gtar',
+ 'hdf' => 'application/x-hdf',
+ 'js' => 'application/x-javascript',
+ 'skp' => 'application/x-koan',
+ 'skd' => 'application/x-koan',
+ 'skt' => 'application/x-koan',
+ 'skm' => 'application/x-koan',
+ 'latex' => 'application/x-latex',
+ 'nc' => 'application/x-netcdf',
+ 'cdf' => 'application/x-netcdf',
+ 'sh' => 'application/x-sh',
+ 'shar' => 'application/x-shar',
+ 'swf' => 'application/x-shockwave-flash',
+ 'sit' => 'application/x-stuffit',
+ 'sv4cpio' => 'application/x-sv4cpio',
+ 'sv4crc' => 'application/x-sv4crc',
+ 'tar' => 'application/x-tar',
+ 'tcl' => 'application/x-tcl',
+ 'tex' => 'application/x-tex',
+ 'texinfo' => 'application/x-texinfo',
+ 'texi' => 'application/x-texinfo',
+ 't' => 'application/x-troff',
+ 'tr' => 'application/x-troff',
+ 'roff' => 'application/x-troff',
+ 'man' => 'application/x-troff-man',
+ 'me' => 'application/x-troff-me',
+ 'ms' => 'application/x-troff-ms',
+ 'ustar' => 'application/x-ustar',
+ 'src' => 'application/x-wais-source',
+ 'xhtml' => 'application/xhtml+xml',
+ 'xht' => 'application/xhtml+xml',
+ 'xslt' => 'application/xslt+xml',
+ 'xml' => 'application/xml',
+ 'xsl' => 'application/xml',
+ 'dtd' => 'application/xml-dtd',
+ 'zip' => 'application/zip',
+ 'au' => 'audio/basic',
+ 'snd' => 'audio/basic',
+ 'mid' => 'audio/midi',
+ 'midi' => 'audio/midi',
+ 'kar' => 'audio/midi',
+ 'mpga' => 'audio/mpeg',
+ 'mp2' => 'audio/mpeg',
+ 'mp3' => 'audio/mpeg',
+ 'aif' => 'audio/x-aiff',
+ 'aiff' => 'audio/x-aiff',
+ 'aifc' => 'audio/x-aiff',
+ 'm3u' => 'audio/x-mpegurl',
+ 'wma' => 'audio/x-ms-wma',
+ 'wax' => 'audio/x-ms-wax',
+ 'ram' => 'audio/x-pn-realaudio',
+ 'ra' => 'audio/x-pn-realaudio',
+ 'rm' => 'application/vnd.rn-realmedia',
+ 'wav' => 'audio/x-wav',
+ 'pdb' => 'chemical/x-pdb',
+ 'xyz' => 'chemical/x-xyz',
+ 'bmp' => 'image/bmp',
+ 'cgm' => 'image/cgm',
+ 'gif' => 'image/gif',
+ 'ief' => 'image/ief',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'jpe' => 'image/jpeg',
+ 'png' => 'image/png',
+ 'svg' => 'image/svg+xml',
+ 'tiff' => 'image/tiff',
+ 'tif' => 'image/tiff',
+ 'djvu' => 'image/vnd.djvu',
+ 'djv' => 'image/vnd.djvu',
+ 'wbmp' => 'image/vnd.wap.wbmp',
+ 'ras' => 'image/x-cmu-raster',
+ 'ico' => 'image/x-icon',
+ 'pnm' => 'image/x-portable-anymap',
+ 'pbm' => 'image/x-portable-bitmap',
+ 'pgm' => 'image/x-portable-graymap',
+ 'ppm' => 'image/x-portable-pixmap',
+ 'rgb' => 'image/x-rgb',
+ 'xbm' => 'image/x-xbitmap',
+ 'psd' => 'image/x-photoshop',
+ 'xpm' => 'image/x-xpixmap',
+ 'xwd' => 'image/x-xwindowdump',
+ 'eml' => 'message/rfc822',
+ 'igs' => 'model/iges',
+ 'iges' => 'model/iges',
+ 'msh' => 'model/mesh',
+ 'mesh' => 'model/mesh',
+ 'silo' => 'model/mesh',
+ 'wrl' => 'model/vrml',
+ 'vrml' => 'model/vrml',
+ 'ics' => 'text/calendar',
+ 'ifb' => 'text/calendar',
+ 'css' => 'text/css',
+ 'csv' => 'text/csv',
+ 'html' => 'text/html',
+ 'htm' => 'text/html',
+ 'txt' => 'text/plain',
+ 'asc' => 'text/plain',
+ 'rtx' => 'text/richtext',
+ 'rtf' => 'text/rtf',
+ 'sgml' => 'text/sgml',
+ 'sgm' => 'text/sgml',
+ 'tsv' => 'text/tab-separated-values',
+ 'wml' => 'text/vnd.wap.wml',
+ 'wmls' => 'text/vnd.wap.wmlscript',
+ 'etx' => 'text/x-setext',
+ 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg',
+ 'mpe' => 'video/mpeg',
+ 'qt' => 'video/quicktime',
+ 'mov' => 'video/quicktime',
+ 'mxu' => 'video/vnd.mpegurl',
+ 'm4u' => 'video/vnd.mpegurl',
+ 'flv' => 'video/x-flv',
+ 'asf' => 'video/x-ms-asf',
+ 'asx' => 'video/x-ms-asf',
+ 'wmv' => 'video/x-ms-wmv',
+ 'wm' => 'video/x-ms-wm',
+ 'wmx' => 'video/x-ms-wmx',
+ 'avi' => 'video/x-msvideo',
+ 'ogv' => 'video/ogg',
+ 'movie' => 'video/x-sgi-movie',
+ 'ice' => 'x-conference/x-cooltalk',
+ );
+
+
+
+ /**
+ * Autodetect a file's MIME-type.
+ *
+ * @param string $file Path to the file to get the type of
+ *
+ * @return string $file's MIME-type on success, PEAR_Error otherwise
+ */
+ function getMIMEType($file)
+ {
+ $extension = substr(strrchr($file, '.'), 1);
+ if ($extension === false) {
+ return PEAR::raiseError("File has no extension.");
+ }
+
+ if (!isset($this->extensionToType[$extension])) {
+ return PEAR::raiseError("Sorry, couldn't determine file type.");
+ }
+
+ return $this->extensionToType[$extension];
+ }
+
+
+
+ /**
+ * Return default MIME-type for the specified extension.
+ *
+ * @param string $type MIME-type
+ *
+ * @return string A file extension without leading period.
+ */
+ function getExtension($type)
+ {
+ include_once 'MIME/Type.php';
+ // Strip parameters and comments.
+ $type = MIME_Type::getMedia($type) . '/' . MIME_Type::getSubType($type);
+
+ $extension = array_search($type, $this->extensionToType);
+ if ($extension === false) {
+ return PEAR::raiseError("Sorry, couldn't determine extension.");
+ }
+ return $extension;
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/MIME/Type/Parameter.php b/includes/pear/MIME/Type/Parameter.php
new file mode 100644
index 0000000..759fd58
--- /dev/null
+++ b/includes/pear/MIME/Type/Parameter.php
@@ -0,0 +1,170 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+/**
+ * Part of MIME_Type
+ *
+ * PHP version 4 and 5
+ *
+ * @category File
+ * @package MIME_Type
+ * @author Ian Eure <ieure@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @link http://pear.php.net/package/MIME_Type
+ */
+
+/**
+ * Class for working with MIME type parameters
+ *
+ * @category File
+ * @package MIME_Type
+ * @author Ian Eure <ieure@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ * @version Release: @version@
+ * @link http://pear.php.net/package/MIME_Type
+ */
+class MIME_Type_Parameter
+{
+ /**
+ * Parameter name
+ *
+ * @var string
+ */
+ var $name;
+
+ /**
+ * Parameter value
+ *
+ * @var string
+ */
+ var $value;
+
+ /**
+ * Parameter comment
+ *
+ * @var string
+ */
+ var $comment;
+
+
+ /**
+ * Constructor.
+ *
+ * @param string $param MIME parameter to parse, if set.
+ *
+ * @return void
+ */
+ function MIME_Type_Parameter($param = false)
+ {
+ if ($param) {
+ $this->parse($param);
+ }
+ }
+
+
+ /**
+ * Parse a MIME type parameter and set object fields
+ *
+ * @param string $param MIME type parameter to parse
+ *
+ * @return void
+ */
+ function parse($param)
+ {
+ $comment = '';
+ $param = MIME_Type::stripComments($param, $comment);
+ $this->name = $this->getAttribute($param);
+ $this->value = $this->getValue($param);
+ $this->comment = $comment;
+ }
+
+
+ /**
+ * Get a parameter attribute (e.g. name)
+ *
+ * @param string $param MIME type parameter
+ *
+ * @return string Attribute name
+ * @static
+ */
+ function getAttribute($param)
+ {
+ $tmp = explode('=', $param);
+ return trim($tmp[0]);
+ }
+
+
+ /**
+ * Get a parameter value
+ *
+ * @param string $param MIME type parameter
+ *
+ * @return string Value
+ * @static
+ */
+ function getValue($param)
+ {
+ $tmp = explode('=', $param, 2);
+ $value = $tmp[1];
+ $value = trim($value);
+ if ($value[0] == '"' && $value[strlen($value)-1] == '"') {
+ $value = substr($value, 1, -1);
+ }
+ $value = str_replace('\\"', '"', $value);
+ return $value;
+ }
+
+
+ /**
+ * Get a parameter comment
+ *
+ * @param string $param MIME type parameter
+ *
+ * @return string Parameter comment
+ * @see hasComment()
+ * @static
+ */
+ function getComment($param)
+ {
+ $cs = strpos($param, '(');
+ if ($cs === false) {
+ return null;
+ }
+ $comment = substr($param, $cs);
+ return trim($comment, '() ');
+ }
+
+
+ /**
+ * Does this parameter have a comment?
+ *
+ * @param string $param MIME type parameter
+ *
+ * @return boolean true if $param has a comment, false otherwise
+ * @static
+ */
+ function hasComment($param)
+ {
+ if (strstr($param, '(')) {
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Get a string representation of this parameter
+ *
+ * This function performs the oppsite of parse()
+ *
+ * @return string String representation of parameter
+ */
+ function get()
+ {
+ $val = $this->name . '="' . str_replace('"', '\\"', $this->value) . '"';
+ if ($this->comment) {
+ $val .= ' (' . $this->comment . ')';
+ }
+ return $val;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/OS/Guess.php b/includes/pear/OS/Guess.php
new file mode 100644
index 0000000..6f41f32
--- /dev/null
+++ b/includes/pear/OS/Guess.php
@@ -0,0 +1,338 @@
+<?php
+/**
+ * The OS_Guess class
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Gregory Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since PEAR 0.1
+ */
+
+// {{{ uname examples
+
+// php_uname() without args returns the same as 'uname -a', or a PHP-custom
+// string for Windows.
+// PHP versions prior to 4.3 return the uname of the host where PHP was built,
+// as of 4.3 it returns the uname of the host running the PHP code.
+//
+// PC RedHat Linux 7.1:
+// Linux host.example.com 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686 unknown
+//
+// PC Debian Potato:
+// Linux host 2.4.17 #2 SMP Tue Feb 12 15:10:04 CET 2002 i686 unknown
+//
+// PC FreeBSD 3.3:
+// FreeBSD host.example.com 3.3-STABLE FreeBSD 3.3-STABLE #0: Mon Feb 21 00:42:31 CET 2000 root@example.com:/usr/src/sys/compile/CONFIG i386
+//
+// PC FreeBSD 4.3:
+// FreeBSD host.example.com 4.3-RELEASE FreeBSD 4.3-RELEASE #1: Mon Jun 25 11:19:43 EDT 2001 root@example.com:/usr/src/sys/compile/CONFIG i386
+//
+// PC FreeBSD 4.5:
+// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb 6 23:59:23 CET 2002 root@example.com:/usr/src/sys/compile/CONFIG i386
+//
+// PC FreeBSD 4.5 w/uname from GNU shellutils:
+// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb i386 unknown
+//
+// HP 9000/712 HP-UX 10:
+// HP-UX iq B.10.10 A 9000/712 2008429113 two-user license
+//
+// HP 9000/712 HP-UX 10 w/uname from GNU shellutils:
+// HP-UX host B.10.10 A 9000/712 unknown
+//
+// IBM RS6000/550 AIX 4.3:
+// AIX host 3 4 000003531C00
+//
+// AIX 4.3 w/uname from GNU shellutils:
+// AIX host 3 4 000003531C00 unknown
+//
+// SGI Onyx IRIX 6.5 w/uname from GNU shellutils:
+// IRIX64 host 6.5 01091820 IP19 mips
+//
+// SGI Onyx IRIX 6.5:
+// IRIX64 host 6.5 01091820 IP19
+//
+// SparcStation 20 Solaris 8 w/uname from GNU shellutils:
+// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc
+//
+// SparcStation 20 Solaris 8:
+// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc SUNW,SPARCstation-20
+//
+// Mac OS X (Darwin)
+// Darwin home-eden.local 7.5.0 Darwin Kernel Version 7.5.0: Thu Aug 5 19:26:16 PDT 2004; root:xnu/xnu-517.7.21.obj~3/RELEASE_PPC Power Macintosh
+//
+// Mac OS X early versions
+//
+
+// }}}
+
+/* TODO:
+ * - define endianness, to allow matchSignature("bigend") etc.
+ */
+
+/**
+ * Retrieves information about the current operating system
+ *
+ * This class uses php_uname() to grok information about the current OS
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Gregory Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ */
+class OS_Guess
+{
+ var $sysname;
+ var $nodename;
+ var $cpu;
+ var $release;
+ var $extra;
+
+ function OS_Guess($uname = null)
+ {
+ list($this->sysname,
+ $this->release,
+ $this->cpu,
+ $this->extra,
+ $this->nodename) = $this->parseSignature($uname);
+ }
+
+ function parseSignature($uname = null)
+ {
+ static $sysmap = array(
+ 'HP-UX' => 'hpux',
+ 'IRIX64' => 'irix',
+ );
+ static $cpumap = array(
+ 'i586' => 'i386',
+ 'i686' => 'i386',
+ 'ppc' => 'powerpc',
+ );
+ if ($uname === null) {
+ $uname = php_uname();
+ }
+ $parts = preg_split('/\s+/', trim($uname));
+ $n = count($parts);
+
+ $release = $machine = $cpu = '';
+ $sysname = $parts[0];
+ $nodename = $parts[1];
+ $cpu = $parts[$n-1];
+ $extra = '';
+ if ($cpu == 'unknown') {
+ $cpu = $parts[$n - 2];
+ }
+
+ switch ($sysname) {
+ case 'AIX' :
+ $release = "$parts[3].$parts[2]";
+ break;
+ case 'Windows' :
+ switch ($parts[1]) {
+ case '95/98':
+ $release = '9x';
+ break;
+ default:
+ $release = $parts[1];
+ break;
+ }
+ $cpu = 'i386';
+ break;
+ case 'Linux' :
+ $extra = $this->_detectGlibcVersion();
+ // use only the first two digits from the kernel version
+ $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]);
+ break;
+ case 'Mac' :
+ $sysname = 'darwin';
+ $nodename = $parts[2];
+ $release = $parts[3];
+ if ($cpu == 'Macintosh') {
+ if ($parts[$n - 2] == 'Power') {
+ $cpu = 'powerpc';
+ }
+ }
+ break;
+ case 'Darwin' :
+ if ($cpu == 'Macintosh') {
+ if ($parts[$n - 2] == 'Power') {
+ $cpu = 'powerpc';
+ }
+ }
+ $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]);
+ break;
+ default:
+ $release = preg_replace('/-.*/', '', $parts[2]);
+ break;
+ }
+
+ if (isset($sysmap[$sysname])) {
+ $sysname = $sysmap[$sysname];
+ } else {
+ $sysname = strtolower($sysname);
+ }
+ if (isset($cpumap[$cpu])) {
+ $cpu = $cpumap[$cpu];
+ }
+ return array($sysname, $release, $cpu, $extra, $nodename);
+ }
+
+ function _detectGlibcVersion()
+ {
+ static $glibc = false;
+ if ($glibc !== false) {
+ return $glibc; // no need to run this multiple times
+ }
+ $major = $minor = 0;
+ include_once "System.php";
+ // Use glibc's <features.h> header file to
+ // get major and minor version number:
+ if (@file_exists('/usr/include/features.h') &&
+ @is_readable('/usr/include/features.h')) {
+ if (!@file_exists('/usr/bin/cpp') || !@is_executable('/usr/bin/cpp')) {
+ $features_file = fopen('/usr/include/features.h', 'rb');
+ while (!feof($features_file)) {
+ $line = fgets($features_file, 8192);
+ if (!$line || (strpos($line, '#define') === false)) {
+ continue;
+ }
+ if (strpos($line, '__GLIBC__')) {
+ // major version number #define __GLIBC__ version
+ $line = preg_split('/\s+/', $line);
+ $glibc_major = trim($line[2]);
+ if (isset($glibc_minor)) {
+ break;
+ }
+ continue;
+ }
+
+ if (strpos($line, '__GLIBC_MINOR__')) {
+ // got the minor version number
+ // #define __GLIBC_MINOR__ version
+ $line = preg_split('/\s+/', $line);
+ $glibc_minor = trim($line[2]);
+ if (isset($glibc_major)) {
+ break;
+ }
+ continue;
+ }
+ }
+ fclose($features_file);
+ if (!isset($glibc_major) || !isset($glibc_minor)) {
+ return $glibc = '';
+ }
+ return $glibc = 'glibc' . trim($glibc_major) . "." . trim($glibc_minor) ;
+ } // no cpp
+
+ $tmpfile = System::mktemp("glibctest");
+ $fp = fopen($tmpfile, "w");
+ fwrite($fp, "#include <features.h>\n__GLIBC__ __GLIBC_MINOR__\n");
+ fclose($fp);
+ $cpp = popen("/usr/bin/cpp $tmpfile", "r");
+ while ($line = fgets($cpp, 1024)) {
+ if ($line{0} == '#' || trim($line) == '') {
+ continue;
+ }
+
+ if (list($major, $minor) = explode(' ', trim($line))) {
+ break;
+ }
+ }
+ pclose($cpp);
+ unlink($tmpfile);
+ } // features.h
+
+ if (!($major && $minor) && @is_link('/lib/libc.so.6')) {
+ // Let's try reading the libc.so.6 symlink
+ if (preg_match('/^libc-(.*)\.so$/', basename(readlink('/lib/libc.so.6')), $matches)) {
+ list($major, $minor) = explode('.', $matches[1]);
+ }
+ }
+
+ if (!($major && $minor)) {
+ return $glibc = '';
+ }
+
+ return $glibc = "glibc{$major}.{$minor}";
+ }
+
+ function getSignature()
+ {
+ if (empty($this->extra)) {
+ return "{$this->sysname}-{$this->release}-{$this->cpu}";
+ }
+ return "{$this->sysname}-{$this->release}-{$this->cpu}-{$this->extra}";
+ }
+
+ function getSysname()
+ {
+ return $this->sysname;
+ }
+
+ function getNodename()
+ {
+ return $this->nodename;
+ }
+
+ function getCpu()
+ {
+ return $this->cpu;
+ }
+
+ function getRelease()
+ {
+ return $this->release;
+ }
+
+ function getExtra()
+ {
+ return $this->extra;
+ }
+
+ function matchSignature($match)
+ {
+ $fragments = is_array($match) ? $match : explode('-', $match);
+ $n = count($fragments);
+ $matches = 0;
+ if ($n > 0) {
+ $matches += $this->_matchFragment($fragments[0], $this->sysname);
+ }
+ if ($n > 1) {
+ $matches += $this->_matchFragment($fragments[1], $this->release);
+ }
+ if ($n > 2) {
+ $matches += $this->_matchFragment($fragments[2], $this->cpu);
+ }
+ if ($n > 3) {
+ $matches += $this->_matchFragment($fragments[3], $this->extra);
+ }
+ return ($matches == $n);
+ }
+
+ function _matchFragment($fragment, $value)
+ {
+ if (strcspn($fragment, '*?') < strlen($fragment)) {
+ $reg = '/^' . str_replace(array('*', '?', '/'), array('.*', '.', '\\/'), $fragment) . '\\z/';
+ return preg_match($reg, $value);
+ }
+ return ($fragment == '*' || !strcasecmp($fragment, $value));
+ }
+
+}
+/*
+ * Local Variables:
+ * indent-tabs-mode: nil
+ * c-basic-offset: 4
+ * End:
+ */ \ No newline at end of file
diff --git a/includes/pear/PEAR.php b/includes/pear/PEAR.php
new file mode 100644
index 0000000..48830fd
--- /dev/null
+++ b/includes/pear/PEAR.php
@@ -0,0 +1,1040 @@
+<?php
+/**
+ * PEAR, the PHP Extension and Application Repository
+ *
+ * PEAR class and PEAR_Error class
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Sterling Hughes <sterling@php.net>
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2010 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**#@+
+ * ERROR constants
+ */
+define('PEAR_ERROR_RETURN', 1);
+define('PEAR_ERROR_PRINT', 2);
+define('PEAR_ERROR_TRIGGER', 4);
+define('PEAR_ERROR_DIE', 8);
+define('PEAR_ERROR_CALLBACK', 16);
+/**
+ * WARNING: obsolete
+ * @deprecated
+ */
+define('PEAR_ERROR_EXCEPTION', 32);
+/**#@-*/
+define('PEAR_ZE2', (function_exists('version_compare') &&
+ version_compare(zend_version(), "2-dev", "ge")));
+
+if (substr(PHP_OS, 0, 3) == 'WIN') {
+ define('OS_WINDOWS', true);
+ define('OS_UNIX', false);
+ define('PEAR_OS', 'Windows');
+} else {
+ define('OS_WINDOWS', false);
+ define('OS_UNIX', true);
+ define('PEAR_OS', 'Unix'); // blatant assumption
+}
+
+$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN;
+$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE;
+$GLOBALS['_PEAR_destructor_object_list'] = array();
+$GLOBALS['_PEAR_shutdown_funcs'] = array();
+$GLOBALS['_PEAR_error_handler_stack'] = array();
+
+@ini_set('track_errors', true);
+
+/**
+ * Base class for other PEAR classes. Provides rudimentary
+ * emulation of destructors.
+ *
+ * If you want a destructor in your class, inherit PEAR and make a
+ * destructor method called _yourclassname (same name as the
+ * constructor, but with a "_" prefix). Also, in your constructor you
+ * have to call the PEAR constructor: $this->PEAR();.
+ * The destructor method will be called without parameters. Note that
+ * at in some SAPI implementations (such as Apache), any output during
+ * the request shutdown (in which destructors are called) seems to be
+ * discarded. If you need to get any debug information from your
+ * destructor, use error_log(), syslog() or something similar.
+ *
+ * IMPORTANT! To use the emulated destructors you need to create the
+ * objects by reference: $obj =& new PEAR_child;
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V. Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @see PEAR_Error
+ * @since Class available since PHP 4.0.2
+ * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear
+ */
+class PEAR
+{
+ /**
+ * Whether to enable internal debug messages.
+ *
+ * @var bool
+ */
+ private $_debug = false;
+
+ /**
+ * Default error mode for this object.
+ *
+ * @var int
+ */
+ private $_default_error_mode = null;
+
+ /**
+ * Default error options used for this object when error mode
+ * is PEAR_ERROR_TRIGGER.
+ *
+ * @var int
+ */
+ private $_default_error_options = null;
+
+ /**
+ * Default error handler (callback) for this object, if error mode is
+ * PEAR_ERROR_CALLBACK.
+ *
+ * @var string
+ */
+ private $_default_error_handler = '';
+
+ /**
+ * Which class to use for error objects.
+ *
+ * @var string
+ */
+ private $_error_class = 'PEAR_Error';
+
+ /**
+ * An array of expected errors.
+ *
+ * @var array
+ */
+ private $_expected_errors = array();
+
+ /**
+ * Constructor. Registers this object in
+ * $_PEAR_destructor_object_list for destructor emulation if a
+ * destructor object exists.
+ *
+ * @param string $error_class (optional) which class to use for
+ * error objects, defaults to PEAR_Error.
+ * @return void
+ */
+ public function PEAR($error_class = null)
+ {
+ $classname = strtolower(get_class($this));
+ if ($this->_debug) {
+ print "PEAR constructor called, class=$classname\n";
+ }
+
+ if ($error_class !== null) {
+ $this->_error_class = $error_class;
+ }
+
+ while ($classname && strcasecmp($classname, "pear")) {
+ $destructor = "_$classname";
+ if (method_exists($this, $destructor)) {
+ global $_PEAR_destructor_object_list;
+ $_PEAR_destructor_object_list[] = &$this;
+ if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
+ register_shutdown_function("_PEAR_call_destructors");
+ $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
+ }
+ break;
+ } else {
+ $classname = get_parent_class($classname);
+ }
+ }
+ }
+
+ /**
+ * Destructor (the emulated type of...). Does nothing right now,
+ * but is included for forward compatibility, so subclass
+ * destructors should always call it.
+ *
+ * See the note in the class desciption about output from
+ * destructors.
+ *
+ * @return void
+ */
+ public function _PEAR() {
+ if ($this->_debug) {
+ printf("PEAR destructor called, class=%s\n", strtolower(get_class($this)));
+ }
+ }
+
+ /**
+ * If you have a class that's mostly/entirely static, and you need static
+ * properties, you can use this method to simulate them. Eg. in your method(s)
+ * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar');
+ * You MUST use a reference, or they will not persist!
+ *
+ * @access public
+ * @param string $class The calling classname, to prevent clashes
+ * @param string $var The variable to retrieve.
+ * @return mixed A reference to the variable. If not set it will be
+ * auto initialised to NULL.
+ */
+ public static function &getStaticProperty($class, $var)
+ {
+ static $properties;
+ if (!isset($properties[$class])) {
+ $properties[$class] = array();
+ }
+
+ if (!array_key_exists($var, $properties[$class])) {
+ $properties[$class][$var] = null;
+ }
+
+ return $properties[$class][$var];
+ }
+
+ /**
+ * Use this function to register a shutdown method for static
+ * classes.
+ *
+ * @access public
+ * @param mixed $func The function name (or array of class/method) to call
+ * @param mixed $args The arguments to pass to the function
+ * @return void
+ */
+ public static function registerShutdownFunc($func, $args = array())
+ {
+ // if we are called statically, there is a potential
+ // that no shutdown func is registered. Bug #6445
+ if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
+ register_shutdown_function("_PEAR_call_destructors");
+ $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
+ }
+ $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args);
+ }
+
+ /**
+ * Tell whether a value is a PEAR error.
+ *
+ * @param mixed $data the value to test
+ * @param int $code if $data is an error object, return true
+ * only if $code is a string and
+ * $obj->getMessage() == $code or
+ * $code is an integer and $obj->getCode() == $code
+ * @return bool true if parameter is an error
+ */
+ public static function isError($data, $code = null)
+ {
+ if (!is_object($data)) {
+ return false;
+ }
+ if (!is_a($data, 'PEAR_Error')) {
+ return false;
+ }
+
+ if (is_null($code)) {
+ return true;
+ } elseif (is_string($code)) {
+ return $data->getMessage() == $code;
+ }
+
+ return $data->getCode() == $code;
+ }
+
+ /**
+ * Sets how errors generated by this object should be handled.
+ * Can be invoked both in objects and statically. If called
+ * statically, setErrorHandling sets the default behaviour for all
+ * PEAR objects. If called in an object, setErrorHandling sets
+ * the default behaviour for that object.
+ *
+ * @param int $mode
+ * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
+ * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
+ * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION.
+ *
+ * @param mixed $options
+ * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one
+ * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
+ *
+ * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected
+ * to be the callback function or method. A callback
+ * function is a string with the name of the function, a
+ * callback method is an array of two elements: the element
+ * at index 0 is the object, and the element at index 1 is
+ * the name of the method to call in the object.
+ *
+ * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is
+ * a printf format string used when printing the error
+ * message.
+ *
+ * @return void
+ * @see PEAR_ERROR_RETURN
+ * @see PEAR_ERROR_PRINT
+ * @see PEAR_ERROR_TRIGGER
+ * @see PEAR_ERROR_DIE
+ * @see PEAR_ERROR_CALLBACK
+ * @see PEAR_ERROR_EXCEPTION
+ *
+ * @since PHP 4.0.5
+ */
+ public function setErrorHandling($mode = null, $options = null)
+ {
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $setmode = &$this->_default_error_mode;
+ $setoptions = &$this->_default_error_options;
+ } else {
+ $setmode = &$GLOBALS['_PEAR_default_error_mode'];
+ $setoptions = &$GLOBALS['_PEAR_default_error_options'];
+ }
+
+ switch ($mode) {
+ case PEAR_ERROR_EXCEPTION:
+ case PEAR_ERROR_RETURN:
+ case PEAR_ERROR_PRINT:
+ case PEAR_ERROR_TRIGGER:
+ case PEAR_ERROR_DIE:
+ case null:
+ $setmode = $mode;
+ $setoptions = $options;
+ break;
+
+ case PEAR_ERROR_CALLBACK:
+ $setmode = $mode;
+ // class/object method callback
+ if (is_callable($options)) {
+ $setoptions = $options;
+ } else {
+ trigger_error("invalid error callback", E_USER_WARNING);
+ }
+ break;
+
+ default:
+ trigger_error("invalid error mode", E_USER_WARNING);
+ break;
+ }
+ }
+
+ /**
+ * This method is used to tell which errors you expect to get.
+ * Expected errors are always returned with error mode
+ * PEAR_ERROR_RETURN. Expected error codes are stored in a stack,
+ * and this method pushes a new element onto it. The list of
+ * expected errors are in effect until they are popped off the
+ * stack with the popExpect() method.
+ *
+ * Note that this method can not be called statically
+ *
+ * @param mixed $code a single error code or an array of error codes to expect
+ *
+ * @return int the new depth of the "expected errors" stack
+ */
+ public function expectError($code = '*')
+ {
+ if (is_array($code)) {
+ array_push($this->_expected_errors, $code);
+ } else {
+ array_push($this->_expected_errors, array($code));
+ }
+ return count($this->_expected_errors);
+ }
+
+ /**
+ * This method pops one element off the expected error codes
+ * stack.
+ *
+ * @return array the list of error codes that were popped
+ */
+ public function popExpect()
+ {
+ return array_pop($this->_expected_errors);
+ }
+
+ /**
+ * This method checks unsets an error code if available
+ *
+ * @param mixed error code
+ * @return bool true if the error code was unset, false otherwise
+ * @since PHP 4.3.0
+ */
+ private function _checkDelExpect($error_code)
+ {
+ $deleted = false;
+ foreach ($this->_expected_errors as $key => $error_array) {
+ if (in_array($error_code, $error_array)) {
+ unset($this->_expected_errors[$key][array_search($error_code, $error_array)]);
+ $deleted = true;
+ }
+
+ // clean up empty arrays
+ if (0 == count($this->_expected_errors[$key])) {
+ unset($this->_expected_errors[$key]);
+ }
+ }
+
+ return $deleted;
+ }
+
+ /**
+ * This method deletes all occurences of the specified element from
+ * the expected error codes stack.
+ *
+ * @param mixed $error_code error code that should be deleted
+ * @return mixed list of error codes that were deleted or error
+ * @since PHP 4.3.0
+ */
+ public function delExpect($error_code)
+ {
+ $deleted = false;
+ if ((is_array($error_code) && (0 != count($error_code)))) {
+ // $error_code is a non-empty array here; we walk through it trying
+ // to unset all values
+ foreach ($error_code as $key => $error) {
+ $deleted = $this->_checkDelExpect($error) ? true : false;
+ }
+
+ return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
+ } elseif (!empty($error_code)) {
+ // $error_code comes alone, trying to unset it
+ if ($this->_checkDelExpect($error_code)) {
+ return true;
+ }
+
+ return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
+ }
+
+ // $error_code is empty
+ return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME
+ }
+
+ /**
+ * This method is a wrapper that returns an instance of the
+ * configured error class with this object's default error
+ * handling applied. If the $mode and $options parameters are not
+ * specified, the object's defaults are used.
+ *
+ * @param mixed $message a text error message or a PEAR error object
+ *
+ * @param int $code a numeric error code (it is up to your class
+ * to define these if you want to use codes)
+ *
+ * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
+ * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
+ * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION.
+ *
+ * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter
+ * specifies the PHP-internal error level (one of
+ * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
+ * If $mode is PEAR_ERROR_CALLBACK, this
+ * parameter specifies the callback function or
+ * method. In other error modes this parameter
+ * is ignored.
+ *
+ * @param string $userinfo If you need to pass along for example debug
+ * information, this parameter is meant for that.
+ *
+ * @param string $error_class The returned error object will be
+ * instantiated from this class, if specified.
+ *
+ * @param bool $skipmsg If true, raiseError will only pass error codes,
+ * the error message parameter will be dropped.
+ *
+ * @return object a PEAR error object
+ * @see PEAR::setErrorHandling
+ * @since PHP 4.0.5
+ */
+ public function &raiseError($message = null,
+ $code = null,
+ $mode = null,
+ $options = null,
+ $userinfo = null,
+ $error_class = null,
+ $skipmsg = false)
+ {
+ // The error is yet a PEAR error object
+ if (is_object($message)) {
+ $code = $message->getCode();
+ $userinfo = $message->getUserInfo();
+ $error_class = $message->getType();
+ $message->error_message_prefix = '';
+ $message = $message->getMessage();
+ }
+
+ if (
+ isset($this) &&
+ isset($this->_expected_errors) &&
+ count($this->_expected_errors) > 0 &&
+ count($exp = end($this->_expected_errors))
+ ) {
+ if ($exp[0] == "*" ||
+ (is_int(reset($exp)) && in_array($code, $exp)) ||
+ (is_string(reset($exp)) && in_array($message, $exp))
+ ) {
+ $mode = PEAR_ERROR_RETURN;
+ }
+ }
+
+ // No mode given, try global ones
+ if ($mode === null) {
+ // Class error handler
+ if (isset($this) && isset($this->_default_error_mode)) {
+ $mode = $this->_default_error_mode;
+ $options = $this->_default_error_options;
+ // Global error handler
+ } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) {
+ $mode = $GLOBALS['_PEAR_default_error_mode'];
+ $options = $GLOBALS['_PEAR_default_error_options'];
+ }
+ }
+
+ if ($error_class !== null) {
+ $ec = $error_class;
+ } elseif (isset($this) && isset($this->_error_class)) {
+ $ec = $this->_error_class;
+ } else {
+ $ec = 'PEAR_Error';
+ }
+
+ if (intval(PHP_VERSION) < 5) {
+ // little non-eval hack to fix bug #12147
+ include 'PEAR/FixPHP5PEARWarnings.php';
+ return $a;
+ }
+
+ if ($skipmsg) {
+ $a = new $ec($code, $mode, $options, $userinfo);
+ } else {
+ $a = new $ec($message, $code, $mode, $options, $userinfo);
+ }
+
+ return $a;
+ }
+
+ /**
+ * Simpler form of raiseError with fewer options. In most cases
+ * message, code and userinfo are enough.
+ *
+ * @param mixed $message a text error message or a PEAR error object
+ *
+ * @param int $code a numeric error code (it is up to your class
+ * to define these if you want to use codes)
+ *
+ * @param string $userinfo If you need to pass along for example debug
+ * information, this parameter is meant for that.
+ *
+ * @return object a PEAR error object
+ * @see PEAR::raiseError
+ */
+ public function &throwError($message = null, $code = null, $userinfo = null)
+ {
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $a = &$this->raiseError($message, $code, null, null, $userinfo);
+ return $a;
+ }
+
+ $a = &PEAR::raiseError($message, $code, null, null, $userinfo);
+ return $a;
+ }
+
+ public static function staticPushErrorHandling($mode, $options = null)
+ {
+ $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+ $def_mode = &$GLOBALS['_PEAR_default_error_mode'];
+ $def_options = &$GLOBALS['_PEAR_default_error_options'];
+ $stack[] = array($def_mode, $def_options);
+ switch ($mode) {
+ case PEAR_ERROR_EXCEPTION:
+ case PEAR_ERROR_RETURN:
+ case PEAR_ERROR_PRINT:
+ case PEAR_ERROR_TRIGGER:
+ case PEAR_ERROR_DIE:
+ case null:
+ $def_mode = $mode;
+ $def_options = $options;
+ break;
+
+ case PEAR_ERROR_CALLBACK:
+ $def_mode = $mode;
+ // class/object method callback
+ if (is_callable($options)) {
+ $def_options = $options;
+ } else {
+ trigger_error("invalid error callback", E_USER_WARNING);
+ }
+ break;
+
+ default:
+ trigger_error("invalid error mode", E_USER_WARNING);
+ break;
+ }
+ $stack[] = array($mode, $options);
+ return true;
+ }
+
+ public static function staticPopErrorHandling()
+ {
+ $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+ $setmode = &$GLOBALS['_PEAR_default_error_mode'];
+ $setoptions = &$GLOBALS['_PEAR_default_error_options'];
+ array_pop($stack);
+ list($mode, $options) = $stack[sizeof($stack) - 1];
+ array_pop($stack);
+ switch ($mode) {
+ case PEAR_ERROR_EXCEPTION:
+ case PEAR_ERROR_RETURN:
+ case PEAR_ERROR_PRINT:
+ case PEAR_ERROR_TRIGGER:
+ case PEAR_ERROR_DIE:
+ case null:
+ $setmode = $mode;
+ $setoptions = $options;
+ break;
+
+ case PEAR_ERROR_CALLBACK:
+ $setmode = $mode;
+ // class/object method callback
+ if (is_callable($options)) {
+ $setoptions = $options;
+ } else {
+ trigger_error("invalid error callback", E_USER_WARNING);
+ }
+ break;
+
+ default:
+ trigger_error("invalid error mode", E_USER_WARNING);
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Push a new error handler on top of the error handler options stack. With this
+ * you can easily override the actual error handler for some code and restore
+ * it later with popErrorHandling.
+ *
+ * @param mixed $mode (same as setErrorHandling)
+ * @param mixed $options (same as setErrorHandling)
+ *
+ * @return bool Always true
+ *
+ * @see PEAR::setErrorHandling
+ */
+ public function pushErrorHandling($mode, $options = null)
+ {
+ $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $def_mode = &$this->_default_error_mode;
+ $def_options = &$this->_default_error_options;
+ } else {
+ $def_mode = &$GLOBALS['_PEAR_default_error_mode'];
+ $def_options = &$GLOBALS['_PEAR_default_error_options'];
+ }
+ $stack[] = array($def_mode, $def_options);
+
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $this->setErrorHandling($mode, $options);
+ } else {
+ PEAR::setErrorHandling($mode, $options);
+ }
+ $stack[] = array($mode, $options);
+ return true;
+ }
+
+ /**
+ * Pop the last error handler used
+ *
+ * @return bool Always true
+ *
+ * @see PEAR::pushErrorHandling
+ */
+ public function popErrorHandling()
+ {
+ $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+ array_pop($stack);
+ list($mode, $options) = $stack[sizeof($stack) - 1];
+ array_pop($stack);
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $this->setErrorHandling($mode, $options);
+ } else {
+ PEAR::setErrorHandling($mode, $options);
+ }
+ return true;
+ }
+
+ /**
+ * OS independant PHP extension load. Remember to take care
+ * on the correct extension name for case sensitive OSes.
+ *
+ * @param string $ext The extension name
+ * @return bool Success or not on the dl() call
+ */
+ public function loadExtension($ext)
+ {
+ if (extension_loaded($ext)) {
+ return true;
+ }
+
+ // if either returns true dl() will produce a FATAL error, stop that
+ if (
+ function_exists('dl') === false ||
+ ini_get('enable_dl') != 1 ||
+ ini_get('safe_mode') == 1
+ ) {
+ return false;
+ }
+
+ if (OS_WINDOWS) {
+ $suffix = '.dll';
+ } elseif (PHP_OS == 'HP-UX') {
+ $suffix = '.sl';
+ } elseif (PHP_OS == 'AIX') {
+ $suffix = '.a';
+ } elseif (PHP_OS == 'OSX') {
+ $suffix = '.bundle';
+ } else {
+ $suffix = '.so';
+ }
+
+ return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
+ }
+}
+
+if (PEAR_ZE2) {
+ include_once 'PEAR5.php';
+}
+
+function _PEAR_call_destructors()
+{
+ global $_PEAR_destructor_object_list;
+ if (is_array($_PEAR_destructor_object_list) &&
+ sizeof($_PEAR_destructor_object_list))
+ {
+ reset($_PEAR_destructor_object_list);
+ if (PEAR_ZE2) {
+ $destructLifoExists = PEAR5::getStaticProperty('PEAR', 'destructlifo');
+ } else {
+ $destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo');
+ }
+
+ if ($destructLifoExists) {
+ $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list);
+ }
+
+ while (list($k, $objref) = each($_PEAR_destructor_object_list)) {
+ $classname = get_class($objref);
+ while ($classname) {
+ $destructor = "_$classname";
+ if (method_exists($objref, $destructor)) {
+ $objref->$destructor();
+ break;
+ } else {
+ $classname = get_parent_class($classname);
+ }
+ }
+ }
+ // Empty the object list to ensure that destructors are
+ // not called more than once.
+ $_PEAR_destructor_object_list = array();
+ }
+
+ // Now call the shutdown functions
+ if (
+ isset($GLOBALS['_PEAR_shutdown_funcs']) &&
+ is_array($GLOBALS['_PEAR_shutdown_funcs']) &&
+ !empty($GLOBALS['_PEAR_shutdown_funcs'])
+ ) {
+ foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) {
+ call_user_func_array($value[0], $value[1]);
+ }
+ }
+}
+
+/**
+ * Standard PEAR error class for PHP 4
+ *
+ * This class is supserseded by {@link PEAR_Exception} in PHP 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V. Cox <cox@idecnet.com>
+ * @author Gregory Beaver <cellog@php.net>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/manual/en/core.pear.pear-error.php
+ * @see PEAR::raiseError(), PEAR::throwError()
+ * @since Class available since PHP 4.0.2
+ */
+class PEAR_Error
+{
+ var $error_message_prefix = '';
+ var $mode = PEAR_ERROR_RETURN;
+ var $level = E_USER_NOTICE;
+ var $code = -1;
+ var $message = '';
+ var $userinfo = '';
+ var $backtrace = null;
+
+ /**
+ * PEAR_Error constructor
+ *
+ * @param string $message message
+ *
+ * @param int $code (optional) error code
+ *
+ * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN,
+ * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER,
+ * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION
+ *
+ * @param mixed $options (optional) error level, _OR_ in the case of
+ * PEAR_ERROR_CALLBACK, the callback function or object/method
+ * tuple.
+ *
+ * @param string $userinfo (optional) additional user/debug info
+ *
+ */
+ public function PEAR_Error($message = 'unknown error', $code = null,
+ $mode = null, $options = null, $userinfo = null)
+ {
+ if ($mode === null) {
+ $mode = PEAR_ERROR_RETURN;
+ }
+ $this->message = $message;
+ $this->code = $code;
+ $this->mode = $mode;
+ $this->userinfo = $userinfo;
+
+ if (PEAR_ZE2) {
+ $skiptrace = PEAR5::getStaticProperty('PEAR_Error', 'skiptrace');
+ } else {
+ $skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace');
+ }
+
+ if (!$skiptrace) {
+ $this->backtrace = debug_backtrace();
+ if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) {
+ unset($this->backtrace[0]['object']);
+ }
+ }
+
+ if ($mode & PEAR_ERROR_CALLBACK) {
+ $this->level = E_USER_NOTICE;
+ $this->callback = $options;
+ } else {
+ if ($options === null) {
+ $options = E_USER_NOTICE;
+ }
+
+ $this->level = $options;
+ $this->callback = null;
+ }
+
+ if ($this->mode & PEAR_ERROR_PRINT) {
+ if (is_null($options) || is_int($options)) {
+ $format = "%s";
+ } else {
+ $format = $options;
+ }
+
+ printf($format, $this->getMessage());
+ }
+
+ if ($this->mode & PEAR_ERROR_TRIGGER) {
+ trigger_error($this->getMessage(), $this->level);
+ }
+
+ if ($this->mode & PEAR_ERROR_DIE) {
+ $msg = $this->getMessage();
+ if (is_null($options) || is_int($options)) {
+ $format = "%s";
+ if (substr($msg, -1) != "\n") {
+ $msg .= "\n";
+ }
+ } else {
+ $format = $options;
+ }
+ die(sprintf($format, $msg));
+ }
+
+ if ($this->mode & PEAR_ERROR_CALLBACK && is_callable($this->callback)) {
+ call_user_func($this->callback, $this);
+ }
+
+ if ($this->mode & PEAR_ERROR_EXCEPTION) {
+ trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING);
+ eval('$e = new Exception($this->message, $this->code);throw($e);');
+ }
+ }
+
+ /**
+ * Get the error mode from an error object.
+ *
+ * @return int error mode
+ */
+ public function getMode()
+ {
+ return $this->mode;
+ }
+
+ /**
+ * Get the callback function/method from an error object.
+ *
+ * @return mixed callback function or object/method array
+ */
+ public function getCallback()
+ {
+ return $this->callback;
+ }
+
+ /**
+ * Get the error message from an error object.
+ *
+ * @return string full error message
+ */
+ public function getMessage()
+ {
+ return ($this->error_message_prefix . $this->message);
+ }
+
+ /**
+ * Get error code from an error object
+ *
+ * @return int error code
+ */
+ public function getCode()
+ {
+ return $this->code;
+ }
+
+ /**
+ * Get the name of this error/exception.
+ *
+ * @return string error/exception name (type)
+ */
+ public function getType()
+ {
+ return get_class($this);
+ }
+
+ /**
+ * Get additional user-supplied information.
+ *
+ * @return string user-supplied information
+ */
+ public function getUserInfo()
+ {
+ return $this->userinfo;
+ }
+
+ /**
+ * Get additional debug information supplied by the application.
+ *
+ * @return string debug information
+ */
+ public function getDebugInfo()
+ {
+ return $this->getUserInfo();
+ }
+
+ /**
+ * Get the call backtrace from where the error was generated.
+ * Supported with PHP 4.3.0 or newer.
+ *
+ * @param int $frame (optional) what frame to fetch
+ * @return array Backtrace, or NULL if not available.
+ */
+ public function getBacktrace($frame = null)
+ {
+ if (defined('PEAR_IGNORE_BACKTRACE')) {
+ return null;
+ }
+ if ($frame === null) {
+ return $this->backtrace;
+ }
+ return $this->backtrace[$frame];
+ }
+
+ public function addUserInfo($info)
+ {
+ if (empty($this->userinfo)) {
+ $this->userinfo = $info;
+ } else {
+ $this->userinfo .= " ** $info";
+ }
+ }
+
+ public function __toString()
+ {
+ return $this->getMessage();
+ }
+
+ /**
+ * Make a string representation of this object.
+ *
+ * @return string a string with an object summary
+ */
+ public function toString()
+ {
+ $modes = array();
+ $levels = array(E_USER_NOTICE => 'notice',
+ E_USER_WARNING => 'warning',
+ E_USER_ERROR => 'error');
+ if ($this->mode & PEAR_ERROR_CALLBACK) {
+ if (is_array($this->callback)) {
+ $callback = (is_object($this->callback[0]) ?
+ strtolower(get_class($this->callback[0])) :
+ $this->callback[0]) . '::' .
+ $this->callback[1];
+ } else {
+ $callback = $this->callback;
+ }
+ return sprintf('[%s: message="%s" code=%d mode=callback '.
+ 'callback=%s prefix="%s" info="%s"]',
+ strtolower(get_class($this)), $this->message, $this->code,
+ $callback, $this->error_message_prefix,
+ $this->userinfo);
+ }
+ if ($this->mode & PEAR_ERROR_PRINT) {
+ $modes[] = 'print';
+ }
+ if ($this->mode & PEAR_ERROR_TRIGGER) {
+ $modes[] = 'trigger';
+ }
+ if ($this->mode & PEAR_ERROR_DIE) {
+ $modes[] = 'die';
+ }
+ if ($this->mode & PEAR_ERROR_RETURN) {
+ $modes[] = 'return';
+ }
+ return sprintf('[%s: message="%s" code=%d mode=%s level=%s '.
+ 'prefix="%s" info="%s"]',
+ strtolower(get_class($this)), $this->message, $this->code,
+ implode("|", $modes), $levels[$this->level],
+ $this->error_message_prefix,
+ $this->userinfo);
+ }
+}
+
+/*
+ * Local Variables:
+ * mode: php
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/includes/pear/PEAR/Autoloader.php b/includes/pear/PEAR/Autoloader.php
new file mode 100644
index 0000000..b833dc6
--- /dev/null
+++ b/includes/pear/PEAR/Autoloader.php
@@ -0,0 +1,218 @@
+<?php
+/**
+ * Class auto-loader
+ *
+ * PHP versions 4
+
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/manual/en/core.ppm.php#core.ppm.pear-autoloader
+ * @since File available since Release 0.1
+ * @deprecated File deprecated in Release 1.4.0a1
+ */
+
+// /* vim: set expandtab tabstop=4 shiftwidth=4: */
+
+if (!extension_loaded("overload")) {
+ // die hard without ext/overload
+ die("Rebuild PHP with the `overload' extension to use PEAR_Autoloader");
+}
+
+/**
+ * Include for PEAR_Error and PEAR classes
+ */
+require_once "PEAR.php";
+
+/**
+ * This class is for objects where you want to separate the code for
+ * some methods into separate classes. This is useful if you have a
+ * class with not-frequently-used methods that contain lots of code
+ * that you would like to avoid always parsing.
+ *
+ * The PEAR_Autoloader class provides autoloading and aggregation.
+ * The autoloading lets you set up in which classes the separated
+ * methods are found. Aggregation is the technique used to import new
+ * methods, an instance of each class providing separated methods is
+ * stored and called every time the aggregated method is called.
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/manual/en/core.ppm.php#core.ppm.pear-autoloader
+ * @since File available since Release 0.1
+ * @deprecated File deprecated in Release 1.4.0a1
+ */
+class PEAR_Autoloader extends PEAR
+{
+ // {{{ properties
+
+ /**
+ * Map of methods and classes where they are defined
+ *
+ * @var array
+ *
+ * @access private
+ */
+ var $_autoload_map = array();
+
+ /**
+ * Map of methods and aggregate objects
+ *
+ * @var array
+ *
+ * @access private
+ */
+ var $_method_map = array();
+
+ // }}}
+ // {{{ addAutoload()
+
+ /**
+ * Add one or more autoload entries.
+ *
+ * @param string $method which method to autoload
+ *
+ * @param string $classname (optional) which class to find the method in.
+ * If the $method parameter is an array, this
+ * parameter may be omitted (and will be ignored
+ * if not), and the $method parameter will be
+ * treated as an associative array with method
+ * names as keys and class names as values.
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function addAutoload($method, $classname = null)
+ {
+ if (is_array($method)) {
+ array_walk($method, create_function('$a,&$b', '$b = strtolower($b);'));
+ $this->_autoload_map = array_merge($this->_autoload_map, $method);
+ } else {
+ $this->_autoload_map[strtolower($method)] = $classname;
+ }
+ }
+
+ // }}}
+ // {{{ removeAutoload()
+
+ /**
+ * Remove an autoload entry.
+ *
+ * @param string $method which method to remove the autoload entry for
+ *
+ * @return bool TRUE if an entry was removed, FALSE if not
+ *
+ * @access public
+ */
+ function removeAutoload($method)
+ {
+ $method = strtolower($method);
+ $ok = isset($this->_autoload_map[$method]);
+ unset($this->_autoload_map[$method]);
+ return $ok;
+ }
+
+ // }}}
+ // {{{ addAggregateObject()
+
+ /**
+ * Add an aggregate object to this object. If the specified class
+ * is not defined, loading it will be attempted following PEAR's
+ * file naming scheme. All the methods in the class will be
+ * aggregated, except private ones (name starting with an
+ * underscore) and constructors.
+ *
+ * @param string $classname what class to instantiate for the object.
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function addAggregateObject($classname)
+ {
+ $classname = strtolower($classname);
+ if (!class_exists($classname)) {
+ $include_file = preg_replace('/[^a-z0-9]/i', '_', $classname);
+ include_once $include_file;
+ }
+ $obj =& new $classname;
+ $methods = get_class_methods($classname);
+ foreach ($methods as $method) {
+ // don't import priviate methods and constructors
+ if ($method{0} != '_' && $method != $classname) {
+ $this->_method_map[$method] = $obj;
+ }
+ }
+ }
+
+ // }}}
+ // {{{ removeAggregateObject()
+
+ /**
+ * Remove an aggregate object.
+ *
+ * @param string $classname the class of the object to remove
+ *
+ * @return bool TRUE if an object was removed, FALSE if not
+ *
+ * @access public
+ */
+ function removeAggregateObject($classname)
+ {
+ $ok = false;
+ $classname = strtolower($classname);
+ reset($this->_method_map);
+ while (list($method, $obj) = each($this->_method_map)) {
+ if (is_a($obj, $classname)) {
+ unset($this->_method_map[$method]);
+ $ok = true;
+ }
+ }
+ return $ok;
+ }
+
+ // }}}
+ // {{{ __call()
+
+ /**
+ * Overloaded object call handler, called each time an
+ * undefined/aggregated method is invoked. This method repeats
+ * the call in the right aggregate object and passes on the return
+ * value.
+ *
+ * @param string $method which method that was called
+ *
+ * @param string $args An array of the parameters passed in the
+ * original call
+ *
+ * @return mixed The return value from the aggregated method, or a PEAR
+ * error if the called method was unknown.
+ */
+ function __call($method, $args, &$retval)
+ {
+ $method = strtolower($method);
+ if (empty($this->_method_map[$method]) && isset($this->_autoload_map[$method])) {
+ $this->addAggregateObject($this->_autoload_map[$method]);
+ }
+ if (isset($this->_method_map[$method])) {
+ $retval = call_user_func_array(array($this->_method_map[$method], $method), $args);
+ return true;
+ }
+ return false;
+ }
+
+ // }}}
+}
+
+overload("PEAR_Autoloader");
+
+?>
diff --git a/includes/pear/PEAR/Builder.php b/includes/pear/PEAR/Builder.php
new file mode 100644
index 0000000..b4fb430
--- /dev/null
+++ b/includes/pear/PEAR/Builder.php
@@ -0,0 +1,518 @@
+<?php
+/**
+ * PEAR_Builder for building PHP extensions (PECL packages)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ *
+ * TODO: log output parameters in PECL command line
+ * TODO: msdev path in configuration
+ */
+
+/**
+ * Needed for extending PEAR_Builder
+ */
+require_once 'PEAR/Common.php';
+require_once 'PEAR/PackageFile.php';
+
+/**
+ * Class to handle building (compiling) extensions.
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @PEAR-VER@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since PHP 4.0.2
+ * @see http://pear.php.net/manual/en/core.ppm.pear-builder.php
+ */
+class PEAR_Builder extends PEAR_Common
+{
+ var $php_api_version = 0;
+ var $zend_module_api_no = 0;
+ var $zend_extension_api_no = 0;
+
+ var $extensions_built = array();
+
+ /**
+ * @var string Used for reporting when it is not possible to pass function
+ * via extra parameter, e.g. log, msdevCallback
+ */
+ var $current_callback = null;
+
+ // used for msdev builds
+ var $_lastline = null;
+ var $_firstline = null;
+
+ /**
+ * PEAR_Builder constructor.
+ *
+ * @param object $ui user interface object (instance of PEAR_Frontend_*)
+ *
+ * @access public
+ */
+ function PEAR_Builder(&$ui)
+ {
+ parent::PEAR_Common();
+ $this->setFrontendObject($ui);
+ }
+
+ /**
+ * Build an extension from source on windows.
+ * requires msdev
+ */
+ function _build_win32($descfile, $callback = null)
+ {
+ if (is_object($descfile)) {
+ $pkg = $descfile;
+ $descfile = $pkg->getPackageFile();
+ } else {
+ $pf = &new PEAR_PackageFile($this->config, $this->debug);
+ $pkg = &$pf->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL);
+ if (PEAR::isError($pkg)) {
+ return $pkg;
+ }
+ }
+ $dir = dirname($descfile);
+ $old_cwd = getcwd();
+
+ if (!file_exists($dir) || !is_dir($dir) || !chdir($dir)) {
+ return $this->raiseError("could not chdir to $dir");
+ }
+
+ // packages that were in a .tar have the packagefile in this directory
+ $vdir = $pkg->getPackage() . '-' . $pkg->getVersion();
+ if (file_exists($dir) && is_dir($vdir)) {
+ if (!chdir($vdir)) {
+ return $this->raiseError("could not chdir to " . realpath($vdir));
+ }
+
+ $dir = getcwd();
+ }
+
+ $this->log(2, "building in $dir");
+
+ $dsp = $pkg->getPackage().'.dsp';
+ if (!file_exists("$dir/$dsp")) {
+ return $this->raiseError("The DSP $dsp does not exist.");
+ }
+ // XXX TODO: make release build type configurable
+ $command = 'msdev '.$dsp.' /MAKE "'.$pkg->getPackage(). ' - Release"';
+
+ $err = $this->_runCommand($command, array(&$this, 'msdevCallback'));
+ if (PEAR::isError($err)) {
+ return $err;
+ }
+
+ // figure out the build platform and type
+ $platform = 'Win32';
+ $buildtype = 'Release';
+ if (preg_match('/.*?'.$pkg->getPackage().'\s-\s(\w+)\s(.*?)-+/i',$this->_firstline,$matches)) {
+ $platform = $matches[1];
+ $buildtype = $matches[2];
+ }
+
+ if (preg_match('/(.*)?\s-\s(\d+).*?(\d+)/', $this->_lastline, $matches)) {
+ if ($matches[2]) {
+ // there were errors in the build
+ return $this->raiseError("There were errors during compilation.");
+ }
+ $out = $matches[1];
+ } else {
+ return $this->raiseError("Did not understand the completion status returned from msdev.exe.");
+ }
+
+ // msdev doesn't tell us the output directory :/
+ // open the dsp, find /out and use that directory
+ $dsptext = join(file($dsp),'');
+
+ // this regex depends on the build platform and type having been
+ // correctly identified above.
+ $regex ='/.*?!IF\s+"\$\(CFG\)"\s+==\s+("'.
+ $pkg->getPackage().'\s-\s'.
+ $platform.'\s'.
+ $buildtype.'").*?'.
+ '\/out:"(.*?)"/is';
+
+ if ($dsptext && preg_match($regex, $dsptext, $matches)) {
+ // what we get back is a relative path to the output file itself.
+ $outfile = realpath($matches[2]);
+ } else {
+ return $this->raiseError("Could not retrieve output information from $dsp.");
+ }
+ // realpath returns false if the file doesn't exist
+ if ($outfile && copy($outfile, "$dir/$out")) {
+ $outfile = "$dir/$out";
+ }
+
+ $built_files[] = array(
+ 'file' => "$outfile",
+ 'php_api' => $this->php_api_version,
+ 'zend_mod_api' => $this->zend_module_api_no,
+ 'zend_ext_api' => $this->zend_extension_api_no,
+ );
+
+ return $built_files;
+ }
+ // }}}
+
+ // {{{ msdevCallback()
+ function msdevCallback($what, $data)
+ {
+ if (!$this->_firstline)
+ $this->_firstline = $data;
+ $this->_lastline = $data;
+ call_user_func($this->current_callback, $what, $data);
+ }
+
+ /**
+ * @param string
+ * @param string
+ * @param array
+ * @access private
+ */
+ function _harvestInstDir($dest_prefix, $dirname, &$built_files)
+ {
+ $d = opendir($dirname);
+ if (!$d)
+ return false;
+
+ $ret = true;
+ while (($ent = readdir($d)) !== false) {
+ if ($ent{0} == '.')
+ continue;
+
+ $full = $dirname . DIRECTORY_SEPARATOR . $ent;
+ if (is_dir($full)) {
+ if (!$this->_harvestInstDir(
+ $dest_prefix . DIRECTORY_SEPARATOR . $ent,
+ $full, $built_files)) {
+ $ret = false;
+ break;
+ }
+ } else {
+ $dest = $dest_prefix . DIRECTORY_SEPARATOR . $ent;
+ $built_files[] = array(
+ 'file' => $full,
+ 'dest' => $dest,
+ 'php_api' => $this->php_api_version,
+ 'zend_mod_api' => $this->zend_module_api_no,
+ 'zend_ext_api' => $this->zend_extension_api_no,
+ );
+ }
+ }
+ closedir($d);
+ return $ret;
+ }
+
+ /**
+ * Build an extension from source. Runs "phpize" in the source
+ * directory, but compiles in a temporary directory
+ * (TMPDIR/pear-build-USER/PACKAGE-VERSION).
+ *
+ * @param string|PEAR_PackageFile_v* $descfile path to XML package description file, or
+ * a PEAR_PackageFile object
+ *
+ * @param mixed $callback callback function used to report output,
+ * see PEAR_Builder::_runCommand for details
+ *
+ * @return array an array of associative arrays with built files,
+ * format:
+ * array( array( 'file' => '/path/to/ext.so',
+ * 'php_api' => YYYYMMDD,
+ * 'zend_mod_api' => YYYYMMDD,
+ * 'zend_ext_api' => YYYYMMDD ),
+ * ... )
+ *
+ * @access public
+ *
+ * @see PEAR_Builder::_runCommand
+ */
+ function build($descfile, $callback = null, $options = array())
+ {
+ if (preg_match('/(\\/|\\\\|^)([^\\/\\\\]+)?php(.+)?$/',
+ $this->config->get('php_bin'), $matches)) {
+ if (isset($matches[2]) && strlen($matches[2]) &&
+ trim($matches[2]) != trim($this->config->get('php_prefix'))) {
+ $this->log(0, 'WARNING: php_bin ' . $this->config->get('php_bin') .
+ ' appears to have a prefix ' . $matches[2] . ', but' .
+ ' config variable php_prefix does not match');
+ }
+
+ if (isset($matches[3]) && strlen($matches[3]) &&
+ trim($matches[3]) != trim($this->config->get('php_suffix'))) {
+ $this->log(0, 'WARNING: php_bin ' . $this->config->get('php_bin') .
+ ' appears to have a suffix ' . $matches[3] . ', but' .
+ ' config variable php_suffix does not match');
+ }
+ }
+
+ $this->current_callback = $callback;
+ if (PEAR_OS == "Windows") {
+ return $this->_build_win32($descfile, $callback);
+ }
+
+ if (PEAR_OS != 'Unix') {
+ return $this->raiseError("building extensions not supported on this platform");
+ }
+
+ if (is_object($descfile)) {
+ $pkg = $descfile;
+ $descfile = $pkg->getPackageFile();
+ if (is_a($pkg, 'PEAR_PackageFile_v1')) {
+ $dir = dirname($descfile);
+ } else {
+ $dir = $pkg->_config->get('temp_dir') . '/' . $pkg->getName();
+ // automatically delete at session end
+ $this->addTempFile($dir);
+ }
+ } else {
+ $pf = &new PEAR_PackageFile($this->config);
+ $pkg = &$pf->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL);
+ if (PEAR::isError($pkg)) {
+ return $pkg;
+ }
+ $dir = dirname($descfile);
+ }
+
+ // Find config. outside of normal path - e.g. config.m4
+ foreach (array_keys($pkg->getInstallationFileList()) as $item) {
+ if (stristr(basename($item), 'config.m4') && dirname($item) != '.') {
+ $dir .= DIRECTORY_SEPARATOR . dirname($item);
+ break;
+ }
+ }
+
+ $old_cwd = getcwd();
+ if (!file_exists($dir) || !is_dir($dir) || !chdir($dir)) {
+ return $this->raiseError("could not chdir to $dir");
+ }
+
+ $vdir = $pkg->getPackage() . '-' . $pkg->getVersion();
+ if (is_dir($vdir)) {
+ chdir($vdir);
+ }
+
+ $dir = getcwd();
+ $this->log(2, "building in $dir");
+ putenv('PATH=' . $this->config->get('bin_dir') . ':' . getenv('PATH'));
+ $err = $this->_runCommand($this->config->get('php_prefix')
+ . "phpize" .
+ $this->config->get('php_suffix'),
+ array(&$this, 'phpizeCallback'));
+ if (PEAR::isError($err)) {
+ return $err;
+ }
+
+ if (!$err) {
+ return $this->raiseError("`phpize' failed");
+ }
+
+ // Figure out what params have been passed in to us already - formatting fixing
+ $opts = array();
+ if (!empty($options)) {
+ foreach ($options as $op) {
+ $op = str_replace('--', '', $op);
+ list($name, $value) = explode('=', $op);
+ $opts[] = $name;
+ }
+ }
+
+ // {{{ start of interactive part
+ $configure_command = "$dir/configure";
+ $configure_options = $pkg->getConfigureOptions();
+ if ($configure_options) {
+ foreach ($configure_options as $o) {
+ // skip params that have been passed already
+ if (in_array($o['name'], $opts)) {
+ continue;
+ }
+
+ $default = array_key_exists('default', $o) ? $o['default'] : null;
+ list($r) = $this->ui->userDialog('build',
+ array($o['prompt']),
+ array('text'),
+ array($default));
+ if (substr($o['name'], 0, 5) == 'with-' &&
+ ($r == 'yes' || $r == 'autodetect')) {
+ $configure_command .= " --$o[name]";
+ } else {
+ $configure_command .= " --$o[name]=".trim($r);
+ }
+ }
+ }
+ // }}} end of interactive part
+
+ // Set any options that were passed in.
+ if (!empty($options)) {
+ foreach ($options as $op) {
+ $configure_command .= ' ' . $op;
+ }
+ }
+
+ // FIXME make configurable
+ if (!$user = getenv('USER')){
+ $user = 'defaultuser';
+ }
+
+ $tmpdir = $this->config->get('temp_dir');
+ $build_basedir = System::mktemp(' -t "' . $tmpdir . '" -d "pear-build-' . $user . '"');
+ $build_dir = "$build_basedir/$vdir";
+ $inst_dir = "$build_basedir/install-$vdir";
+ $this->log(1, "building in $build_dir");
+ if (is_dir($build_dir)) {
+ System::rm(array('-rf', $build_dir));
+ }
+
+ if (!System::mkDir(array('-p', $build_dir))) {
+ return $this->raiseError("could not create build dir: $build_dir");
+ }
+
+ $this->addTempFile($build_dir);
+ if (!System::mkDir(array('-p', $inst_dir))) {
+ return $this->raiseError("could not create temporary install dir: $inst_dir");
+ }
+ $this->addTempFile($inst_dir);
+
+ $make_command = getenv('MAKE') ? getenv('MAKE') : 'make';
+
+ $to_run = array(
+ $configure_command,
+ $make_command,
+ "$make_command INSTALL_ROOT=\"$inst_dir\" install",
+ "find \"$inst_dir\" | xargs ls -dils"
+ );
+ if (!file_exists($build_dir) || !is_dir($build_dir) || !chdir($build_dir)) {
+ return $this->raiseError("could not chdir to $build_dir");
+ }
+
+ putenv('PHP_PEAR_VERSION=1.9.4');
+ foreach ($to_run as $cmd) {
+ $err = $this->_runCommand($cmd, $callback);
+ if (PEAR::isError($err)) {
+ chdir($old_cwd);
+ return $err;
+ }
+ if (!$err) {
+ chdir($old_cwd);
+ return $this->raiseError("`$cmd' failed");
+ }
+ }
+ if (!($dp = opendir("modules"))) {
+ chdir($old_cwd);
+ return $this->raiseError("no `modules' directory found");
+ }
+
+ $built_files = array();
+ $prefix = exec($this->config->get('php_prefix')
+ . "php-config" .
+ $this->config->get('php_suffix') . " --prefix");
+ $ext_dir = $this->config->get('ext_dir');
+ if (!$ext_dir) {
+ $ext_dir = $prefix;
+ }
+ $this->_harvestInstDir($ext_dir, $inst_dir . DIRECTORY_SEPARATOR . $prefix, $built_files);
+
+ chdir($old_cwd);
+ return $built_files;
+ }
+
+ /**
+ * Message callback function used when running the "phpize"
+ * program. Extracts the API numbers used. Ignores other message
+ * types than "cmdoutput".
+ *
+ * @param string $what the type of message
+ * @param mixed $data the message
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function phpizeCallback($what, $data)
+ {
+ if ($what != 'cmdoutput') {
+ return;
+ }
+ $this->log(1, rtrim($data));
+ if (preg_match('/You should update your .aclocal.m4/', $data)) {
+ return;
+ }
+ $matches = array();
+ if (preg_match('/^\s+(\S[^:]+):\s+(\d{8})/', $data, $matches)) {
+ $member = preg_replace('/[^a-z]/', '_', strtolower($matches[1]));
+ $apino = (int)$matches[2];
+ if (isset($this->$member)) {
+ $this->$member = $apino;
+ //$msg = sprintf("%-22s : %d", $matches[1], $apino);
+ //$this->log(1, $msg);
+ }
+ }
+ }
+
+ /**
+ * Run an external command, using a message callback to report
+ * output. The command will be run through popen and output is
+ * reported for every line with a "cmdoutput" message with the
+ * line string, including newlines, as payload.
+ *
+ * @param string $command the command to run
+ *
+ * @param mixed $callback (optional) function to use as message
+ * callback
+ *
+ * @return bool whether the command was successful (exit code 0
+ * means success, any other means failure)
+ *
+ * @access private
+ */
+ function _runCommand($command, $callback = null)
+ {
+ $this->log(1, "running: $command");
+ $pp = popen("$command 2>&1", "r");
+ if (!$pp) {
+ return $this->raiseError("failed to run `$command'");
+ }
+ if ($callback && $callback[0]->debug == 1) {
+ $olddbg = $callback[0]->debug;
+ $callback[0]->debug = 2;
+ }
+
+ while ($line = fgets($pp, 1024)) {
+ if ($callback) {
+ call_user_func($callback, 'cmdoutput', $line);
+ } else {
+ $this->log(2, rtrim($line));
+ }
+ }
+ if ($callback && isset($olddbg)) {
+ $callback[0]->debug = $olddbg;
+ }
+
+ $exitcode = is_resource($pp) ? pclose($pp) : -1;
+ return ($exitcode == 0);
+ }
+
+ function log($level, $msg)
+ {
+ if ($this->current_callback) {
+ if ($this->debug >= $level) {
+ call_user_func($this->current_callback, 'output', $msg);
+ }
+ return;
+ }
+ return PEAR_Common::log($level, $msg);
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/ChannelFile.php b/includes/pear/PEAR/ChannelFile.php
new file mode 100644
index 0000000..9b27f28
--- /dev/null
+++ b/includes/pear/PEAR/ChannelFile.php
@@ -0,0 +1,1559 @@
+<?php
+/**
+ * PEAR_ChannelFile, the channel handling class
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * Needed for error handling
+ */
+require_once 'PEAR/ErrorStack.php';
+require_once 'PEAR/XMLParser.php';
+require_once 'PEAR/Common.php';
+
+/**
+ * Error code if the channel.xml <channel> tag does not contain a valid version
+ */
+define('PEAR_CHANNELFILE_ERROR_NO_VERSION', 1);
+/**
+ * Error code if the channel.xml <channel> tag version is not supported (version 1.0 is the only supported version,
+ * currently
+ */
+define('PEAR_CHANNELFILE_ERROR_INVALID_VERSION', 2);
+
+/**
+ * Error code if parsing is attempted with no xml extension
+ */
+define('PEAR_CHANNELFILE_ERROR_NO_XML_EXT', 3);
+
+/**
+ * Error code if creating the xml parser resource fails
+ */
+define('PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER', 4);
+
+/**
+ * Error code used for all sax xml parsing errors
+ */
+define('PEAR_CHANNELFILE_ERROR_PARSER_ERROR', 5);
+
+/**#@+
+ * Validation errors
+ */
+/**
+ * Error code when channel name is missing
+ */
+define('PEAR_CHANNELFILE_ERROR_NO_NAME', 6);
+/**
+ * Error code when channel name is invalid
+ */
+define('PEAR_CHANNELFILE_ERROR_INVALID_NAME', 7);
+/**
+ * Error code when channel summary is missing
+ */
+define('PEAR_CHANNELFILE_ERROR_NO_SUMMARY', 8);
+/**
+ * Error code when channel summary is multi-line
+ */
+define('PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY', 9);
+/**
+ * Error code when channel server is missing for protocol
+ */
+define('PEAR_CHANNELFILE_ERROR_NO_HOST', 10);
+/**
+ * Error code when channel server is invalid for protocol
+ */
+define('PEAR_CHANNELFILE_ERROR_INVALID_HOST', 11);
+/**
+ * Error code when a mirror name is invalid
+ */
+define('PEAR_CHANNELFILE_ERROR_INVALID_MIRROR', 21);
+/**
+ * Error code when a mirror type is invalid
+ */
+define('PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE', 22);
+/**
+ * Error code when an attempt is made to generate xml, but the parsed content is invalid
+ */
+define('PEAR_CHANNELFILE_ERROR_INVALID', 23);
+/**
+ * Error code when an empty package name validate regex is passed in
+ */
+define('PEAR_CHANNELFILE_ERROR_EMPTY_REGEX', 24);
+/**
+ * Error code when a <function> tag has no version
+ */
+define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION', 25);
+/**
+ * Error code when a <function> tag has no name
+ */
+define('PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME', 26);
+/**
+ * Error code when a <validatepackage> tag has no name
+ */
+define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME', 27);
+/**
+ * Error code when a <validatepackage> tag has no version attribute
+ */
+define('PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION', 28);
+/**
+ * Error code when a mirror does not exist but is called for in one of the set*
+ * methods.
+ */
+define('PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND', 32);
+/**
+ * Error code when a server port is not numeric
+ */
+define('PEAR_CHANNELFILE_ERROR_INVALID_PORT', 33);
+/**
+ * Error code when <static> contains no version attribute
+ */
+define('PEAR_CHANNELFILE_ERROR_NO_STATICVERSION', 34);
+/**
+ * Error code when <baseurl> contains no type attribute in a <rest> protocol definition
+ */
+define('PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE', 35);
+/**
+ * Error code when a mirror is defined and the channel.xml represents the __uri pseudo-channel
+ */
+define('PEAR_CHANNELFILE_URI_CANT_MIRROR', 36);
+/**
+ * Error code when ssl attribute is present and is not "yes"
+ */
+define('PEAR_CHANNELFILE_ERROR_INVALID_SSL', 37);
+/**#@-*/
+
+/**
+ * Mirror types allowed. Currently only internet servers are recognized.
+ */
+$GLOBALS['_PEAR_CHANNELS_MIRROR_TYPES'] = array('server');
+
+
+/**
+ * The Channel handling class
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_ChannelFile
+{
+ /**
+ * @access private
+ * @var PEAR_ErrorStack
+ * @access private
+ */
+ var $_stack;
+
+ /**
+ * Supported channel.xml versions, for parsing
+ * @var array
+ * @access private
+ */
+ var $_supportedVersions = array('1.0');
+
+ /**
+ * Parsed channel information
+ * @var array
+ * @access private
+ */
+ var $_channelInfo;
+
+ /**
+ * index into the subchannels array, used for parsing xml
+ * @var int
+ * @access private
+ */
+ var $_subchannelIndex;
+
+ /**
+ * index into the mirrors array, used for parsing xml
+ * @var int
+ * @access private
+ */
+ var $_mirrorIndex;
+
+ /**
+ * Flag used to determine the validity of parsed content
+ * @var boolean
+ * @access private
+ */
+ var $_isValid = false;
+
+ function PEAR_ChannelFile()
+ {
+ $this->_stack = &new PEAR_ErrorStack('PEAR_ChannelFile');
+ $this->_stack->setErrorMessageTemplate($this->_getErrorMessage());
+ $this->_isValid = false;
+ }
+
+ /**
+ * @return array
+ * @access protected
+ */
+ function _getErrorMessage()
+ {
+ return
+ array(
+ PEAR_CHANNELFILE_ERROR_INVALID_VERSION =>
+ 'While parsing channel.xml, an invalid version number "%version% was passed in, expecting one of %versions%',
+ PEAR_CHANNELFILE_ERROR_NO_VERSION =>
+ 'No version number found in <channel> tag',
+ PEAR_CHANNELFILE_ERROR_NO_XML_EXT =>
+ '%error%',
+ PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER =>
+ 'Unable to create XML parser',
+ PEAR_CHANNELFILE_ERROR_PARSER_ERROR =>
+ '%error%',
+ PEAR_CHANNELFILE_ERROR_NO_NAME =>
+ 'Missing channel name',
+ PEAR_CHANNELFILE_ERROR_INVALID_NAME =>
+ 'Invalid channel %tag% "%name%"',
+ PEAR_CHANNELFILE_ERROR_NO_SUMMARY =>
+ 'Missing channel summary',
+ PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY =>
+ 'Channel summary should be on one line, but is multi-line',
+ PEAR_CHANNELFILE_ERROR_NO_HOST =>
+ 'Missing channel server for %type% server',
+ PEAR_CHANNELFILE_ERROR_INVALID_HOST =>
+ 'Server name "%server%" is invalid for %type% server',
+ PEAR_CHANNELFILE_ERROR_INVALID_MIRROR =>
+ 'Invalid mirror name "%name%", mirror type %type%',
+ PEAR_CHANNELFILE_ERROR_INVALID_MIRRORTYPE =>
+ 'Invalid mirror type "%type%"',
+ PEAR_CHANNELFILE_ERROR_INVALID =>
+ 'Cannot generate xml, contents are invalid',
+ PEAR_CHANNELFILE_ERROR_EMPTY_REGEX =>
+ 'packagenameregex cannot be empty',
+ PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION =>
+ '%parent% %protocol% function has no version',
+ PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME =>
+ '%parent% %protocol% function has no name',
+ PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE =>
+ '%parent% rest baseurl has no type',
+ PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME =>
+ 'Validation package has no name in <validatepackage> tag',
+ PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION =>
+ 'Validation package "%package%" has no version',
+ PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND =>
+ 'Mirror "%mirror%" does not exist',
+ PEAR_CHANNELFILE_ERROR_INVALID_PORT =>
+ 'Port "%port%" must be numeric',
+ PEAR_CHANNELFILE_ERROR_NO_STATICVERSION =>
+ '<static> tag must contain version attribute',
+ PEAR_CHANNELFILE_URI_CANT_MIRROR =>
+ 'The __uri pseudo-channel cannot have mirrors',
+ PEAR_CHANNELFILE_ERROR_INVALID_SSL =>
+ '%server% has invalid ssl attribute "%ssl%" can only be yes or not present',
+ );
+ }
+
+ /**
+ * @param string contents of package.xml file
+ * @return bool success of parsing
+ */
+ function fromXmlString($data)
+ {
+ if (preg_match('/<channel\s+version="([0-9]+\.[0-9]+)"/', $data, $channelversion)) {
+ if (!in_array($channelversion[1], $this->_supportedVersions)) {
+ $this->_stack->push(PEAR_CHANNELFILE_ERROR_INVALID_VERSION, 'error',
+ array('version' => $channelversion[1]));
+ return false;
+ }
+ $parser = new PEAR_XMLParser;
+ $result = $parser->parse($data);
+ if ($result !== true) {
+ if ($result->getCode() == 1) {
+ $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_XML_EXT, 'error',
+ array('error' => $result->getMessage()));
+ } else {
+ $this->_stack->push(PEAR_CHANNELFILE_ERROR_CANT_MAKE_PARSER, 'error');
+ }
+ return false;
+ }
+ $this->_channelInfo = $parser->getData();
+ return true;
+ } else {
+ $this->_stack->push(PEAR_CHANNELFILE_ERROR_NO_VERSION, 'error', array('xml' => $data));
+ return false;
+ }
+ }
+
+ /**
+ * @return array
+ */
+ function toArray()
+ {
+ if (!$this->_isValid && !$this->validate()) {
+ return false;
+ }
+ return $this->_channelInfo;
+ }
+
+ /**
+ * @param array
+ * @static
+ * @return PEAR_ChannelFile|false false if invalid
+ */
+ function &fromArray($data, $compatibility = false, $stackClass = 'PEAR_ErrorStack')
+ {
+ $a = new PEAR_ChannelFile($compatibility, $stackClass);
+ $a->_fromArray($data);
+ if (!$a->validate()) {
+ $a = false;
+ return $a;
+ }
+ return $a;
+ }
+
+ /**
+ * Unlike {@link fromArray()} this does not do any validation
+ * @param array
+ * @static
+ * @return PEAR_ChannelFile
+ */
+ function &fromArrayWithErrors($data, $compatibility = false,
+ $stackClass = 'PEAR_ErrorStack')
+ {
+ $a = new PEAR_ChannelFile($compatibility, $stackClass);
+ $a->_fromArray($data);
+ return $a;
+ }
+
+ /**
+ * @param array
+ * @access private
+ */
+ function _fromArray($data)
+ {
+ $this->_channelInfo = $data;
+ }
+
+ /**
+ * Wrapper to {@link PEAR_ErrorStack::getErrors()}
+ * @param boolean determines whether to purge the error stack after retrieving
+ * @return array
+ */
+ function getErrors($purge = false)
+ {
+ return $this->_stack->getErrors($purge);
+ }
+
+ /**
+ * Unindent given string (?)
+ *
+ * @param string $str The string that has to be unindented.
+ * @return string
+ * @access private
+ */
+ function _unIndent($str)
+ {
+ // remove leading newlines
+ $str = preg_replace('/^[\r\n]+/', '', $str);
+ // find whitespace at the beginning of the first line
+ $indent_len = strspn($str, " \t");
+ $indent = substr($str, 0, $indent_len);
+ $data = '';
+ // remove the same amount of whitespace from following lines
+ foreach (explode("\n", $str) as $line) {
+ if (substr($line, 0, $indent_len) == $indent) {
+ $data .= substr($line, $indent_len) . "\n";
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * Parse a channel.xml file. Expects the name of
+ * a channel xml file as input.
+ *
+ * @param string $descfile name of channel xml file
+ * @return bool success of parsing
+ */
+ function fromXmlFile($descfile)
+ {
+ if (!file_exists($descfile) || !is_file($descfile) || !is_readable($descfile) ||
+ (!$fp = fopen($descfile, 'r'))) {
+ require_once 'PEAR.php';
+ return PEAR::raiseError("Unable to open $descfile");
+ }
+
+ // read the whole thing so we only get one cdata callback
+ // for each block of cdata
+ fclose($fp);
+ $data = file_get_contents($descfile);
+ return $this->fromXmlString($data);
+ }
+
+ /**
+ * Parse channel information from different sources
+ *
+ * This method is able to extract information about a channel
+ * from an .xml file or a string
+ *
+ * @access public
+ * @param string Filename of the source or the source itself
+ * @return bool
+ */
+ function fromAny($info)
+ {
+ if (is_string($info) && file_exists($info) && strlen($info) < 255) {
+ $tmp = substr($info, -4);
+ if ($tmp == '.xml') {
+ $info = $this->fromXmlFile($info);
+ } else {
+ $fp = fopen($info, "r");
+ $test = fread($fp, 5);
+ fclose($fp);
+ if ($test == "<?xml") {
+ $info = $this->fromXmlFile($info);
+ }
+ }
+ if (PEAR::isError($info)) {
+ require_once 'PEAR.php';
+ return PEAR::raiseError($info);
+ }
+ }
+ if (is_string($info)) {
+ $info = $this->fromXmlString($info);
+ }
+ return $info;
+ }
+
+ /**
+ * Return an XML document based on previous parsing and modifications
+ *
+ * @return string XML data
+ *
+ * @access public
+ */
+ function toXml()
+ {
+ if (!$this->_isValid && !$this->validate()) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID);
+ return false;
+ }
+ if (!isset($this->_channelInfo['attribs']['version'])) {
+ $this->_channelInfo['attribs']['version'] = '1.0';
+ }
+ $channelInfo = $this->_channelInfo;
+ $ret = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
+ $ret .= "<channel version=\"" .
+ $channelInfo['attribs']['version'] . "\" xmlns=\"http://pear.php.net/channel-1.0\"
+ xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
+ xsi:schemaLocation=\"http://pear.php.net/dtd/channel-"
+ . $channelInfo['attribs']['version'] . " http://pear.php.net/dtd/channel-" .
+ $channelInfo['attribs']['version'] . ".xsd\">
+ <name>$channelInfo[name]</name>
+ <summary>" . htmlspecialchars($channelInfo['summary'])."</summary>
+";
+ if (isset($channelInfo['suggestedalias'])) {
+ $ret .= ' <suggestedalias>' . $channelInfo['suggestedalias'] . "</suggestedalias>\n";
+ }
+ if (isset($channelInfo['validatepackage'])) {
+ $ret .= ' <validatepackage version="' .
+ $channelInfo['validatepackage']['attribs']['version']. '">' .
+ htmlspecialchars($channelInfo['validatepackage']['_content']) .
+ "</validatepackage>\n";
+ }
+ $ret .= " <servers>\n";
+ $ret .= ' <primary';
+ if (isset($channelInfo['servers']['primary']['attribs']['ssl'])) {
+ $ret .= ' ssl="' . $channelInfo['servers']['primary']['attribs']['ssl'] . '"';
+ }
+ if (isset($channelInfo['servers']['primary']['attribs']['port'])) {
+ $ret .= ' port="' . $channelInfo['servers']['primary']['attribs']['port'] . '"';
+ }
+ $ret .= ">\n";
+ if (isset($channelInfo['servers']['primary']['rest'])) {
+ $ret .= $this->_makeRestXml($channelInfo['servers']['primary']['rest'], ' ');
+ }
+ $ret .= " </primary>\n";
+ if (isset($channelInfo['servers']['mirror'])) {
+ $ret .= $this->_makeMirrorsXml($channelInfo);
+ }
+ $ret .= " </servers>\n";
+ $ret .= "</channel>";
+ return str_replace("\r", "\n", str_replace("\r\n", "\n", $ret));
+ }
+
+ /**
+ * Generate the <rest> tag
+ * @access private
+ */
+ function _makeRestXml($info, $indent)
+ {
+ $ret = $indent . "<rest>\n";
+ if (isset($info['baseurl']) && !isset($info['baseurl'][0])) {
+ $info['baseurl'] = array($info['baseurl']);
+ }
+
+ if (isset($info['baseurl'])) {
+ foreach ($info['baseurl'] as $url) {
+ $ret .= "$indent <baseurl type=\"" . $url['attribs']['type'] . "\"";
+ $ret .= ">" . $url['_content'] . "</baseurl>\n";
+ }
+ }
+ $ret .= $indent . "</rest>\n";
+ return $ret;
+ }
+
+ /**
+ * Generate the <mirrors> tag
+ * @access private
+ */
+ function _makeMirrorsXml($channelInfo)
+ {
+ $ret = "";
+ if (!isset($channelInfo['servers']['mirror'][0])) {
+ $channelInfo['servers']['mirror'] = array($channelInfo['servers']['mirror']);
+ }
+ foreach ($channelInfo['servers']['mirror'] as $mirror) {
+ $ret .= ' <mirror host="' . $mirror['attribs']['host'] . '"';
+ if (isset($mirror['attribs']['port'])) {
+ $ret .= ' port="' . $mirror['attribs']['port'] . '"';
+ }
+ if (isset($mirror['attribs']['ssl'])) {
+ $ret .= ' ssl="' . $mirror['attribs']['ssl'] . '"';
+ }
+ $ret .= ">\n";
+ if (isset($mirror['rest'])) {
+ if (isset($mirror['rest'])) {
+ $ret .= $this->_makeRestXml($mirror['rest'], ' ');
+ }
+ $ret .= " </mirror>\n";
+ } else {
+ $ret .= "/>\n";
+ }
+ }
+ return $ret;
+ }
+
+ /**
+ * Generate the <functions> tag
+ * @access private
+ */
+ function _makeFunctionsXml($functions, $indent, $rest = false)
+ {
+ $ret = '';
+ if (!isset($functions[0])) {
+ $functions = array($functions);
+ }
+ foreach ($functions as $function) {
+ $ret .= "$indent<function version=\"" . $function['attribs']['version'] . "\"";
+ if ($rest) {
+ $ret .= ' uri="' . $function['attribs']['uri'] . '"';
+ }
+ $ret .= ">" . $function['_content'] . "</function>\n";
+ }
+ return $ret;
+ }
+
+ /**
+ * Validation error. Also marks the object contents as invalid
+ * @param error code
+ * @param array error information
+ * @access private
+ */
+ function _validateError($code, $params = array())
+ {
+ $this->_stack->push($code, 'error', $params);
+ $this->_isValid = false;
+ }
+
+ /**
+ * Validation warning. Does not mark the object contents invalid.
+ * @param error code
+ * @param array error information
+ * @access private
+ */
+ function _validateWarning($code, $params = array())
+ {
+ $this->_stack->push($code, 'warning', $params);
+ }
+
+ /**
+ * Validate parsed file.
+ *
+ * @access public
+ * @return boolean
+ */
+ function validate()
+ {
+ $this->_isValid = true;
+ $info = $this->_channelInfo;
+ if (empty($info['name'])) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_NAME);
+ } elseif (!$this->validChannelServer($info['name'])) {
+ if ($info['name'] != '__uri') {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME, array('tag' => 'name',
+ 'name' => $info['name']));
+ }
+ }
+ if (empty($info['summary'])) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY);
+ } elseif (strpos(trim($info['summary']), "\n") !== false) {
+ $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY,
+ array('summary' => $info['summary']));
+ }
+ if (isset($info['suggestedalias'])) {
+ if (!$this->validChannelServer($info['suggestedalias'])) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME,
+ array('tag' => 'suggestedalias', 'name' =>$info['suggestedalias']));
+ }
+ }
+ if (isset($info['localalias'])) {
+ if (!$this->validChannelServer($info['localalias'])) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME,
+ array('tag' => 'localalias', 'name' =>$info['localalias']));
+ }
+ }
+ if (isset($info['validatepackage'])) {
+ if (!isset($info['validatepackage']['_content'])) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_NAME);
+ }
+ if (!isset($info['validatepackage']['attribs']['version'])) {
+ $content = isset($info['validatepackage']['_content']) ?
+ $info['validatepackage']['_content'] :
+ null;
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_NOVALIDATE_VERSION,
+ array('package' => $content));
+ }
+ }
+
+ if (isset($info['servers']['primary']['attribs'], $info['servers']['primary']['attribs']['port']) &&
+ !is_numeric($info['servers']['primary']['attribs']['port'])) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_PORT,
+ array('port' => $info['servers']['primary']['attribs']['port']));
+ }
+
+ if (isset($info['servers']['primary']['attribs'], $info['servers']['primary']['attribs']['ssl']) &&
+ $info['servers']['primary']['attribs']['ssl'] != 'yes') {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_SSL,
+ array('ssl' => $info['servers']['primary']['attribs']['ssl'],
+ 'server' => $info['name']));
+ }
+
+ if (isset($info['servers']['primary']['rest']) &&
+ isset($info['servers']['primary']['rest']['baseurl'])) {
+ $this->_validateFunctions('rest', $info['servers']['primary']['rest']['baseurl']);
+ }
+ if (isset($info['servers']['mirror'])) {
+ if ($this->_channelInfo['name'] == '__uri') {
+ $this->_validateError(PEAR_CHANNELFILE_URI_CANT_MIRROR);
+ }
+ if (!isset($info['servers']['mirror'][0])) {
+ $info['servers']['mirror'] = array($info['servers']['mirror']);
+ }
+ foreach ($info['servers']['mirror'] as $mirror) {
+ if (!isset($mirror['attribs']['host'])) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_HOST,
+ array('type' => 'mirror'));
+ } elseif (!$this->validChannelServer($mirror['attribs']['host'])) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_HOST,
+ array('server' => $mirror['attribs']['host'], 'type' => 'mirror'));
+ }
+ if (isset($mirror['attribs']['ssl']) && $mirror['attribs']['ssl'] != 'yes') {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_SSL,
+ array('ssl' => $info['ssl'], 'server' => $mirror['attribs']['host']));
+ }
+ if (isset($mirror['rest'])) {
+ $this->_validateFunctions('rest', $mirror['rest']['baseurl'],
+ $mirror['attribs']['host']);
+ }
+ }
+ }
+ return $this->_isValid;
+ }
+
+ /**
+ * @param string rest - protocol name this function applies to
+ * @param array the functions
+ * @param string the name of the parent element (mirror name, for instance)
+ */
+ function _validateFunctions($protocol, $functions, $parent = '')
+ {
+ if (!isset($functions[0])) {
+ $functions = array($functions);
+ }
+
+ foreach ($functions as $function) {
+ if (!isset($function['_content']) || empty($function['_content'])) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_FUNCTIONNAME,
+ array('parent' => $parent, 'protocol' => $protocol));
+ }
+
+ if ($protocol == 'rest') {
+ if (!isset($function['attribs']['type']) ||
+ empty($function['attribs']['type'])) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_NOBASEURLTYPE,
+ array('parent' => $parent, 'protocol' => $protocol));
+ }
+ } else {
+ if (!isset($function['attribs']['version']) ||
+ empty($function['attribs']['version'])) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_FUNCTIONVERSION,
+ array('parent' => $parent, 'protocol' => $protocol));
+ }
+ }
+ }
+ }
+
+ /**
+ * Test whether a string contains a valid channel server.
+ * @param string $ver the package version to test
+ * @return bool
+ */
+ function validChannelServer($server)
+ {
+ if ($server == '__uri') {
+ return true;
+ }
+ return (bool) preg_match(PEAR_CHANNELS_SERVER_PREG, $server);
+ }
+
+ /**
+ * @return string|false
+ */
+ function getName()
+ {
+ if (isset($this->_channelInfo['name'])) {
+ return $this->_channelInfo['name'];
+ }
+
+ return false;
+ }
+
+ /**
+ * @return string|false
+ */
+ function getServer()
+ {
+ if (isset($this->_channelInfo['name'])) {
+ return $this->_channelInfo['name'];
+ }
+
+ return false;
+ }
+
+ /**
+ * @return int|80 port number to connect to
+ */
+ function getPort($mirror = false)
+ {
+ if ($mirror) {
+ if ($mir = $this->getMirror($mirror)) {
+ if (isset($mir['attribs']['port'])) {
+ return $mir['attribs']['port'];
+ }
+
+ if ($this->getSSL($mirror)) {
+ return 443;
+ }
+
+ return 80;
+ }
+
+ return false;
+ }
+
+ if (isset($this->_channelInfo['servers']['primary']['attribs']['port'])) {
+ return $this->_channelInfo['servers']['primary']['attribs']['port'];
+ }
+
+ if ($this->getSSL()) {
+ return 443;
+ }
+
+ return 80;
+ }
+
+ /**
+ * @return bool Determines whether secure sockets layer (SSL) is used to connect to this channel
+ */
+ function getSSL($mirror = false)
+ {
+ if ($mirror) {
+ if ($mir = $this->getMirror($mirror)) {
+ if (isset($mir['attribs']['ssl'])) {
+ return true;
+ }
+
+ return false;
+ }
+
+ return false;
+ }
+
+ if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return string|false
+ */
+ function getSummary()
+ {
+ if (isset($this->_channelInfo['summary'])) {
+ return $this->_channelInfo['summary'];
+ }
+
+ return false;
+ }
+
+ /**
+ * @param string protocol type
+ * @param string Mirror name
+ * @return array|false
+ */
+ function getFunctions($protocol, $mirror = false)
+ {
+ if ($this->getName() == '__uri') {
+ return false;
+ }
+
+ $function = $protocol == 'rest' ? 'baseurl' : 'function';
+ if ($mirror) {
+ if ($mir = $this->getMirror($mirror)) {
+ if (isset($mir[$protocol][$function])) {
+ return $mir[$protocol][$function];
+ }
+ }
+
+ return false;
+ }
+
+ if (isset($this->_channelInfo['servers']['primary'][$protocol][$function])) {
+ return $this->_channelInfo['servers']['primary'][$protocol][$function];
+ }
+
+ return false;
+ }
+
+ /**
+ * @param string Protocol type
+ * @param string Function name (null to return the
+ * first protocol of the type requested)
+ * @param string Mirror name, if any
+ * @return array
+ */
+ function getFunction($type, $name = null, $mirror = false)
+ {
+ $protocols = $this->getFunctions($type, $mirror);
+ if (!$protocols) {
+ return false;
+ }
+
+ foreach ($protocols as $protocol) {
+ if ($name === null) {
+ return $protocol;
+ }
+
+ if ($protocol['_content'] != $name) {
+ continue;
+ }
+
+ return $protocol;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param string protocol type
+ * @param string protocol name
+ * @param string version
+ * @param string mirror name
+ * @return boolean
+ */
+ function supports($type, $name = null, $mirror = false, $version = '1.0')
+ {
+ $protocols = $this->getFunctions($type, $mirror);
+ if (!$protocols) {
+ return false;
+ }
+
+ foreach ($protocols as $protocol) {
+ if ($protocol['attribs']['version'] != $version) {
+ continue;
+ }
+
+ if ($name === null) {
+ return true;
+ }
+
+ if ($protocol['_content'] != $name) {
+ continue;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines whether a channel supports Representational State Transfer (REST) protocols
+ * for retrieving channel information
+ * @param string
+ * @return bool
+ */
+ function supportsREST($mirror = false)
+ {
+ if ($mirror == $this->_channelInfo['name']) {
+ $mirror = false;
+ }
+
+ if ($mirror) {
+ if ($mir = $this->getMirror($mirror)) {
+ return isset($mir['rest']);
+ }
+
+ return false;
+ }
+
+ return isset($this->_channelInfo['servers']['primary']['rest']);
+ }
+
+ /**
+ * Get the URL to access a base resource.
+ *
+ * Hyperlinks in the returned xml will be used to retrieve the proper information
+ * needed. This allows extreme extensibility and flexibility in implementation
+ * @param string Resource Type to retrieve
+ */
+ function getBaseURL($resourceType, $mirror = false)
+ {
+ if ($mirror == $this->_channelInfo['name']) {
+ $mirror = false;
+ }
+
+ if ($mirror) {
+ $mir = $this->getMirror($mirror);
+ if (!$mir) {
+ return false;
+ }
+
+ $rest = $mir['rest'];
+ } else {
+ $rest = $this->_channelInfo['servers']['primary']['rest'];
+ }
+
+ if (!isset($rest['baseurl'][0])) {
+ $rest['baseurl'] = array($rest['baseurl']);
+ }
+
+ foreach ($rest['baseurl'] as $baseurl) {
+ if (strtolower($baseurl['attribs']['type']) == strtolower($resourceType)) {
+ return $baseurl['_content'];
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Since REST does not implement RPC, provide this as a logical wrapper around
+ * resetFunctions for REST
+ * @param string|false mirror name, if any
+ */
+ function resetREST($mirror = false)
+ {
+ return $this->resetFunctions('rest', $mirror);
+ }
+
+ /**
+ * Empty all protocol definitions
+ * @param string protocol type
+ * @param string|false mirror name, if any
+ */
+ function resetFunctions($type, $mirror = false)
+ {
+ if ($mirror) {
+ if (isset($this->_channelInfo['servers']['mirror'])) {
+ $mirrors = $this->_channelInfo['servers']['mirror'];
+ if (!isset($mirrors[0])) {
+ $mirrors = array($mirrors);
+ }
+
+ foreach ($mirrors as $i => $mir) {
+ if ($mir['attribs']['host'] == $mirror) {
+ if (isset($this->_channelInfo['servers']['mirror'][$i][$type])) {
+ unset($this->_channelInfo['servers']['mirror'][$i][$type]);
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return false;
+ }
+
+ if (isset($this->_channelInfo['servers']['primary'][$type])) {
+ unset($this->_channelInfo['servers']['primary'][$type]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Set a channel's protocols to the protocols supported by pearweb
+ */
+ function setDefaultPEARProtocols($version = '1.0', $mirror = false)
+ {
+ switch ($version) {
+ case '1.0' :
+ $this->resetREST($mirror);
+
+ if (!isset($this->_channelInfo['servers'])) {
+ $this->_channelInfo['servers'] = array('primary' =>
+ array('rest' => array()));
+ } elseif (!isset($this->_channelInfo['servers']['primary'])) {
+ $this->_channelInfo['servers']['primary'] = array('rest' => array());
+ }
+
+ return true;
+ break;
+ default :
+ return false;
+ break;
+ }
+ }
+
+ /**
+ * @return array
+ */
+ function getMirrors()
+ {
+ if (isset($this->_channelInfo['servers']['mirror'])) {
+ $mirrors = $this->_channelInfo['servers']['mirror'];
+ if (!isset($mirrors[0])) {
+ $mirrors = array($mirrors);
+ }
+
+ return $mirrors;
+ }
+
+ return array();
+ }
+
+ /**
+ * Get the unserialized XML representing a mirror
+ * @return array|false
+ */
+ function getMirror($server)
+ {
+ foreach ($this->getMirrors() as $mirror) {
+ if ($mirror['attribs']['host'] == $server) {
+ return $mirror;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param string
+ * @return string|false
+ * @error PEAR_CHANNELFILE_ERROR_NO_NAME
+ * @error PEAR_CHANNELFILE_ERROR_INVALID_NAME
+ */
+ function setName($name)
+ {
+ return $this->setServer($name);
+ }
+
+ /**
+ * Set the socket number (port) that is used to connect to this channel
+ * @param integer
+ * @param string|false name of the mirror server, or false for the primary
+ */
+ function setPort($port, $mirror = false)
+ {
+ if ($mirror) {
+ if (!isset($this->_channelInfo['servers']['mirror'])) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
+ array('mirror' => $mirror));
+ return false;
+ }
+
+ if (isset($this->_channelInfo['servers']['mirror'][0])) {
+ foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
+ if ($mirror == $mir['attribs']['host']) {
+ $this->_channelInfo['servers']['mirror'][$i]['attribs']['port'] = $port;
+ return true;
+ }
+ }
+
+ return false;
+ } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
+ $this->_channelInfo['servers']['mirror']['attribs']['port'] = $port;
+ $this->_isValid = false;
+ return true;
+ }
+ }
+
+ $this->_channelInfo['servers']['primary']['attribs']['port'] = $port;
+ $this->_isValid = false;
+ return true;
+ }
+
+ /**
+ * Set the socket number (port) that is used to connect to this channel
+ * @param bool Determines whether to turn on SSL support or turn it off
+ * @param string|false name of the mirror server, or false for the primary
+ */
+ function setSSL($ssl = true, $mirror = false)
+ {
+ if ($mirror) {
+ if (!isset($this->_channelInfo['servers']['mirror'])) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
+ array('mirror' => $mirror));
+ return false;
+ }
+
+ if (isset($this->_channelInfo['servers']['mirror'][0])) {
+ foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
+ if ($mirror == $mir['attribs']['host']) {
+ if (!$ssl) {
+ if (isset($this->_channelInfo['servers']['mirror'][$i]
+ ['attribs']['ssl'])) {
+ unset($this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl']);
+ }
+ } else {
+ $this->_channelInfo['servers']['mirror'][$i]['attribs']['ssl'] = 'yes';
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ } elseif ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
+ if (!$ssl) {
+ if (isset($this->_channelInfo['servers']['mirror']['attribs']['ssl'])) {
+ unset($this->_channelInfo['servers']['mirror']['attribs']['ssl']);
+ }
+ } else {
+ $this->_channelInfo['servers']['mirror']['attribs']['ssl'] = 'yes';
+ }
+
+ $this->_isValid = false;
+ return true;
+ }
+ }
+
+ if ($ssl) {
+ $this->_channelInfo['servers']['primary']['attribs']['ssl'] = 'yes';
+ } else {
+ if (isset($this->_channelInfo['servers']['primary']['attribs']['ssl'])) {
+ unset($this->_channelInfo['servers']['primary']['attribs']['ssl']);
+ }
+ }
+
+ $this->_isValid = false;
+ return true;
+ }
+
+ /**
+ * @param string
+ * @return string|false
+ * @error PEAR_CHANNELFILE_ERROR_NO_SERVER
+ * @error PEAR_CHANNELFILE_ERROR_INVALID_SERVER
+ */
+ function setServer($server, $mirror = false)
+ {
+ if (empty($server)) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SERVER);
+ return false;
+ } elseif (!$this->validChannelServer($server)) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME,
+ array('tag' => 'name', 'name' => $server));
+ return false;
+ }
+
+ if ($mirror) {
+ $found = false;
+ foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
+ if ($mirror == $mir['attribs']['host']) {
+ $found = true;
+ break;
+ }
+ }
+
+ if (!$found) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
+ array('mirror' => $mirror));
+ return false;
+ }
+
+ $this->_channelInfo['mirror'][$i]['attribs']['host'] = $server;
+ return true;
+ }
+
+ $this->_channelInfo['name'] = $server;
+ return true;
+ }
+
+ /**
+ * @param string
+ * @return boolean success
+ * @error PEAR_CHANNELFILE_ERROR_NO_SUMMARY
+ * @warning PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY
+ */
+ function setSummary($summary)
+ {
+ if (empty($summary)) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_NO_SUMMARY);
+ return false;
+ } elseif (strpos(trim($summary), "\n") !== false) {
+ $this->_validateWarning(PEAR_CHANNELFILE_ERROR_MULTILINE_SUMMARY,
+ array('summary' => $summary));
+ }
+
+ $this->_channelInfo['summary'] = $summary;
+ return true;
+ }
+
+ /**
+ * @param string
+ * @param boolean determines whether the alias is in channel.xml or local
+ * @return boolean success
+ */
+ function setAlias($alias, $local = false)
+ {
+ if (!$this->validChannelServer($alias)) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_INVALID_NAME,
+ array('tag' => 'suggestedalias', 'name' => $alias));
+ return false;
+ }
+
+ if ($local) {
+ $this->_channelInfo['localalias'] = $alias;
+ } else {
+ $this->_channelInfo['suggestedalias'] = $alias;
+ }
+
+ return true;
+ }
+
+ /**
+ * @return string
+ */
+ function getAlias()
+ {
+ if (isset($this->_channelInfo['localalias'])) {
+ return $this->_channelInfo['localalias'];
+ }
+ if (isset($this->_channelInfo['suggestedalias'])) {
+ return $this->_channelInfo['suggestedalias'];
+ }
+ if (isset($this->_channelInfo['name'])) {
+ return $this->_channelInfo['name'];
+ }
+ return '';
+ }
+
+ /**
+ * Set the package validation object if it differs from PEAR's default
+ * The class must be includeable via changing _ in the classname to path separator,
+ * but no checking of this is made.
+ * @param string|false pass in false to reset to the default packagename regex
+ * @return boolean success
+ */
+ function setValidationPackage($validateclass, $version)
+ {
+ if (empty($validateclass)) {
+ unset($this->_channelInfo['validatepackage']);
+ }
+ $this->_channelInfo['validatepackage'] = array('_content' => $validateclass);
+ $this->_channelInfo['validatepackage']['attribs'] = array('version' => $version);
+ }
+
+ /**
+ * Add a protocol to the provides section
+ * @param string protocol type
+ * @param string protocol version
+ * @param string protocol name, if any
+ * @param string mirror name, if this is a mirror's protocol
+ * @return bool
+ */
+ function addFunction($type, $version, $name = '', $mirror = false)
+ {
+ if ($mirror) {
+ return $this->addMirrorFunction($mirror, $type, $version, $name);
+ }
+
+ $set = array('attribs' => array('version' => $version), '_content' => $name);
+ if (!isset($this->_channelInfo['servers']['primary'][$type]['function'])) {
+ if (!isset($this->_channelInfo['servers'])) {
+ $this->_channelInfo['servers'] = array('primary' =>
+ array($type => array()));
+ } elseif (!isset($this->_channelInfo['servers']['primary'])) {
+ $this->_channelInfo['servers']['primary'] = array($type => array());
+ }
+
+ $this->_channelInfo['servers']['primary'][$type]['function'] = $set;
+ $this->_isValid = false;
+ return true;
+ } elseif (!isset($this->_channelInfo['servers']['primary'][$type]['function'][0])) {
+ $this->_channelInfo['servers']['primary'][$type]['function'] = array(
+ $this->_channelInfo['servers']['primary'][$type]['function']);
+ }
+
+ $this->_channelInfo['servers']['primary'][$type]['function'][] = $set;
+ return true;
+ }
+ /**
+ * Add a protocol to a mirror's provides section
+ * @param string mirror name (server)
+ * @param string protocol type
+ * @param string protocol version
+ * @param string protocol name, if any
+ */
+ function addMirrorFunction($mirror, $type, $version, $name = '')
+ {
+ if (!isset($this->_channelInfo['servers']['mirror'])) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
+ array('mirror' => $mirror));
+ return false;
+ }
+
+ $setmirror = false;
+ if (isset($this->_channelInfo['servers']['mirror'][0])) {
+ foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
+ if ($mirror == $mir['attribs']['host']) {
+ $setmirror = &$this->_channelInfo['servers']['mirror'][$i];
+ break;
+ }
+ }
+ } else {
+ if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
+ $setmirror = &$this->_channelInfo['servers']['mirror'];
+ }
+ }
+
+ if (!$setmirror) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
+ array('mirror' => $mirror));
+ return false;
+ }
+
+ $set = array('attribs' => array('version' => $version), '_content' => $name);
+ if (!isset($setmirror[$type]['function'])) {
+ $setmirror[$type]['function'] = $set;
+ $this->_isValid = false;
+ return true;
+ } elseif (!isset($setmirror[$type]['function'][0])) {
+ $setmirror[$type]['function'] = array($setmirror[$type]['function']);
+ }
+
+ $setmirror[$type]['function'][] = $set;
+ $this->_isValid = false;
+ return true;
+ }
+
+ /**
+ * @param string Resource Type this url links to
+ * @param string URL
+ * @param string|false mirror name, if this is not a primary server REST base URL
+ */
+ function setBaseURL($resourceType, $url, $mirror = false)
+ {
+ if ($mirror) {
+ if (!isset($this->_channelInfo['servers']['mirror'])) {
+ $this->_validateError(PEAR_CHANNELFILE_ERROR_MIRROR_NOT_FOUND,
+ array('mirror' => $mirror));
+ return false;
+ }
+
+ $setmirror = false;
+ if (isset($this->_channelInfo['servers']['mirror'][0])) {
+ foreach ($this->_channelInfo['servers']['mirror'] as $i => $mir) {
+ if ($mirror == $mir['attribs']['host']) {
+ $setmirror = &$this->_channelInfo['servers']['mirror'][$i];
+ break;
+ }
+ }
+ } else {
+ if ($this->_channelInfo['servers']['mirror']['attribs']['host'] == $mirror) {
+ $setmirror = &$this->_channelInfo['servers']['mirror'];
+ }
+ }
+ } else {
+ $setmirror = &$this->_channelInfo['servers']['primary'];
+ }
+
+ $set = array('attribs' => array('type' => $resourceType), '_content' => $url);
+ if (!isset($setmirror['rest'])) {
+ $setmirror['rest'] = array();
+ }
+
+ if (!isset($setmirror['rest']['baseurl'])) {
+ $setmirror['rest']['baseurl'] = $set;
+ $this->_isValid = false;
+ return true;
+ } elseif (!isset($setmirror['rest']['baseurl'][0])) {
+ $setmirror['rest']['baseurl'] = array($setmirror['rest']['baseurl']);
+ }
+
+ foreach ($setmirror['rest']['baseurl'] as $i => $url) {
+ if ($url['attribs']['type'] == $resourceType) {
+ $this->_isValid = false;
+ $setmirror['rest']['baseurl'][$i] = $set;
+ return true;
+ }
+ }
+
+ $setmirror['rest']['baseurl'][] = $set;
+ $this->_isValid = false;
+ return true;
+ }
+
+ /**
+ * @param string mirror server
+ * @param int mirror http port
+ * @return boolean
+ */
+ function addMirror($server, $port = null)
+ {
+ if ($this->_channelInfo['name'] == '__uri') {
+ return false; // the __uri channel cannot have mirrors by definition
+ }
+
+ $set = array('attribs' => array('host' => $server));
+ if (is_numeric($port)) {
+ $set['attribs']['port'] = $port;
+ }
+
+ if (!isset($this->_channelInfo['servers']['mirror'])) {
+ $this->_channelInfo['servers']['mirror'] = $set;
+ return true;
+ }
+
+ if (!isset($this->_channelInfo['servers']['mirror'][0])) {
+ $this->_channelInfo['servers']['mirror'] =
+ array($this->_channelInfo['servers']['mirror']);
+ }
+
+ $this->_channelInfo['servers']['mirror'][] = $set;
+ return true;
+ }
+
+ /**
+ * Retrieve the name of the validation package for this channel
+ * @return string|false
+ */
+ function getValidationPackage()
+ {
+ if (!$this->_isValid && !$this->validate()) {
+ return false;
+ }
+
+ if (!isset($this->_channelInfo['validatepackage'])) {
+ return array('attribs' => array('version' => 'default'),
+ '_content' => 'PEAR_Validate');
+ }
+
+ return $this->_channelInfo['validatepackage'];
+ }
+
+ /**
+ * Retrieve the object that can be used for custom validation
+ * @param string|false the name of the package to validate. If the package is
+ * the channel validation package, PEAR_Validate is returned
+ * @return PEAR_Validate|false false is returned if the validation package
+ * cannot be located
+ */
+ function &getValidationObject($package = false)
+ {
+ if (!class_exists('PEAR_Validate')) {
+ require_once 'PEAR/Validate.php';
+ }
+
+ if (!$this->_isValid) {
+ if (!$this->validate()) {
+ $a = false;
+ return $a;
+ }
+ }
+
+ if (isset($this->_channelInfo['validatepackage'])) {
+ if ($package == $this->_channelInfo['validatepackage']) {
+ // channel validation packages are always validated by PEAR_Validate
+ $val = &new PEAR_Validate;
+ return $val;
+ }
+
+ if (!class_exists(str_replace('.', '_',
+ $this->_channelInfo['validatepackage']['_content']))) {
+ if ($this->isIncludeable(str_replace('_', '/',
+ $this->_channelInfo['validatepackage']['_content']) . '.php')) {
+ include_once str_replace('_', '/',
+ $this->_channelInfo['validatepackage']['_content']) . '.php';
+ $vclass = str_replace('.', '_',
+ $this->_channelInfo['validatepackage']['_content']);
+ $val = &new $vclass;
+ } else {
+ $a = false;
+ return $a;
+ }
+ } else {
+ $vclass = str_replace('.', '_',
+ $this->_channelInfo['validatepackage']['_content']);
+ $val = &new $vclass;
+ }
+ } else {
+ $val = &new PEAR_Validate;
+ }
+
+ return $val;
+ }
+
+ function isIncludeable($path)
+ {
+ $possibilities = explode(PATH_SEPARATOR, ini_get('include_path'));
+ foreach ($possibilities as $dir) {
+ if (file_exists($dir . DIRECTORY_SEPARATOR . $path)
+ && is_readable($dir . DIRECTORY_SEPARATOR . $path)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * This function is used by the channel updater and retrieves a value set by
+ * the registry, or the current time if it has not been set
+ * @return string
+ */
+ function lastModified()
+ {
+ if (isset($this->_channelInfo['_lastmodified'])) {
+ return $this->_channelInfo['_lastmodified'];
+ }
+
+ return time();
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/ChannelFile/Parser.php b/includes/pear/PEAR/ChannelFile/Parser.php
new file mode 100644
index 0000000..5416c3d
--- /dev/null
+++ b/includes/pear/PEAR/ChannelFile/Parser.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * PEAR_ChannelFile_Parser for parsing channel.xml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * base xml parser class
+ */
+require_once 'PEAR/XMLParser.php';
+require_once 'PEAR/ChannelFile.php';
+/**
+ * Parser for channel.xml
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_ChannelFile_Parser extends PEAR_XMLParser
+{
+ var $_config;
+ var $_logger;
+ var $_registry;
+
+ function setConfig(&$c)
+ {
+ $this->_config = &$c;
+ $this->_registry = &$c->getRegistry();
+ }
+
+ function setLogger(&$l)
+ {
+ $this->_logger = &$l;
+ }
+
+ function parse($data, $file)
+ {
+ if (PEAR::isError($err = parent::parse($data, $file))) {
+ return $err;
+ }
+
+ $ret = new PEAR_ChannelFile;
+ $ret->setConfig($this->_config);
+ if (isset($this->_logger)) {
+ $ret->setLogger($this->_logger);
+ }
+
+ $ret->fromArray($this->_unserializedData);
+ // make sure the filelist is in the easy to read format needed
+ $ret->flattenFilelist();
+ $ret->setPackagefile($file, $archive);
+ return $ret;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Command.php b/includes/pear/PEAR/Command.php
new file mode 100644
index 0000000..5dd3cc5
--- /dev/null
+++ b/includes/pear/PEAR/Command.php
@@ -0,0 +1,414 @@
+<?php
+/**
+ * PEAR_Command, command pattern class
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**
+ * Needed for error handling
+ */
+require_once 'PEAR.php';
+require_once 'PEAR/Frontend.php';
+require_once 'PEAR/XMLParser.php';
+
+/**
+ * List of commands and what classes they are implemented in.
+ * @var array command => implementing class
+ */
+$GLOBALS['_PEAR_Command_commandlist'] = array();
+
+/**
+ * List of commands and their descriptions
+ * @var array command => description
+ */
+$GLOBALS['_PEAR_Command_commanddesc'] = array();
+
+/**
+ * List of shortcuts to common commands.
+ * @var array shortcut => command
+ */
+$GLOBALS['_PEAR_Command_shortcuts'] = array();
+
+/**
+ * Array of command objects
+ * @var array class => object
+ */
+$GLOBALS['_PEAR_Command_objects'] = array();
+
+/**
+ * PEAR command class, a simple factory class for administrative
+ * commands.
+ *
+ * How to implement command classes:
+ *
+ * - The class must be called PEAR_Command_Nnn, installed in the
+ * "PEAR/Common" subdir, with a method called getCommands() that
+ * returns an array of the commands implemented by the class (see
+ * PEAR/Command/Install.php for an example).
+ *
+ * - The class must implement a run() function that is called with three
+ * params:
+ *
+ * (string) command name
+ * (array) assoc array with options, freely defined by each
+ * command, for example:
+ * array('force' => true)
+ * (array) list of the other parameters
+ *
+ * The run() function returns a PEAR_CommandResponse object. Use
+ * these methods to get information:
+ *
+ * int getStatus() Returns PEAR_COMMAND_(SUCCESS|FAILURE|PARTIAL)
+ * *_PARTIAL means that you need to issue at least
+ * one more command to complete the operation
+ * (used for example for validation steps).
+ *
+ * string getMessage() Returns a message for the user. Remember,
+ * no HTML or other interface-specific markup.
+ *
+ * If something unexpected happens, run() returns a PEAR error.
+ *
+ * - DON'T OUTPUT ANYTHING! Return text for output instead.
+ *
+ * - DON'T USE HTML! The text you return will be used from both Gtk,
+ * web and command-line interfaces, so for now, keep everything to
+ * plain text.
+ *
+ * - DON'T USE EXIT OR DIE! Always use pear errors. From static
+ * classes do PEAR::raiseError(), from other classes do
+ * $this->raiseError().
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ */
+class PEAR_Command
+{
+ // {{{ factory()
+
+ /**
+ * Get the right object for executing a command.
+ *
+ * @param string $command The name of the command
+ * @param object $config Instance of PEAR_Config object
+ *
+ * @return object the command object or a PEAR error
+ *
+ * @access public
+ * @static
+ */
+ function &factory($command, &$config)
+ {
+ if (empty($GLOBALS['_PEAR_Command_commandlist'])) {
+ PEAR_Command::registerCommands();
+ }
+ if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) {
+ $command = $GLOBALS['_PEAR_Command_shortcuts'][$command];
+ }
+ if (!isset($GLOBALS['_PEAR_Command_commandlist'][$command])) {
+ $a = PEAR::raiseError("unknown command `$command'");
+ return $a;
+ }
+ $class = $GLOBALS['_PEAR_Command_commandlist'][$command];
+ if (!class_exists($class)) {
+ require_once $GLOBALS['_PEAR_Command_objects'][$class];
+ }
+ if (!class_exists($class)) {
+ $a = PEAR::raiseError("unknown command `$command'");
+ return $a;
+ }
+ $ui =& PEAR_Command::getFrontendObject();
+ $obj = &new $class($ui, $config);
+ return $obj;
+ }
+
+ // }}}
+ // {{{ & getObject()
+ function &getObject($command)
+ {
+ $class = $GLOBALS['_PEAR_Command_commandlist'][$command];
+ if (!class_exists($class)) {
+ require_once $GLOBALS['_PEAR_Command_objects'][$class];
+ }
+ if (!class_exists($class)) {
+ return PEAR::raiseError("unknown command `$command'");
+ }
+ $ui =& PEAR_Command::getFrontendObject();
+ $config = &PEAR_Config::singleton();
+ $obj = &new $class($ui, $config);
+ return $obj;
+ }
+
+ // }}}
+ // {{{ & getFrontendObject()
+
+ /**
+ * Get instance of frontend object.
+ *
+ * @return object|PEAR_Error
+ * @static
+ */
+ function &getFrontendObject()
+ {
+ $a = &PEAR_Frontend::singleton();
+ return $a;
+ }
+
+ // }}}
+ // {{{ & setFrontendClass()
+
+ /**
+ * Load current frontend class.
+ *
+ * @param string $uiclass Name of class implementing the frontend
+ *
+ * @return object the frontend object, or a PEAR error
+ * @static
+ */
+ function &setFrontendClass($uiclass)
+ {
+ $a = &PEAR_Frontend::setFrontendClass($uiclass);
+ return $a;
+ }
+
+ // }}}
+ // {{{ setFrontendType()
+
+ /**
+ * Set current frontend.
+ *
+ * @param string $uitype Name of the frontend type (for example "CLI")
+ *
+ * @return object the frontend object, or a PEAR error
+ * @static
+ */
+ function setFrontendType($uitype)
+ {
+ $uiclass = 'PEAR_Frontend_' . $uitype;
+ return PEAR_Command::setFrontendClass($uiclass);
+ }
+
+ // }}}
+ // {{{ registerCommands()
+
+ /**
+ * Scan through the Command directory looking for classes
+ * and see what commands they implement.
+ *
+ * @param bool (optional) if FALSE (default), the new list of
+ * commands should replace the current one. If TRUE,
+ * new entries will be merged with old.
+ *
+ * @param string (optional) where (what directory) to look for
+ * classes, defaults to the Command subdirectory of
+ * the directory from where this file (__FILE__) is
+ * included.
+ *
+ * @return bool TRUE on success, a PEAR error on failure
+ *
+ * @access public
+ * @static
+ */
+ function registerCommands($merge = false, $dir = null)
+ {
+ $parser = new PEAR_XMLParser;
+ if ($dir === null) {
+ $dir = dirname(__FILE__) . '/Command';
+ }
+ if (!is_dir($dir)) {
+ return PEAR::raiseError("registerCommands: opendir($dir) '$dir' does not exist or is not a directory");
+ }
+ $dp = @opendir($dir);
+ if (empty($dp)) {
+ return PEAR::raiseError("registerCommands: opendir($dir) failed");
+ }
+ if (!$merge) {
+ $GLOBALS['_PEAR_Command_commandlist'] = array();
+ }
+
+ while ($file = readdir($dp)) {
+ if ($file{0} == '.' || substr($file, -4) != '.xml') {
+ continue;
+ }
+
+ $f = substr($file, 0, -4);
+ $class = "PEAR_Command_" . $f;
+ // List of commands
+ if (empty($GLOBALS['_PEAR_Command_objects'][$class])) {
+ $GLOBALS['_PEAR_Command_objects'][$class] = "$dir/" . $f . '.php';
+ }
+
+ $parser->parse(file_get_contents("$dir/$file"));
+ $implements = $parser->getData();
+ foreach ($implements as $command => $desc) {
+ if ($command == 'attribs') {
+ continue;
+ }
+
+ if (isset($GLOBALS['_PEAR_Command_commandlist'][$command])) {
+ return PEAR::raiseError('Command "' . $command . '" already registered in ' .
+ 'class "' . $GLOBALS['_PEAR_Command_commandlist'][$command] . '"');
+ }
+
+ $GLOBALS['_PEAR_Command_commandlist'][$command] = $class;
+ $GLOBALS['_PEAR_Command_commanddesc'][$command] = $desc['summary'];
+ if (isset($desc['shortcut'])) {
+ $shortcut = $desc['shortcut'];
+ if (isset($GLOBALS['_PEAR_Command_shortcuts'][$shortcut])) {
+ return PEAR::raiseError('Command shortcut "' . $shortcut . '" already ' .
+ 'registered to command "' . $command . '" in class "' .
+ $GLOBALS['_PEAR_Command_commandlist'][$command] . '"');
+ }
+ $GLOBALS['_PEAR_Command_shortcuts'][$shortcut] = $command;
+ }
+
+ if (isset($desc['options']) && $desc['options']) {
+ foreach ($desc['options'] as $oname => $option) {
+ if (isset($option['shortopt']) && strlen($option['shortopt']) > 1) {
+ return PEAR::raiseError('Option "' . $oname . '" short option "' .
+ $option['shortopt'] . '" must be ' .
+ 'only 1 character in Command "' . $command . '" in class "' .
+ $class . '"');
+ }
+ }
+ }
+ }
+ }
+
+ ksort($GLOBALS['_PEAR_Command_shortcuts']);
+ ksort($GLOBALS['_PEAR_Command_commandlist']);
+ @closedir($dp);
+ return true;
+ }
+
+ // }}}
+ // {{{ getCommands()
+
+ /**
+ * Get the list of currently supported commands, and what
+ * classes implement them.
+ *
+ * @return array command => implementing class
+ *
+ * @access public
+ * @static
+ */
+ function getCommands()
+ {
+ if (empty($GLOBALS['_PEAR_Command_commandlist'])) {
+ PEAR_Command::registerCommands();
+ }
+ return $GLOBALS['_PEAR_Command_commandlist'];
+ }
+
+ // }}}
+ // {{{ getShortcuts()
+
+ /**
+ * Get the list of command shortcuts.
+ *
+ * @return array shortcut => command
+ *
+ * @access public
+ * @static
+ */
+ function getShortcuts()
+ {
+ if (empty($GLOBALS['_PEAR_Command_shortcuts'])) {
+ PEAR_Command::registerCommands();
+ }
+ return $GLOBALS['_PEAR_Command_shortcuts'];
+ }
+
+ // }}}
+ // {{{ getGetoptArgs()
+
+ /**
+ * Compiles arguments for getopt.
+ *
+ * @param string $command command to get optstring for
+ * @param string $short_args (reference) short getopt format
+ * @param array $long_args (reference) long getopt format
+ *
+ * @return void
+ *
+ * @access public
+ * @static
+ */
+ function getGetoptArgs($command, &$short_args, &$long_args)
+ {
+ if (empty($GLOBALS['_PEAR_Command_commandlist'])) {
+ PEAR_Command::registerCommands();
+ }
+ if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) {
+ $command = $GLOBALS['_PEAR_Command_shortcuts'][$command];
+ }
+ if (!isset($GLOBALS['_PEAR_Command_commandlist'][$command])) {
+ return null;
+ }
+ $obj = &PEAR_Command::getObject($command);
+ return $obj->getGetoptArgs($command, $short_args, $long_args);
+ }
+
+ // }}}
+ // {{{ getDescription()
+
+ /**
+ * Get description for a command.
+ *
+ * @param string $command Name of the command
+ *
+ * @return string command description
+ *
+ * @access public
+ * @static
+ */
+ function getDescription($command)
+ {
+ if (!isset($GLOBALS['_PEAR_Command_commanddesc'][$command])) {
+ return null;
+ }
+ return $GLOBALS['_PEAR_Command_commanddesc'][$command];
+ }
+
+ // }}}
+ // {{{ getHelp()
+
+ /**
+ * Get help for command.
+ *
+ * @param string $command Name of the command to return help for
+ *
+ * @access public
+ * @static
+ */
+ function getHelp($command)
+ {
+ $cmds = PEAR_Command::getCommands();
+ if (isset($GLOBALS['_PEAR_Command_shortcuts'][$command])) {
+ $command = $GLOBALS['_PEAR_Command_shortcuts'][$command];
+ }
+ if (isset($cmds[$command])) {
+ $obj = &PEAR_Command::getObject($command);
+ return $obj->getHelp($command);
+ }
+ return false;
+ }
+ // }}}
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Auth.php b/includes/pear/PEAR/Command/Auth.php
new file mode 100644
index 0000000..5376bc3
--- /dev/null
+++ b/includes/pear/PEAR/Command/Auth.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * PEAR_Command_Auth (login, logout commands)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ * @deprecated since 1.8.0alpha1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Channels.php';
+
+/**
+ * PEAR commands for login/logout
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ * @deprecated since 1.8.0alpha1
+ */
+class PEAR_Command_Auth extends PEAR_Command_Channels
+{
+ var $commands = array(
+ 'login' => array(
+ 'summary' => 'Connects and authenticates to remote server [Deprecated in favor of channel-login]',
+ 'shortcut' => 'li',
+ 'function' => 'doLogin',
+ 'options' => array(),
+ 'doc' => '<channel name>
+WARNING: This function is deprecated in favor of using channel-login
+
+Log in to a remote channel server. If <channel name> is not supplied,
+the default channel is used. To use remote functions in the installer
+that require any kind of privileges, you need to log in first. The
+username and password you enter here will be stored in your per-user
+PEAR configuration (~/.pearrc on Unix-like systems). After logging
+in, your username and password will be sent along in subsequent
+operations on the remote server.',
+ ),
+ 'logout' => array(
+ 'summary' => 'Logs out from the remote server [Deprecated in favor of channel-logout]',
+ 'shortcut' => 'lo',
+ 'function' => 'doLogout',
+ 'options' => array(),
+ 'doc' => '
+WARNING: This function is deprecated in favor of using channel-logout
+
+Logs out from the remote server. This command does not actually
+connect to the remote server, it only deletes the stored username and
+password from your user configuration.',
+ )
+
+ );
+
+ /**
+ * PEAR_Command_Auth constructor.
+ *
+ * @access public
+ */
+ function PEAR_Command_Auth(&$ui, &$config)
+ {
+ parent::PEAR_Command_Channels($ui, $config);
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Auth.xml b/includes/pear/PEAR/Command/Auth.xml
new file mode 100644
index 0000000..590193d
--- /dev/null
+++ b/includes/pear/PEAR/Command/Auth.xml
@@ -0,0 +1,30 @@
+<commands version="1.0">
+ <login>
+ <summary>Connects and authenticates to remote server [Deprecated in favor of channel-login]</summary>
+ <function>doLogin</function>
+ <shortcut>li</shortcut>
+ <options />
+ <doc>&lt;channel name&gt;
+WARNING: This function is deprecated in favor of using channel-login
+
+Log in to a remote channel server. If &lt;channel name&gt; is not supplied,
+the default channel is used. To use remote functions in the installer
+that require any kind of privileges, you need to log in first. The
+username and password you enter here will be stored in your per-user
+PEAR configuration (~/.pearrc on Unix-like systems). After logging
+in, your username and password will be sent along in subsequent
+operations on the remote server.</doc>
+ </login>
+ <logout>
+ <summary>Logs out from the remote server [Deprecated in favor of channel-logout]</summary>
+ <function>doLogout</function>
+ <shortcut>lo</shortcut>
+ <options />
+ <doc>
+WARNING: This function is deprecated in favor of using channel-logout
+
+Logs out from the remote server. This command does not actually
+connect to the remote server, it only deletes the stored username and
+password from your user configuration.</doc>
+ </logout>
+</commands> \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Build.php b/includes/pear/PEAR/Command/Build.php
new file mode 100644
index 0000000..67a61d3
--- /dev/null
+++ b/includes/pear/PEAR/Command/Build.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * PEAR_Command_Auth (build command)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+/**
+ * PEAR commands for building extensions.
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ */
+class PEAR_Command_Build extends PEAR_Command_Common
+{
+ var $commands = array(
+ 'build' => array(
+ 'summary' => 'Build an Extension From C Source',
+ 'function' => 'doBuild',
+ 'shortcut' => 'b',
+ 'options' => array(),
+ 'doc' => '[package.xml]
+Builds one or more extensions contained in a package.'
+ ),
+ );
+
+ /**
+ * PEAR_Command_Build constructor.
+ *
+ * @access public
+ */
+ function PEAR_Command_Build(&$ui, &$config)
+ {
+ parent::PEAR_Command_Common($ui, $config);
+ }
+
+ function doBuild($command, $options, $params)
+ {
+ require_once 'PEAR/Builder.php';
+ if (sizeof($params) < 1) {
+ $params[0] = 'package.xml';
+ }
+
+ $builder = &new PEAR_Builder($this->ui);
+ $this->debug = $this->config->get('verbose');
+ $err = $builder->build($params[0], array(&$this, 'buildCallback'));
+ if (PEAR::isError($err)) {
+ return $err;
+ }
+
+ return true;
+ }
+
+ function buildCallback($what, $data)
+ {
+ if (($what == 'cmdoutput' && $this->debug > 1) ||
+ ($what == 'output' && $this->debug > 0)) {
+ $this->ui->outputData(rtrim($data), 'build');
+ }
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Build.xml b/includes/pear/PEAR/Command/Build.xml
new file mode 100644
index 0000000..ec4e6f5
--- /dev/null
+++ b/includes/pear/PEAR/Command/Build.xml
@@ -0,0 +1,10 @@
+<commands version="1.0">
+ <build>
+ <summary>Build an Extension From C Source</summary>
+ <function>doBuild</function>
+ <shortcut>b</shortcut>
+ <options />
+ <doc>[package.xml]
+Builds one or more extensions contained in a package.</doc>
+ </build>
+</commands> \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Channels.php b/includes/pear/PEAR/Command/Channels.php
new file mode 100644
index 0000000..5ab8f42
--- /dev/null
+++ b/includes/pear/PEAR/Command/Channels.php
@@ -0,0 +1,883 @@
+<?php
+// /* vim: set expandtab tabstop=4 shiftwidth=4: */
+/**
+ * PEAR_Command_Channels (list-channels, update-channels, channel-delete, channel-add,
+ * channel-update, channel-info, channel-alias, channel-discover commands)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+define('PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS', -500);
+
+/**
+ * PEAR commands for managing channels.
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Command_Channels extends PEAR_Command_Common
+{
+ var $commands = array(
+ 'list-channels' => array(
+ 'summary' => 'List Available Channels',
+ 'function' => 'doList',
+ 'shortcut' => 'lc',
+ 'options' => array(),
+ 'doc' => '
+List all available channels for installation.
+',
+ ),
+ 'update-channels' => array(
+ 'summary' => 'Update the Channel List',
+ 'function' => 'doUpdateAll',
+ 'shortcut' => 'uc',
+ 'options' => array(),
+ 'doc' => '
+List all installed packages in all channels.
+'
+ ),
+ 'channel-delete' => array(
+ 'summary' => 'Remove a Channel From the List',
+ 'function' => 'doDelete',
+ 'shortcut' => 'cde',
+ 'options' => array(),
+ 'doc' => '<channel name>
+Delete a channel from the registry. You may not
+remove any channel that has installed packages.
+'
+ ),
+ 'channel-add' => array(
+ 'summary' => 'Add a Channel',
+ 'function' => 'doAdd',
+ 'shortcut' => 'ca',
+ 'options' => array(),
+ 'doc' => '<channel.xml>
+Add a private channel to the channel list. Note that all
+public channels should be synced using "update-channels".
+Parameter may be either a local file or remote URL to a
+channel.xml.
+'
+ ),
+ 'channel-update' => array(
+ 'summary' => 'Update an Existing Channel',
+ 'function' => 'doUpdate',
+ 'shortcut' => 'cu',
+ 'options' => array(
+ 'force' => array(
+ 'shortopt' => 'f',
+ 'doc' => 'will force download of new channel.xml if an existing channel name is used',
+ ),
+ 'channel' => array(
+ 'shortopt' => 'c',
+ 'arg' => 'CHANNEL',
+ 'doc' => 'will force download of new channel.xml if an existing channel name is used',
+ ),
+),
+ 'doc' => '[<channel.xml>|<channel name>]
+Update a channel in the channel list directly. Note that all
+public channels can be synced using "update-channels".
+Parameter may be a local or remote channel.xml, or the name of
+an existing channel.
+'
+ ),
+ 'channel-info' => array(
+ 'summary' => 'Retrieve Information on a Channel',
+ 'function' => 'doInfo',
+ 'shortcut' => 'ci',
+ 'options' => array(),
+ 'doc' => '<package>
+List the files in an installed package.
+'
+ ),
+ 'channel-alias' => array(
+ 'summary' => 'Specify an alias to a channel name',
+ 'function' => 'doAlias',
+ 'shortcut' => 'cha',
+ 'options' => array(),
+ 'doc' => '<channel> <alias>
+Specify a specific alias to use for a channel name.
+The alias may not be an existing channel name or
+alias.
+'
+ ),
+ 'channel-discover' => array(
+ 'summary' => 'Initialize a Channel from its server',
+ 'function' => 'doDiscover',
+ 'shortcut' => 'di',
+ 'options' => array(),
+ 'doc' => '[<channel.xml>|<channel name>]
+Initialize a channel from its server and create a local channel.xml.
+If <channel name> is in the format "<username>:<password>@<channel>" then
+<username> and <password> will be set as the login username/password for
+<channel>. Use caution when passing the username/password in this way, as
+it may allow other users on your computer to briefly view your username/
+password via the system\'s process list.
+'
+ ),
+ 'channel-login' => array(
+ 'summary' => 'Connects and authenticates to remote channel server',
+ 'shortcut' => 'cli',
+ 'function' => 'doLogin',
+ 'options' => array(),
+ 'doc' => '<channel name>
+Log in to a remote channel server. If <channel name> is not supplied,
+the default channel is used. To use remote functions in the installer
+that require any kind of privileges, you need to log in first. The
+username and password you enter here will be stored in your per-user
+PEAR configuration (~/.pearrc on Unix-like systems). After logging
+in, your username and password will be sent along in subsequent
+operations on the remote server.',
+ ),
+ 'channel-logout' => array(
+ 'summary' => 'Logs out from the remote channel server',
+ 'shortcut' => 'clo',
+ 'function' => 'doLogout',
+ 'options' => array(),
+ 'doc' => '<channel name>
+Logs out from a remote channel server. If <channel name> is not supplied,
+the default channel is used. This command does not actually connect to the
+remote server, it only deletes the stored username and password from your user
+configuration.',
+ ),
+ );
+
+ /**
+ * PEAR_Command_Registry constructor.
+ *
+ * @access public
+ */
+ function PEAR_Command_Channels(&$ui, &$config)
+ {
+ parent::PEAR_Command_Common($ui, $config);
+ }
+
+ function _sortChannels($a, $b)
+ {
+ return strnatcasecmp($a->getName(), $b->getName());
+ }
+
+ function doList($command, $options, $params)
+ {
+ $reg = &$this->config->getRegistry();
+ $registered = $reg->getChannels();
+ usort($registered, array(&$this, '_sortchannels'));
+ $i = $j = 0;
+ $data = array(
+ 'caption' => 'Registered Channels:',
+ 'border' => true,
+ 'headline' => array('Channel', 'Alias', 'Summary')
+ );
+ foreach ($registered as $channel) {
+ $data['data'][] = array($channel->getName(),
+ $channel->getAlias(),
+ $channel->getSummary());
+ }
+
+ if (count($registered) === 0) {
+ $data = '(no registered channels)';
+ }
+ $this->ui->outputData($data, $command);
+ return true;
+ }
+
+ function doUpdateAll($command, $options, $params)
+ {
+ $reg = &$this->config->getRegistry();
+ $channels = $reg->getChannels();
+
+ $success = true;
+ foreach ($channels as $channel) {
+ if ($channel->getName() != '__uri') {
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $err = $this->doUpdate('channel-update',
+ $options,
+ array($channel->getName()));
+ if (PEAR::isError($err)) {
+ $this->ui->outputData($err->getMessage(), $command);
+ $success = false;
+ } else {
+ $success &= $err;
+ }
+ }
+ }
+ return $success;
+ }
+
+ function doInfo($command, $options, $params)
+ {
+ if (count($params) !== 1) {
+ return $this->raiseError("No channel specified");
+ }
+
+ $reg = &$this->config->getRegistry();
+ $channel = strtolower($params[0]);
+ if ($reg->channelExists($channel)) {
+ $chan = $reg->getChannel($channel);
+ if (PEAR::isError($chan)) {
+ return $this->raiseError($chan);
+ }
+ } else {
+ if (strpos($channel, '://')) {
+ $downloader = &$this->getDownloader();
+ $tmpdir = $this->config->get('temp_dir');
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $loc = $downloader->downloadHttp($channel, $this->ui, $tmpdir);
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($loc)) {
+ return $this->raiseError('Cannot open "' . $channel .
+ '" (' . $loc->getMessage() . ')');
+ } else {
+ $contents = implode('', file($loc));
+ }
+ } else {
+ if (!file_exists($params[0])) {
+ return $this->raiseError('Unknown channel "' . $channel . '"');
+ }
+
+ $fp = fopen($params[0], 'r');
+ if (!$fp) {
+ return $this->raiseError('Cannot open "' . $params[0] . '"');
+ }
+
+ $contents = '';
+ while (!feof($fp)) {
+ $contents .= fread($fp, 1024);
+ }
+ fclose($fp);
+ }
+
+ if (!class_exists('PEAR_ChannelFile')) {
+ require_once 'PEAR/ChannelFile.php';
+ }
+
+ $chan = new PEAR_ChannelFile;
+ $chan->fromXmlString($contents);
+ $chan->validate();
+ if ($errs = $chan->getErrors(true)) {
+ foreach ($errs as $err) {
+ $this->ui->outputData($err['level'] . ': ' . $err['message']);
+ }
+ return $this->raiseError('Channel file "' . $params[0] . '" is not valid');
+ }
+ }
+
+ if (!$chan) {
+ return $this->raiseError('Serious error: Channel "' . $params[0] .
+ '" has a corrupted registry entry');
+ }
+
+ $channel = $chan->getName();
+ $caption = 'Channel ' . $channel . ' Information:';
+ $data1 = array(
+ 'caption' => $caption,
+ 'border' => true);
+ $data1['data']['server'] = array('Name and Server', $chan->getName());
+ if ($chan->getAlias() != $chan->getName()) {
+ $data1['data']['alias'] = array('Alias', $chan->getAlias());
+ }
+
+ $data1['data']['summary'] = array('Summary', $chan->getSummary());
+ $validate = $chan->getValidationPackage();
+ $data1['data']['vpackage'] = array('Validation Package Name', $validate['_content']);
+ $data1['data']['vpackageversion'] =
+ array('Validation Package Version', $validate['attribs']['version']);
+ $d = array();
+ $d['main'] = $data1;
+
+ $data['data'] = array();
+ $data['caption'] = 'Server Capabilities';
+ $data['headline'] = array('Type', 'Version/REST type', 'Function Name/REST base');
+ if ($chan->supportsREST()) {
+ if ($chan->supportsREST()) {
+ $funcs = $chan->getFunctions('rest');
+ if (!isset($funcs[0])) {
+ $funcs = array($funcs);
+ }
+ foreach ($funcs as $protocol) {
+ $data['data'][] = array('rest', $protocol['attribs']['type'],
+ $protocol['_content']);
+ }
+ }
+ } else {
+ $data['data'][] = array('No supported protocols');
+ }
+
+ $d['protocols'] = $data;
+ $data['data'] = array();
+ $mirrors = $chan->getMirrors();
+ if ($mirrors) {
+ $data['caption'] = 'Channel ' . $channel . ' Mirrors:';
+ unset($data['headline']);
+ foreach ($mirrors as $mirror) {
+ $data['data'][] = array($mirror['attribs']['host']);
+ $d['mirrors'] = $data;
+ }
+
+ foreach ($mirrors as $i => $mirror) {
+ $data['data'] = array();
+ $data['caption'] = 'Mirror ' . $mirror['attribs']['host'] . ' Capabilities';
+ $data['headline'] = array('Type', 'Version/REST type', 'Function Name/REST base');
+ if ($chan->supportsREST($mirror['attribs']['host'])) {
+ if ($chan->supportsREST($mirror['attribs']['host'])) {
+ $funcs = $chan->getFunctions('rest', $mirror['attribs']['host']);
+ if (!isset($funcs[0])) {
+ $funcs = array($funcs);
+ }
+
+ foreach ($funcs as $protocol) {
+ $data['data'][] = array('rest', $protocol['attribs']['type'],
+ $protocol['_content']);
+ }
+ }
+ } else {
+ $data['data'][] = array('No supported protocols');
+ }
+ $d['mirrorprotocols' . $i] = $data;
+ }
+ }
+ $this->ui->outputData($d, 'channel-info');
+ }
+
+ // }}}
+
+ function doDelete($command, $options, $params)
+ {
+ if (count($params) !== 1) {
+ return $this->raiseError('channel-delete: no channel specified');
+ }
+
+ $reg = &$this->config->getRegistry();
+ if (!$reg->channelExists($params[0])) {
+ return $this->raiseError('channel-delete: channel "' . $params[0] . '" does not exist');
+ }
+
+ $channel = $reg->channelName($params[0]);
+ if ($channel == 'pear.php.net') {
+ return $this->raiseError('Cannot delete the pear.php.net channel');
+ }
+
+ if ($channel == 'pecl.php.net') {
+ return $this->raiseError('Cannot delete the pecl.php.net channel');
+ }
+
+ if ($channel == 'doc.php.net') {
+ return $this->raiseError('Cannot delete the doc.php.net channel');
+ }
+
+ if ($channel == '__uri') {
+ return $this->raiseError('Cannot delete the __uri pseudo-channel');
+ }
+
+ if (PEAR::isError($err = $reg->listPackages($channel))) {
+ return $err;
+ }
+
+ if (count($err)) {
+ return $this->raiseError('Channel "' . $channel .
+ '" has installed packages, cannot delete');
+ }
+
+ if (!$reg->deleteChannel($channel)) {
+ return $this->raiseError('Channel "' . $channel . '" deletion failed');
+ } else {
+ $this->config->deleteChannel($channel);
+ $this->ui->outputData('Channel "' . $channel . '" deleted', $command);
+ }
+ }
+
+ function doAdd($command, $options, $params)
+ {
+ if (count($params) !== 1) {
+ return $this->raiseError('channel-add: no channel file specified');
+ }
+
+ if (strpos($params[0], '://')) {
+ $downloader = &$this->getDownloader();
+ $tmpdir = $this->config->get('temp_dir');
+ if (!file_exists($tmpdir)) {
+ require_once 'System.php';
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $err = System::mkdir(array('-p', $tmpdir));
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($err)) {
+ return $this->raiseError('channel-add: temp_dir does not exist: "' .
+ $tmpdir .
+ '" - You can change this location with "pear config-set temp_dir"');
+ }
+ }
+
+ if (!is_writable($tmpdir)) {
+ return $this->raiseError('channel-add: temp_dir is not writable: "' .
+ $tmpdir .
+ '" - You can change this location with "pear config-set temp_dir"');
+ }
+
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $loc = $downloader->downloadHttp($params[0], $this->ui, $tmpdir, null, false);
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($loc)) {
+ return $this->raiseError('channel-add: Cannot open "' . $params[0] .
+ '" (' . $loc->getMessage() . ')');
+ }
+
+ list($loc, $lastmodified) = $loc;
+ $contents = implode('', file($loc));
+ } else {
+ $lastmodified = $fp = false;
+ if (file_exists($params[0])) {
+ $fp = fopen($params[0], 'r');
+ }
+
+ if (!$fp) {
+ return $this->raiseError('channel-add: cannot open "' . $params[0] . '"');
+ }
+
+ $contents = '';
+ while (!feof($fp)) {
+ $contents .= fread($fp, 1024);
+ }
+ fclose($fp);
+ }
+
+ if (!class_exists('PEAR_ChannelFile')) {
+ require_once 'PEAR/ChannelFile.php';
+ }
+
+ $channel = new PEAR_ChannelFile;
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $result = $channel->fromXmlString($contents);
+ PEAR::staticPopErrorHandling();
+ if (!$result) {
+ $exit = false;
+ if (count($errors = $channel->getErrors(true))) {
+ foreach ($errors as $error) {
+ $this->ui->outputData(ucfirst($error['level'] . ': ' . $error['message']));
+ if (!$exit) {
+ $exit = $error['level'] == 'error' ? true : false;
+ }
+ }
+ if ($exit) {
+ return $this->raiseError('channel-add: invalid channel.xml file');
+ }
+ }
+ }
+
+ $reg = &$this->config->getRegistry();
+ if ($reg->channelExists($channel->getName())) {
+ return $this->raiseError('channel-add: Channel "' . $channel->getName() .
+ '" exists, use channel-update to update entry', PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS);
+ }
+
+ $ret = $reg->addChannel($channel, $lastmodified);
+ if (PEAR::isError($ret)) {
+ return $ret;
+ }
+
+ if (!$ret) {
+ return $this->raiseError('channel-add: adding Channel "' . $channel->getName() .
+ '" to registry failed');
+ }
+
+ $this->config->setChannels($reg->listChannels());
+ $this->config->writeConfigFile();
+ $this->ui->outputData('Adding Channel "' . $channel->getName() . '" succeeded', $command);
+ }
+
+ function doUpdate($command, $options, $params)
+ {
+ if (count($params) !== 1) {
+ return $this->raiseError("No channel file specified");
+ }
+
+ $tmpdir = $this->config->get('temp_dir');
+ if (!file_exists($tmpdir)) {
+ require_once 'System.php';
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $err = System::mkdir(array('-p', $tmpdir));
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($err)) {
+ return $this->raiseError('channel-add: temp_dir does not exist: "' .
+ $tmpdir .
+ '" - You can change this location with "pear config-set temp_dir"');
+ }
+ }
+
+ if (!is_writable($tmpdir)) {
+ return $this->raiseError('channel-add: temp_dir is not writable: "' .
+ $tmpdir .
+ '" - You can change this location with "pear config-set temp_dir"');
+ }
+
+ $reg = &$this->config->getRegistry();
+ $lastmodified = false;
+ if ((!file_exists($params[0]) || is_dir($params[0]))
+ && $reg->channelExists(strtolower($params[0]))) {
+ $c = $reg->getChannel(strtolower($params[0]));
+ if (PEAR::isError($c)) {
+ return $this->raiseError($c);
+ }
+
+ $this->ui->outputData("Updating channel \"$params[0]\"", $command);
+ $dl = &$this->getDownloader(array());
+ // if force is specified, use a timestamp of "1" to force retrieval
+ $lastmodified = isset($options['force']) ? false : $c->lastModified();
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $contents = $dl->downloadHttp('http://' . $c->getName() . '/channel.xml',
+ $this->ui, $tmpdir, null, $lastmodified);
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($contents)) {
+ // Attempt to fall back to https
+ $this->ui->outputData("Channel \"$params[0]\" is not responding over http://, failed with message: " . $contents->getMessage());
+ $this->ui->outputData("Trying channel \"$params[0]\" over https:// instead");
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $contents = $dl->downloadHttp('https://' . $c->getName() . '/channel.xml',
+ $this->ui, $tmpdir, null, $lastmodified);
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($contents)) {
+ return $this->raiseError('Cannot retrieve channel.xml for channel "' .
+ $c->getName() . '" (' . $contents->getMessage() . ')');
+ }
+ }
+
+ list($contents, $lastmodified) = $contents;
+ if (!$contents) {
+ $this->ui->outputData("Channel \"$params[0]\" is up to date");
+ return;
+ }
+
+ $contents = implode('', file($contents));
+ if (!class_exists('PEAR_ChannelFile')) {
+ require_once 'PEAR/ChannelFile.php';
+ }
+
+ $channel = new PEAR_ChannelFile;
+ $channel->fromXmlString($contents);
+ if (!$channel->getErrors()) {
+ // security check: is the downloaded file for the channel we got it from?
+ if (strtolower($channel->getName()) != strtolower($c->getName())) {
+ if (!isset($options['force'])) {
+ return $this->raiseError('ERROR: downloaded channel definition file' .
+ ' for channel "' . $channel->getName() . '" from channel "' .
+ strtolower($c->getName()) . '"');
+ }
+
+ $this->ui->log(0, 'WARNING: downloaded channel definition file' .
+ ' for channel "' . $channel->getName() . '" from channel "' .
+ strtolower($c->getName()) . '"');
+ }
+ }
+ } else {
+ if (strpos($params[0], '://')) {
+ $dl = &$this->getDownloader();
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $loc = $dl->downloadHttp($params[0],
+ $this->ui, $tmpdir, null, $lastmodified);
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($loc)) {
+ return $this->raiseError("Cannot open " . $params[0] .
+ ' (' . $loc->getMessage() . ')');
+ }
+
+ list($loc, $lastmodified) = $loc;
+ $contents = implode('', file($loc));
+ } else {
+ $fp = false;
+ if (file_exists($params[0])) {
+ $fp = fopen($params[0], 'r');
+ }
+
+ if (!$fp) {
+ return $this->raiseError("Cannot open " . $params[0]);
+ }
+
+ $contents = '';
+ while (!feof($fp)) {
+ $contents .= fread($fp, 1024);
+ }
+ fclose($fp);
+ }
+
+ if (!class_exists('PEAR_ChannelFile')) {
+ require_once 'PEAR/ChannelFile.php';
+ }
+
+ $channel = new PEAR_ChannelFile;
+ $channel->fromXmlString($contents);
+ }
+
+ $exit = false;
+ if (count($errors = $channel->getErrors(true))) {
+ foreach ($errors as $error) {
+ $this->ui->outputData(ucfirst($error['level'] . ': ' . $error['message']));
+ if (!$exit) {
+ $exit = $error['level'] == 'error' ? true : false;
+ }
+ }
+ if ($exit) {
+ return $this->raiseError('Invalid channel.xml file');
+ }
+ }
+
+ if (!$reg->channelExists($channel->getName())) {
+ return $this->raiseError('Error: Channel "' . $channel->getName() .
+ '" does not exist, use channel-add to add an entry');
+ }
+
+ $ret = $reg->updateChannel($channel, $lastmodified);
+ if (PEAR::isError($ret)) {
+ return $ret;
+ }
+
+ if (!$ret) {
+ return $this->raiseError('Updating Channel "' . $channel->getName() .
+ '" in registry failed');
+ }
+
+ $this->config->setChannels($reg->listChannels());
+ $this->config->writeConfigFile();
+ $this->ui->outputData('Update of Channel "' . $channel->getName() . '" succeeded');
+ }
+
+ function &getDownloader()
+ {
+ if (!class_exists('PEAR_Downloader')) {
+ require_once 'PEAR/Downloader.php';
+ }
+ $a = new PEAR_Downloader($this->ui, array(), $this->config);
+ return $a;
+ }
+
+ function doAlias($command, $options, $params)
+ {
+ if (count($params) === 1) {
+ return $this->raiseError('No channel alias specified');
+ }
+
+ if (count($params) !== 2 || (!empty($params[1]) && $params[1]{0} == '-')) {
+ return $this->raiseError(
+ 'Invalid format, correct is: channel-alias channel alias');
+ }
+
+ $reg = &$this->config->getRegistry();
+ if (!$reg->channelExists($params[0], true)) {
+ $extra = '';
+ if ($reg->isAlias($params[0])) {
+ $extra = ' (use "channel-alias ' . $reg->channelName($params[0]) . ' ' .
+ strtolower($params[1]) . '")';
+ }
+
+ return $this->raiseError('"' . $params[0] . '" is not a valid channel' . $extra);
+ }
+
+ if ($reg->isAlias($params[1])) {
+ return $this->raiseError('Channel "' . $reg->channelName($params[1]) . '" is ' .
+ 'already aliased to "' . strtolower($params[1]) . '", cannot re-alias');
+ }
+
+ $chan = &$reg->getChannel($params[0]);
+ if (PEAR::isError($chan)) {
+ return $this->raiseError('Corrupt registry? Error retrieving channel "' . $params[0] .
+ '" information (' . $chan->getMessage() . ')');
+ }
+
+ // make it a local alias
+ if (!$chan->setAlias(strtolower($params[1]), true)) {
+ return $this->raiseError('Alias "' . strtolower($params[1]) .
+ '" is not a valid channel alias');
+ }
+
+ $reg->updateChannel($chan);
+ $this->ui->outputData('Channel "' . $chan->getName() . '" aliased successfully to "' .
+ strtolower($params[1]) . '"');
+ }
+
+ /**
+ * The channel-discover command
+ *
+ * @param string $command command name
+ * @param array $options option_name => value
+ * @param array $params list of additional parameters.
+ * $params[0] should contain a string with either:
+ * - <channel name> or
+ * - <username>:<password>@<channel name>
+ * @return null|PEAR_Error
+ */
+ function doDiscover($command, $options, $params)
+ {
+ if (count($params) !== 1) {
+ return $this->raiseError("No channel server specified");
+ }
+
+ // Look for the possible input format "<username>:<password>@<channel>"
+ if (preg_match('/^(.+):(.+)@(.+)\\z/', $params[0], $matches)) {
+ $username = $matches[1];
+ $password = $matches[2];
+ $channel = $matches[3];
+ } else {
+ $channel = $params[0];
+ }
+
+ $reg = &$this->config->getRegistry();
+ if ($reg->channelExists($channel)) {
+ if (!$reg->isAlias($channel)) {
+ return $this->raiseError("Channel \"$channel\" is already initialized", PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS);
+ }
+
+ return $this->raiseError("A channel alias named \"$channel\" " .
+ 'already exists, aliasing channel "' . $reg->channelName($channel)
+ . '"');
+ }
+
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $err = $this->doAdd($command, $options, array('http://' . $channel . '/channel.xml'));
+ $this->popErrorHandling();
+ if (PEAR::isError($err)) {
+ if ($err->getCode() === PEAR_COMMAND_CHANNELS_CHANNEL_EXISTS) {
+ return $this->raiseError("Discovery of channel \"$channel\" failed (" .
+ $err->getMessage() . ')');
+ }
+ // Attempt fetch via https
+ $this->ui->outputData("Discovering channel $channel over http:// failed with message: " . $err->getMessage());
+ $this->ui->outputData("Trying to discover channel $channel over https:// instead");
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $err = $this->doAdd($command, $options, array('https://' . $channel . '/channel.xml'));
+ $this->popErrorHandling();
+ if (PEAR::isError($err)) {
+ return $this->raiseError("Discovery of channel \"$channel\" failed (" .
+ $err->getMessage() . ')');
+ }
+ }
+
+ // Store username/password if they were given
+ // Arguably we should do a logintest on the channel here, but since
+ // that's awkward on a REST-based channel (even "pear login" doesn't
+ // do it for those), and XML-RPC is deprecated, it's fairly pointless.
+ if (isset($username)) {
+ $this->config->set('username', $username, 'user', $channel);
+ $this->config->set('password', $password, 'user', $channel);
+ $this->config->store();
+ $this->ui->outputData("Stored login for channel \"$channel\" using username \"$username\"", $command);
+ }
+
+ $this->ui->outputData("Discovery of channel \"$channel\" succeeded", $command);
+ }
+
+ /**
+ * Execute the 'login' command.
+ *
+ * @param string $command command name
+ * @param array $options option_name => value
+ * @param array $params list of additional parameters
+ *
+ * @return bool TRUE on success or
+ * a PEAR error on failure
+ *
+ * @access public
+ */
+ function doLogin($command, $options, $params)
+ {
+ $reg = &$this->config->getRegistry();
+
+ // If a parameter is supplied, use that as the channel to log in to
+ $channel = isset($params[0]) ? $params[0] : $this->config->get('default_channel');
+
+ $chan = $reg->getChannel($channel);
+ if (PEAR::isError($chan)) {
+ return $this->raiseError($chan);
+ }
+
+ $server = $this->config->get('preferred_mirror', null, $channel);
+ $username = $this->config->get('username', null, $channel);
+ if (empty($username)) {
+ $username = isset($_ENV['USER']) ? $_ENV['USER'] : null;
+ }
+ $this->ui->outputData("Logging in to $server.", $command);
+
+ list($username, $password) = $this->ui->userDialog(
+ $command,
+ array('Username', 'Password'),
+ array('text', 'password'),
+ array($username, '')
+ );
+ $username = trim($username);
+ $password = trim($password);
+
+ $ourfile = $this->config->getConfFile('user');
+ if (!$ourfile) {
+ $ourfile = $this->config->getConfFile('system');
+ }
+
+ $this->config->set('username', $username, 'user', $channel);
+ $this->config->set('password', $password, 'user', $channel);
+
+ if ($chan->supportsREST()) {
+ $ok = true;
+ }
+
+ if ($ok !== true) {
+ return $this->raiseError('Login failed!');
+ }
+
+ $this->ui->outputData("Logged in.", $command);
+ // avoid changing any temporary settings changed with -d
+ $ourconfig = new PEAR_Config($ourfile, $ourfile);
+ $ourconfig->set('username', $username, 'user', $channel);
+ $ourconfig->set('password', $password, 'user', $channel);
+ $ourconfig->store();
+
+ return true;
+ }
+
+ /**
+ * Execute the 'logout' command.
+ *
+ * @param string $command command name
+ * @param array $options option_name => value
+ * @param array $params list of additional parameters
+ *
+ * @return bool TRUE on success or
+ * a PEAR error on failure
+ *
+ * @access public
+ */
+ function doLogout($command, $options, $params)
+ {
+ $reg = &$this->config->getRegistry();
+
+ // If a parameter is supplied, use that as the channel to log in to
+ $channel = isset($params[0]) ? $params[0] : $this->config->get('default_channel');
+
+ $chan = $reg->getChannel($channel);
+ if (PEAR::isError($chan)) {
+ return $this->raiseError($chan);
+ }
+
+ $server = $this->config->get('preferred_mirror', null, $channel);
+ $this->ui->outputData("Logging out from $server.", $command);
+ $this->config->remove('username', 'user', $channel);
+ $this->config->remove('password', 'user', $channel);
+ $this->config->store();
+ return true;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Channels.xml b/includes/pear/PEAR/Command/Channels.xml
new file mode 100644
index 0000000..47b7206
--- /dev/null
+++ b/includes/pear/PEAR/Command/Channels.xml
@@ -0,0 +1,123 @@
+<commands version="1.0">
+ <list-channels>
+ <summary>List Available Channels</summary>
+ <function>doList</function>
+ <shortcut>lc</shortcut>
+ <options />
+ <doc>
+List all available channels for installation.
+</doc>
+ </list-channels>
+ <update-channels>
+ <summary>Update the Channel List</summary>
+ <function>doUpdateAll</function>
+ <shortcut>uc</shortcut>
+ <options />
+ <doc>
+List all installed packages in all channels.
+</doc>
+ </update-channels>
+ <channel-delete>
+ <summary>Remove a Channel From the List</summary>
+ <function>doDelete</function>
+ <shortcut>cde</shortcut>
+ <options />
+ <doc>&lt;channel name&gt;
+Delete a channel from the registry. You may not
+remove any channel that has installed packages.
+</doc>
+ </channel-delete>
+ <channel-add>
+ <summary>Add a Channel</summary>
+ <function>doAdd</function>
+ <shortcut>ca</shortcut>
+ <options />
+ <doc>&lt;channel.xml&gt;
+Add a private channel to the channel list. Note that all
+public channels should be synced using &quot;update-channels&quot;.
+Parameter may be either a local file or remote URL to a
+channel.xml.
+</doc>
+ </channel-add>
+ <channel-update>
+ <summary>Update an Existing Channel</summary>
+ <function>doUpdate</function>
+ <shortcut>cu</shortcut>
+ <options>
+ <force>
+ <shortopt>f</shortopt>
+ <doc>will force download of new channel.xml if an existing channel name is used</doc>
+ </force>
+ <channel>
+ <shortopt>c</shortopt>
+ <doc>will force download of new channel.xml if an existing channel name is used</doc>
+ <arg>CHANNEL</arg>
+ </channel>
+ </options>
+ <doc>[&lt;channel.xml&gt;|&lt;channel name&gt;]
+Update a channel in the channel list directly. Note that all
+public channels can be synced using &quot;update-channels&quot;.
+Parameter may be a local or remote channel.xml, or the name of
+an existing channel.
+</doc>
+ </channel-update>
+ <channel-info>
+ <summary>Retrieve Information on a Channel</summary>
+ <function>doInfo</function>
+ <shortcut>ci</shortcut>
+ <options />
+ <doc>&lt;package&gt;
+List the files in an installed package.
+</doc>
+ </channel-info>
+ <channel-alias>
+ <summary>Specify an alias to a channel name</summary>
+ <function>doAlias</function>
+ <shortcut>cha</shortcut>
+ <options />
+ <doc>&lt;channel&gt; &lt;alias&gt;
+Specify a specific alias to use for a channel name.
+The alias may not be an existing channel name or
+alias.
+</doc>
+ </channel-alias>
+ <channel-discover>
+ <summary>Initialize a Channel from its server</summary>
+ <function>doDiscover</function>
+ <shortcut>di</shortcut>
+ <options />
+ <doc>[&lt;channel.xml&gt;|&lt;channel name&gt;]
+Initialize a channel from its server and create a local channel.xml.
+If &lt;channel name&gt; is in the format &quot;&lt;username&gt;:&lt;password&gt;@&lt;channel&gt;&quot; then
+&lt;username&gt; and &lt;password&gt; will be set as the login username/password for
+&lt;channel&gt;. Use caution when passing the username/password in this way, as
+it may allow other users on your computer to briefly view your username/
+password via the system&#039;s process list.
+</doc>
+ </channel-discover>
+ <channel-login>
+ <summary>Connects and authenticates to remote channel server</summary>
+ <function>doLogin</function>
+ <shortcut>cli</shortcut>
+ <options />
+ <doc>&lt;channel name&gt;
+Log in to a remote channel server. If &lt;channel name&gt; is not supplied,
+the default channel is used. To use remote functions in the installer
+that require any kind of privileges, you need to log in first. The
+username and password you enter here will be stored in your per-user
+PEAR configuration (~/.pearrc on Unix-like systems). After logging
+in, your username and password will be sent along in subsequent
+operations on the remote server.</doc>
+ </channel-login>
+ <channel-logout>
+ <summary>Logs out from the remote channel server</summary>
+ <function>doLogout</function>
+ <shortcut>clo</shortcut>
+ <options />
+ <doc>&lt;channel name&gt;
+Logs out from a remote channel server. If &lt;channel name&gt; is not supplied,
+the default channel is used. This command does not actually connect to the
+remote server, it only deletes the stored username and password from your user
+configuration.</doc>
+ </channel-logout>
+</commands> \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Common.php b/includes/pear/PEAR/Command/Common.php
new file mode 100644
index 0000000..69b5cf2
--- /dev/null
+++ b/includes/pear/PEAR/Command/Common.php
@@ -0,0 +1,273 @@
+<?php
+/**
+ * PEAR_Command_Common base class
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR.php';
+
+/**
+ * PEAR commands base class
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ */
+class PEAR_Command_Common extends PEAR
+{
+ /**
+ * PEAR_Config object used to pass user system and configuration
+ * on when executing commands
+ *
+ * @var PEAR_Config
+ */
+ var $config;
+ /**
+ * @var PEAR_Registry
+ * @access protected
+ */
+ var $_registry;
+
+ /**
+ * User Interface object, for all interaction with the user.
+ * @var object
+ */
+ var $ui;
+
+ var $_deps_rel_trans = array(
+ 'lt' => '<',
+ 'le' => '<=',
+ 'eq' => '=',
+ 'ne' => '!=',
+ 'gt' => '>',
+ 'ge' => '>=',
+ 'has' => '=='
+ );
+
+ var $_deps_type_trans = array(
+ 'pkg' => 'package',
+ 'ext' => 'extension',
+ 'php' => 'PHP',
+ 'prog' => 'external program',
+ 'ldlib' => 'external library for linking',
+ 'rtlib' => 'external runtime library',
+ 'os' => 'operating system',
+ 'websrv' => 'web server',
+ 'sapi' => 'SAPI backend'
+ );
+
+ /**
+ * PEAR_Command_Common constructor.
+ *
+ * @access public
+ */
+ function PEAR_Command_Common(&$ui, &$config)
+ {
+ parent::PEAR();
+ $this->config = &$config;
+ $this->ui = &$ui;
+ }
+
+ /**
+ * Return a list of all the commands defined by this class.
+ * @return array list of commands
+ * @access public
+ */
+ function getCommands()
+ {
+ $ret = array();
+ foreach (array_keys($this->commands) as $command) {
+ $ret[$command] = $this->commands[$command]['summary'];
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Return a list of all the command shortcuts defined by this class.
+ * @return array shortcut => command
+ * @access public
+ */
+ function getShortcuts()
+ {
+ $ret = array();
+ foreach (array_keys($this->commands) as $command) {
+ if (isset($this->commands[$command]['shortcut'])) {
+ $ret[$this->commands[$command]['shortcut']] = $command;
+ }
+ }
+
+ return $ret;
+ }
+
+ function getOptions($command)
+ {
+ $shortcuts = $this->getShortcuts();
+ if (isset($shortcuts[$command])) {
+ $command = $shortcuts[$command];
+ }
+
+ if (isset($this->commands[$command]) &&
+ isset($this->commands[$command]['options'])) {
+ return $this->commands[$command]['options'];
+ }
+
+ return null;
+ }
+
+ function getGetoptArgs($command, &$short_args, &$long_args)
+ {
+ $short_args = '';
+ $long_args = array();
+ if (empty($this->commands[$command]) || empty($this->commands[$command]['options'])) {
+ return;
+ }
+
+ reset($this->commands[$command]['options']);
+ while (list($option, $info) = each($this->commands[$command]['options'])) {
+ $larg = $sarg = '';
+ if (isset($info['arg'])) {
+ if ($info['arg']{0} == '(') {
+ $larg = '==';
+ $sarg = '::';
+ $arg = substr($info['arg'], 1, -1);
+ } else {
+ $larg = '=';
+ $sarg = ':';
+ $arg = $info['arg'];
+ }
+ }
+
+ if (isset($info['shortopt'])) {
+ $short_args .= $info['shortopt'] . $sarg;
+ }
+
+ $long_args[] = $option . $larg;
+ }
+ }
+
+ /**
+ * Returns the help message for the given command
+ *
+ * @param string $command The command
+ * @return mixed A fail string if the command does not have help or
+ * a two elements array containing [0]=>help string,
+ * [1]=> help string for the accepted cmd args
+ */
+ function getHelp($command)
+ {
+ $config = &PEAR_Config::singleton();
+ if (!isset($this->commands[$command])) {
+ return "No such command \"$command\"";
+ }
+
+ $help = null;
+ if (isset($this->commands[$command]['doc'])) {
+ $help = $this->commands[$command]['doc'];
+ }
+
+ if (empty($help)) {
+ // XXX (cox) Fallback to summary if there is no doc (show both?)
+ if (!isset($this->commands[$command]['summary'])) {
+ return "No help for command \"$command\"";
+ }
+ $help = $this->commands[$command]['summary'];
+ }
+
+ if (preg_match_all('/{config\s+([^\}]+)}/e', $help, $matches)) {
+ foreach($matches[0] as $k => $v) {
+ $help = preg_replace("/$v/", $config->get($matches[1][$k]), $help);
+ }
+ }
+
+ return array($help, $this->getHelpArgs($command));
+ }
+
+ /**
+ * Returns the help for the accepted arguments of a command
+ *
+ * @param string $command
+ * @return string The help string
+ */
+ function getHelpArgs($command)
+ {
+ if (isset($this->commands[$command]['options']) &&
+ count($this->commands[$command]['options']))
+ {
+ $help = "Options:\n";
+ foreach ($this->commands[$command]['options'] as $k => $v) {
+ if (isset($v['arg'])) {
+ if ($v['arg'][0] == '(') {
+ $arg = substr($v['arg'], 1, -1);
+ $sapp = " [$arg]";
+ $lapp = "[=$arg]";
+ } else {
+ $sapp = " $v[arg]";
+ $lapp = "=$v[arg]";
+ }
+ } else {
+ $sapp = $lapp = "";
+ }
+
+ if (isset($v['shortopt'])) {
+ $s = $v['shortopt'];
+ $help .= " -$s$sapp, --$k$lapp\n";
+ } else {
+ $help .= " --$k$lapp\n";
+ }
+
+ $p = " ";
+ $doc = rtrim(str_replace("\n", "\n$p", $v['doc']));
+ $help .= " $doc\n";
+ }
+
+ return $help;
+ }
+
+ return null;
+ }
+
+ function run($command, $options, $params)
+ {
+ if (empty($this->commands[$command]['function'])) {
+ // look for shortcuts
+ foreach (array_keys($this->commands) as $cmd) {
+ if (isset($this->commands[$cmd]['shortcut']) && $this->commands[$cmd]['shortcut'] == $command) {
+ if (empty($this->commands[$cmd]['function'])) {
+ return $this->raiseError("unknown command `$command'");
+ } else {
+ $func = $this->commands[$cmd]['function'];
+ }
+ $command = $cmd;
+
+ //$command = $this->commands[$cmd]['function'];
+ break;
+ }
+ }
+ } else {
+ $func = $this->commands[$command]['function'];
+ }
+
+ return $this->$func($command, $options, $params);
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Config.php b/includes/pear/PEAR/Command/Config.php
new file mode 100644
index 0000000..8561ee5
--- /dev/null
+++ b/includes/pear/PEAR/Command/Config.php
@@ -0,0 +1,414 @@
+<?php
+/**
+ * PEAR_Command_Config (config-show, config-get, config-set, config-help, config-create commands)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+/**
+ * PEAR commands for managing configuration data.
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ */
+class PEAR_Command_Config extends PEAR_Command_Common
+{
+ var $commands = array(
+ 'config-show' => array(
+ 'summary' => 'Show All Settings',
+ 'function' => 'doConfigShow',
+ 'shortcut' => 'csh',
+ 'options' => array(
+ 'channel' => array(
+ 'shortopt' => 'c',
+ 'doc' => 'show configuration variables for another channel',
+ 'arg' => 'CHAN',
+ ),
+),
+ 'doc' => '[layer]
+Displays all configuration values. An optional argument
+may be used to tell which configuration layer to display. Valid
+configuration layers are "user", "system" and "default". To display
+configurations for different channels, set the default_channel
+configuration variable and run config-show again.
+',
+ ),
+ 'config-get' => array(
+ 'summary' => 'Show One Setting',
+ 'function' => 'doConfigGet',
+ 'shortcut' => 'cg',
+ 'options' => array(
+ 'channel' => array(
+ 'shortopt' => 'c',
+ 'doc' => 'show configuration variables for another channel',
+ 'arg' => 'CHAN',
+ ),
+),
+ 'doc' => '<parameter> [layer]
+Displays the value of one configuration parameter. The
+first argument is the name of the parameter, an optional second argument
+may be used to tell which configuration layer to look in. Valid configuration
+layers are "user", "system" and "default". If no layer is specified, a value
+will be picked from the first layer that defines the parameter, in the order
+just specified. The configuration value will be retrieved for the channel
+specified by the default_channel configuration variable.
+',
+ ),
+ 'config-set' => array(
+ 'summary' => 'Change Setting',
+ 'function' => 'doConfigSet',
+ 'shortcut' => 'cs',
+ 'options' => array(
+ 'channel' => array(
+ 'shortopt' => 'c',
+ 'doc' => 'show configuration variables for another channel',
+ 'arg' => 'CHAN',
+ ),
+),
+ 'doc' => '<parameter> <value> [layer]
+Sets the value of one configuration parameter. The first argument is
+the name of the parameter, the second argument is the new value. Some
+parameters are subject to validation, and the command will fail with
+an error message if the new value does not make sense. An optional
+third argument may be used to specify in which layer to set the
+configuration parameter. The default layer is "user". The
+configuration value will be set for the current channel, which
+is controlled by the default_channel configuration variable.
+',
+ ),
+ 'config-help' => array(
+ 'summary' => 'Show Information About Setting',
+ 'function' => 'doConfigHelp',
+ 'shortcut' => 'ch',
+ 'options' => array(),
+ 'doc' => '[parameter]
+Displays help for a configuration parameter. Without arguments it
+displays help for all configuration parameters.
+',
+ ),
+ 'config-create' => array(
+ 'summary' => 'Create a Default configuration file',
+ 'function' => 'doConfigCreate',
+ 'shortcut' => 'coc',
+ 'options' => array(
+ 'windows' => array(
+ 'shortopt' => 'w',
+ 'doc' => 'create a config file for a windows install',
+ ),
+ ),
+ 'doc' => '<root path> <filename>
+Create a default configuration file with all directory configuration
+variables set to subdirectories of <root path>, and save it as <filename>.
+This is useful especially for creating a configuration file for a remote
+PEAR installation (using the --remoteconfig option of install, upgrade,
+and uninstall).
+',
+ ),
+ );
+
+ /**
+ * PEAR_Command_Config constructor.
+ *
+ * @access public
+ */
+ function PEAR_Command_Config(&$ui, &$config)
+ {
+ parent::PEAR_Command_Common($ui, $config);
+ }
+
+ function doConfigShow($command, $options, $params)
+ {
+ $layer = null;
+ if (is_array($params)) {
+ $layer = isset($params[0]) ? $params[0] : null;
+ }
+
+ // $params[0] -> the layer
+ if ($error = $this->_checkLayer($layer)) {
+ return $this->raiseError("config-show:$error");
+ }
+
+ $keys = $this->config->getKeys();
+ sort($keys);
+ $channel = isset($options['channel']) ? $options['channel'] :
+ $this->config->get('default_channel');
+ $reg = &$this->config->getRegistry();
+ if (!$reg->channelExists($channel)) {
+ return $this->raiseError('Channel "' . $channel . '" does not exist');
+ }
+
+ $channel = $reg->channelName($channel);
+ $data = array('caption' => 'Configuration (channel ' . $channel . '):');
+ foreach ($keys as $key) {
+ $type = $this->config->getType($key);
+ $value = $this->config->get($key, $layer, $channel);
+ if ($type == 'password' && $value) {
+ $value = '********';
+ }
+
+ if ($value === false) {
+ $value = 'false';
+ } elseif ($value === true) {
+ $value = 'true';
+ }
+
+ $data['data'][$this->config->getGroup($key)][] = array($this->config->getPrompt($key) , $key, $value);
+ }
+
+ foreach ($this->config->getLayers() as $layer) {
+ $data['data']['Config Files'][] = array(ucfirst($layer) . ' Configuration File', 'Filename' , $this->config->getConfFile($layer));
+ }
+
+ $this->ui->outputData($data, $command);
+ return true;
+ }
+
+ function doConfigGet($command, $options, $params)
+ {
+ $args_cnt = is_array($params) ? count($params) : 0;
+ switch ($args_cnt) {
+ case 1:
+ $config_key = $params[0];
+ $layer = null;
+ break;
+ case 2:
+ $config_key = $params[0];
+ $layer = $params[1];
+ if ($error = $this->_checkLayer($layer)) {
+ return $this->raiseError("config-get:$error");
+ }
+ break;
+ case 0:
+ default:
+ return $this->raiseError("config-get expects 1 or 2 parameters");
+ }
+
+ $reg = &$this->config->getRegistry();
+ $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel');
+ if (!$reg->channelExists($channel)) {
+ return $this->raiseError('Channel "' . $channel . '" does not exist');
+ }
+
+ $channel = $reg->channelName($channel);
+ $this->ui->outputData($this->config->get($config_key, $layer, $channel), $command);
+ return true;
+ }
+
+ function doConfigSet($command, $options, $params)
+ {
+ // $param[0] -> a parameter to set
+ // $param[1] -> the value for the parameter
+ // $param[2] -> the layer
+ $failmsg = '';
+ if (count($params) < 2 || count($params) > 3) {
+ $failmsg .= "config-set expects 2 or 3 parameters";
+ return PEAR::raiseError($failmsg);
+ }
+
+ if (isset($params[2]) && ($error = $this->_checkLayer($params[2]))) {
+ $failmsg .= $error;
+ return PEAR::raiseError("config-set:$failmsg");
+ }
+
+ $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel');
+ $reg = &$this->config->getRegistry();
+ if (!$reg->channelExists($channel)) {
+ return $this->raiseError('Channel "' . $channel . '" does not exist');
+ }
+
+ $channel = $reg->channelName($channel);
+ if ($params[0] == 'default_channel' && !$reg->channelExists($params[1])) {
+ return $this->raiseError('Channel "' . $params[1] . '" does not exist');
+ }
+
+ if ($params[0] == 'preferred_mirror'
+ && (
+ !$reg->mirrorExists($channel, $params[1]) &&
+ (!$reg->channelExists($params[1]) || $channel != $params[1])
+ )
+ ) {
+ $msg = 'Channel Mirror "' . $params[1] . '" does not exist';
+ $msg .= ' in your registry for channel "' . $channel . '".';
+ $msg .= "\n" . 'Attempt to run "pear channel-update ' . $channel .'"';
+ $msg .= ' if you believe this mirror should exist as you may';
+ $msg .= ' have outdated channel information.';
+ return $this->raiseError($msg);
+ }
+
+ if (count($params) == 2) {
+ array_push($params, 'user');
+ $layer = 'user';
+ } else {
+ $layer = $params[2];
+ }
+
+ array_push($params, $channel);
+ if (!call_user_func_array(array(&$this->config, 'set'), $params)) {
+ array_pop($params);
+ $failmsg = "config-set (" . implode(", ", $params) . ") failed, channel $channel";
+ } else {
+ $this->config->store($layer);
+ }
+
+ if ($failmsg) {
+ return $this->raiseError($failmsg);
+ }
+
+ $this->ui->outputData('config-set succeeded', $command);
+ return true;
+ }
+
+ function doConfigHelp($command, $options, $params)
+ {
+ if (empty($params)) {
+ $params = $this->config->getKeys();
+ }
+
+ $data['caption'] = "Config help" . ((count($params) == 1) ? " for $params[0]" : '');
+ $data['headline'] = array('Name', 'Type', 'Description');
+ $data['border'] = true;
+ foreach ($params as $name) {
+ $type = $this->config->getType($name);
+ $docs = $this->config->getDocs($name);
+ if ($type == 'set') {
+ $docs = rtrim($docs) . "\nValid set: " .
+ implode(' ', $this->config->getSetValues($name));
+ }
+
+ $data['data'][] = array($name, $type, $docs);
+ }
+
+ $this->ui->outputData($data, $command);
+ }
+
+ function doConfigCreate($command, $options, $params)
+ {
+ if (count($params) != 2) {
+ return PEAR::raiseError('config-create: must have 2 parameters, root path and ' .
+ 'filename to save as');
+ }
+
+ $root = $params[0];
+ // Clean up the DIRECTORY_SEPARATOR mess
+ $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
+ $root = preg_replace(array('!\\\\+!', '!/+!', "!$ds2+!"),
+ array('/', '/', '/'),
+ $root);
+ if ($root{0} != '/') {
+ if (!isset($options['windows'])) {
+ return PEAR::raiseError('Root directory must be an absolute path beginning ' .
+ 'with "/", was: "' . $root . '"');
+ }
+
+ if (!preg_match('/^[A-Za-z]:/', $root)) {
+ return PEAR::raiseError('Root directory must be an absolute path beginning ' .
+ 'with "\\" or "C:\\", was: "' . $root . '"');
+ }
+ }
+
+ $windows = isset($options['windows']);
+ if ($windows) {
+ $root = str_replace('/', '\\', $root);
+ }
+
+ if (!file_exists($params[1]) && !@touch($params[1])) {
+ return PEAR::raiseError('Could not create "' . $params[1] . '"');
+ }
+
+ $params[1] = realpath($params[1]);
+ $config = &new PEAR_Config($params[1], '#no#system#config#', false, false);
+ if ($root{strlen($root) - 1} == '/') {
+ $root = substr($root, 0, strlen($root) - 1);
+ }
+
+ $config->noRegistry();
+ $config->set('php_dir', $windows ? "$root\\pear\\php" : "$root/pear/php", 'user');
+ $config->set('data_dir', $windows ? "$root\\pear\\data" : "$root/pear/data");
+ $config->set('www_dir', $windows ? "$root\\pear\\www" : "$root/pear/www");
+ $config->set('cfg_dir', $windows ? "$root\\pear\\cfg" : "$root/pear/cfg");
+ $config->set('ext_dir', $windows ? "$root\\pear\\ext" : "$root/pear/ext");
+ $config->set('doc_dir', $windows ? "$root\\pear\\docs" : "$root/pear/docs");
+ $config->set('test_dir', $windows ? "$root\\pear\\tests" : "$root/pear/tests");
+ $config->set('cache_dir', $windows ? "$root\\pear\\cache" : "$root/pear/cache");
+ $config->set('download_dir', $windows ? "$root\\pear\\download" : "$root/pear/download");
+ $config->set('temp_dir', $windows ? "$root\\pear\\temp" : "$root/pear/temp");
+ $config->set('bin_dir', $windows ? "$root\\pear" : "$root/pear");
+ $config->writeConfigFile();
+ $this->_showConfig($config);
+ $this->ui->outputData('Successfully created default configuration file "' . $params[1] . '"',
+ $command);
+ }
+
+ function _showConfig(&$config)
+ {
+ $params = array('user');
+ $keys = $config->getKeys();
+ sort($keys);
+ $channel = 'pear.php.net';
+ $data = array('caption' => 'Configuration (channel ' . $channel . '):');
+ foreach ($keys as $key) {
+ $type = $config->getType($key);
+ $value = $config->get($key, 'user', $channel);
+ if ($type == 'password' && $value) {
+ $value = '********';
+ }
+
+ if ($value === false) {
+ $value = 'false';
+ } elseif ($value === true) {
+ $value = 'true';
+ }
+ $data['data'][$config->getGroup($key)][] =
+ array($config->getPrompt($key) , $key, $value);
+ }
+
+ foreach ($config->getLayers() as $layer) {
+ $data['data']['Config Files'][] =
+ array(ucfirst($layer) . ' Configuration File', 'Filename' ,
+ $config->getConfFile($layer));
+ }
+
+ $this->ui->outputData($data, 'config-show');
+ return true;
+ }
+
+ /**
+ * Checks if a layer is defined or not
+ *
+ * @param string $layer The layer to search for
+ * @return mixed False on no error or the error message
+ */
+ function _checkLayer($layer = null)
+ {
+ if (!empty($layer) && $layer != 'default') {
+ $layers = $this->config->getLayers();
+ if (!in_array($layer, $layers)) {
+ return " only the layers: \"" . implode('" or "', $layers) . "\" are supported";
+ }
+ }
+
+ return false;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Config.xml b/includes/pear/PEAR/Command/Config.xml
new file mode 100644
index 0000000..f64a925
--- /dev/null
+++ b/includes/pear/PEAR/Command/Config.xml
@@ -0,0 +1,92 @@
+<commands version="1.0">
+ <config-show>
+ <summary>Show All Settings</summary>
+ <function>doConfigShow</function>
+ <shortcut>csh</shortcut>
+ <options>
+ <channel>
+ <shortopt>c</shortopt>
+ <doc>show configuration variables for another channel</doc>
+ <arg>CHAN</arg>
+ </channel>
+ </options>
+ <doc>[layer]
+Displays all configuration values. An optional argument
+may be used to tell which configuration layer to display. Valid
+configuration layers are &quot;user&quot;, &quot;system&quot; and &quot;default&quot;. To display
+configurations for different channels, set the default_channel
+configuration variable and run config-show again.
+</doc>
+ </config-show>
+ <config-get>
+ <summary>Show One Setting</summary>
+ <function>doConfigGet</function>
+ <shortcut>cg</shortcut>
+ <options>
+ <channel>
+ <shortopt>c</shortopt>
+ <doc>show configuration variables for another channel</doc>
+ <arg>CHAN</arg>
+ </channel>
+ </options>
+ <doc>&lt;parameter&gt; [layer]
+Displays the value of one configuration parameter. The
+first argument is the name of the parameter, an optional second argument
+may be used to tell which configuration layer to look in. Valid configuration
+layers are &quot;user&quot;, &quot;system&quot; and &quot;default&quot;. If no layer is specified, a value
+will be picked from the first layer that defines the parameter, in the order
+just specified. The configuration value will be retrieved for the channel
+specified by the default_channel configuration variable.
+</doc>
+ </config-get>
+ <config-set>
+ <summary>Change Setting</summary>
+ <function>doConfigSet</function>
+ <shortcut>cs</shortcut>
+ <options>
+ <channel>
+ <shortopt>c</shortopt>
+ <doc>show configuration variables for another channel</doc>
+ <arg>CHAN</arg>
+ </channel>
+ </options>
+ <doc>&lt;parameter&gt; &lt;value&gt; [layer]
+Sets the value of one configuration parameter. The first argument is
+the name of the parameter, the second argument is the new value. Some
+parameters are subject to validation, and the command will fail with
+an error message if the new value does not make sense. An optional
+third argument may be used to specify in which layer to set the
+configuration parameter. The default layer is &quot;user&quot;. The
+configuration value will be set for the current channel, which
+is controlled by the default_channel configuration variable.
+</doc>
+ </config-set>
+ <config-help>
+ <summary>Show Information About Setting</summary>
+ <function>doConfigHelp</function>
+ <shortcut>ch</shortcut>
+ <options />
+ <doc>[parameter]
+Displays help for a configuration parameter. Without arguments it
+displays help for all configuration parameters.
+</doc>
+ </config-help>
+ <config-create>
+ <summary>Create a Default configuration file</summary>
+ <function>doConfigCreate</function>
+ <shortcut>coc</shortcut>
+ <options>
+ <windows>
+ <shortopt>w</shortopt>
+ <doc>create a config file for a windows install</doc>
+ </windows>
+ </options>
+ <doc>&lt;root path&gt; &lt;filename&gt;
+Create a default configuration file with all directory configuration
+variables set to subdirectories of &lt;root path&gt;, and save it as &lt;filename&gt;.
+This is useful especially for creating a configuration file for a remote
+PEAR installation (using the --remoteconfig option of install, upgrade,
+and uninstall).
+</doc>
+ </config-create>
+</commands> \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Install.php b/includes/pear/PEAR/Command/Install.php
new file mode 100644
index 0000000..79e68db
--- /dev/null
+++ b/includes/pear/PEAR/Command/Install.php
@@ -0,0 +1,1268 @@
+<?php
+/**
+ * PEAR_Command_Install (install, upgrade, upgrade-all, uninstall, bundle, run-scripts commands)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+/**
+ * PEAR commands for installation or deinstallation/upgrading of
+ * packages.
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ */
+class PEAR_Command_Install extends PEAR_Command_Common
+{
+ // {{{ properties
+
+ var $commands = array(
+ 'install' => array(
+ 'summary' => 'Install Package',
+ 'function' => 'doInstall',
+ 'shortcut' => 'i',
+ 'options' => array(
+ 'force' => array(
+ 'shortopt' => 'f',
+ 'doc' => 'will overwrite newer installed packages',
+ ),
+ 'loose' => array(
+ 'shortopt' => 'l',
+ 'doc' => 'do not check for recommended dependency version',
+ ),
+ 'nodeps' => array(
+ 'shortopt' => 'n',
+ 'doc' => 'ignore dependencies, install anyway',
+ ),
+ 'register-only' => array(
+ 'shortopt' => 'r',
+ 'doc' => 'do not install files, only register the package as installed',
+ ),
+ 'soft' => array(
+ 'shortopt' => 's',
+ 'doc' => 'soft install, fail silently, or upgrade if already installed',
+ ),
+ 'nobuild' => array(
+ 'shortopt' => 'B',
+ 'doc' => 'don\'t build C extensions',
+ ),
+ 'nocompress' => array(
+ 'shortopt' => 'Z',
+ 'doc' => 'request uncompressed files when downloading',
+ ),
+ 'installroot' => array(
+ 'shortopt' => 'R',
+ 'arg' => 'DIR',
+ 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT), use packagingroot for RPM',
+ ),
+ 'packagingroot' => array(
+ 'shortopt' => 'P',
+ 'arg' => 'DIR',
+ 'doc' => 'root directory used when packaging files, like RPM packaging',
+ ),
+ 'ignore-errors' => array(
+ 'doc' => 'force install even if there were errors',
+ ),
+ 'alldeps' => array(
+ 'shortopt' => 'a',
+ 'doc' => 'install all required and optional dependencies',
+ ),
+ 'onlyreqdeps' => array(
+ 'shortopt' => 'o',
+ 'doc' => 'install all required dependencies',
+ ),
+ 'offline' => array(
+ 'shortopt' => 'O',
+ 'doc' => 'do not attempt to download any urls or contact channels',
+ ),
+ 'pretend' => array(
+ 'shortopt' => 'p',
+ 'doc' => 'Only list the packages that would be downloaded',
+ ),
+ ),
+ 'doc' => '[channel/]<package> ...
+Installs one or more PEAR packages. You can specify a package to
+install in four ways:
+
+"Package-1.0.tgz" : installs from a local file
+
+"http://example.com/Package-1.0.tgz" : installs from
+anywhere on the net.
+
+"package.xml" : installs the package described in
+package.xml. Useful for testing, or for wrapping a PEAR package in
+another package manager such as RPM.
+
+"Package[-version/state][.tar]" : queries your default channel\'s server
+({config master_server}) and downloads the newest package with
+the preferred quality/state ({config preferred_state}).
+
+To retrieve Package version 1.1, use "Package-1.1," to retrieve
+Package state beta, use "Package-beta." To retrieve an uncompressed
+file, append .tar (make sure there is no file by the same name first)
+
+To download a package from another channel, prefix with the channel name like
+"channel/Package"
+
+More than one package may be specified at once. It is ok to mix these
+four ways of specifying packages.
+'),
+ 'upgrade' => array(
+ 'summary' => 'Upgrade Package',
+ 'function' => 'doInstall',
+ 'shortcut' => 'up',
+ 'options' => array(
+ 'channel' => array(
+ 'shortopt' => 'c',
+ 'doc' => 'upgrade packages from a specific channel',
+ 'arg' => 'CHAN',
+ ),
+ 'force' => array(
+ 'shortopt' => 'f',
+ 'doc' => 'overwrite newer installed packages',
+ ),
+ 'loose' => array(
+ 'shortopt' => 'l',
+ 'doc' => 'do not check for recommended dependency version',
+ ),
+ 'nodeps' => array(
+ 'shortopt' => 'n',
+ 'doc' => 'ignore dependencies, upgrade anyway',
+ ),
+ 'register-only' => array(
+ 'shortopt' => 'r',
+ 'doc' => 'do not install files, only register the package as upgraded',
+ ),
+ 'nobuild' => array(
+ 'shortopt' => 'B',
+ 'doc' => 'don\'t build C extensions',
+ ),
+ 'nocompress' => array(
+ 'shortopt' => 'Z',
+ 'doc' => 'request uncompressed files when downloading',
+ ),
+ 'installroot' => array(
+ 'shortopt' => 'R',
+ 'arg' => 'DIR',
+ 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)',
+ ),
+ 'ignore-errors' => array(
+ 'doc' => 'force install even if there were errors',
+ ),
+ 'alldeps' => array(
+ 'shortopt' => 'a',
+ 'doc' => 'install all required and optional dependencies',
+ ),
+ 'onlyreqdeps' => array(
+ 'shortopt' => 'o',
+ 'doc' => 'install all required dependencies',
+ ),
+ 'offline' => array(
+ 'shortopt' => 'O',
+ 'doc' => 'do not attempt to download any urls or contact channels',
+ ),
+ 'pretend' => array(
+ 'shortopt' => 'p',
+ 'doc' => 'Only list the packages that would be downloaded',
+ ),
+ ),
+ 'doc' => '<package> ...
+Upgrades one or more PEAR packages. See documentation for the
+"install" command for ways to specify a package.
+
+When upgrading, your package will be updated if the provided new
+package has a higher version number (use the -f option if you need to
+upgrade anyway).
+
+More than one package may be specified at once.
+'),
+ 'upgrade-all' => array(
+ 'summary' => 'Upgrade All Packages [Deprecated in favor of calling upgrade with no parameters]',
+ 'function' => 'doUpgradeAll',
+ 'shortcut' => 'ua',
+ 'options' => array(
+ 'channel' => array(
+ 'shortopt' => 'c',
+ 'doc' => 'upgrade packages from a specific channel',
+ 'arg' => 'CHAN',
+ ),
+ 'nodeps' => array(
+ 'shortopt' => 'n',
+ 'doc' => 'ignore dependencies, upgrade anyway',
+ ),
+ 'register-only' => array(
+ 'shortopt' => 'r',
+ 'doc' => 'do not install files, only register the package as upgraded',
+ ),
+ 'nobuild' => array(
+ 'shortopt' => 'B',
+ 'doc' => 'don\'t build C extensions',
+ ),
+ 'nocompress' => array(
+ 'shortopt' => 'Z',
+ 'doc' => 'request uncompressed files when downloading',
+ ),
+ 'installroot' => array(
+ 'shortopt' => 'R',
+ 'arg' => 'DIR',
+ 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT), use packagingroot for RPM',
+ ),
+ 'ignore-errors' => array(
+ 'doc' => 'force install even if there were errors',
+ ),
+ 'loose' => array(
+ 'doc' => 'do not check for recommended dependency version',
+ ),
+ ),
+ 'doc' => '
+WARNING: This function is deprecated in favor of using the upgrade command with no params
+
+Upgrades all packages that have a newer release available. Upgrades are
+done only if there is a release available of the state specified in
+"preferred_state" (currently {config preferred_state}), or a state considered
+more stable.
+'),
+ 'uninstall' => array(
+ 'summary' => 'Un-install Package',
+ 'function' => 'doUninstall',
+ 'shortcut' => 'un',
+ 'options' => array(
+ 'nodeps' => array(
+ 'shortopt' => 'n',
+ 'doc' => 'ignore dependencies, uninstall anyway',
+ ),
+ 'register-only' => array(
+ 'shortopt' => 'r',
+ 'doc' => 'do not remove files, only register the packages as not installed',
+ ),
+ 'installroot' => array(
+ 'shortopt' => 'R',
+ 'arg' => 'DIR',
+ 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)',
+ ),
+ 'ignore-errors' => array(
+ 'doc' => 'force install even if there were errors',
+ ),
+ 'offline' => array(
+ 'shortopt' => 'O',
+ 'doc' => 'do not attempt to uninstall remotely',
+ ),
+ ),
+ 'doc' => '[channel/]<package> ...
+Uninstalls one or more PEAR packages. More than one package may be
+specified at once. Prefix with channel name to uninstall from a
+channel not in your default channel ({config default_channel})
+'),
+ 'bundle' => array(
+ 'summary' => 'Unpacks a Pecl Package',
+ 'function' => 'doBundle',
+ 'shortcut' => 'bun',
+ 'options' => array(
+ 'destination' => array(
+ 'shortopt' => 'd',
+ 'arg' => 'DIR',
+ 'doc' => 'Optional destination directory for unpacking (defaults to current path or "ext" if exists)',
+ ),
+ 'force' => array(
+ 'shortopt' => 'f',
+ 'doc' => 'Force the unpacking even if there were errors in the package',
+ ),
+ ),
+ 'doc' => '<package>
+Unpacks a Pecl Package into the selected location. It will download the
+package if needed.
+'),
+ 'run-scripts' => array(
+ 'summary' => 'Run Post-Install Scripts bundled with a package',
+ 'function' => 'doRunScripts',
+ 'shortcut' => 'rs',
+ 'options' => array(
+ ),
+ 'doc' => '<package>
+Run post-installation scripts in package <package>, if any exist.
+'),
+ );
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * PEAR_Command_Install constructor.
+ *
+ * @access public
+ */
+ function PEAR_Command_Install(&$ui, &$config)
+ {
+ parent::PEAR_Command_Common($ui, $config);
+ }
+
+ // }}}
+
+ /**
+ * For unit testing purposes
+ */
+ function &getDownloader(&$ui, $options, &$config)
+ {
+ if (!class_exists('PEAR_Downloader')) {
+ require_once 'PEAR/Downloader.php';
+ }
+ $a = &new PEAR_Downloader($ui, $options, $config);
+ return $a;
+ }
+
+ /**
+ * For unit testing purposes
+ */
+ function &getInstaller(&$ui)
+ {
+ if (!class_exists('PEAR_Installer')) {
+ require_once 'PEAR/Installer.php';
+ }
+ $a = &new PEAR_Installer($ui);
+ return $a;
+ }
+
+ function enableExtension($binaries, $type)
+ {
+ if (!($phpini = $this->config->get('php_ini', null, 'pear.php.net'))) {
+ return PEAR::raiseError('configuration option "php_ini" is not set to php.ini location');
+ }
+ $ini = $this->_parseIni($phpini);
+ if (PEAR::isError($ini)) {
+ return $ini;
+ }
+ $line = 0;
+ if ($type == 'extsrc' || $type == 'extbin') {
+ $search = 'extensions';
+ $enable = 'extension';
+ } else {
+ $search = 'zend_extensions';
+ ob_start();
+ phpinfo(INFO_GENERAL);
+ $info = ob_get_contents();
+ ob_end_clean();
+ $debug = function_exists('leak') ? '_debug' : '';
+ $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : '';
+ $enable = 'zend_extension' . $debug . $ts;
+ }
+ foreach ($ini[$search] as $line => $extension) {
+ if (in_array($extension, $binaries, true) || in_array(
+ $ini['extension_dir'] . DIRECTORY_SEPARATOR . $extension, $binaries, true)) {
+ // already enabled - assume if one is, all are
+ return true;
+ }
+ }
+ if ($line) {
+ $newini = array_slice($ini['all'], 0, $line);
+ } else {
+ $newini = array();
+ }
+ foreach ($binaries as $binary) {
+ if (!$this->config->get('ext_dir') && $ini['extension_dir']) {
+ $binary = basename($binary);
+ }
+ $newini[] = $enable . '="' . $binary . '"' . (OS_UNIX ? "\n" : "\r\n");
+ }
+ $newini = array_merge($newini, array_slice($ini['all'], $line));
+ $fp = @fopen($phpini, 'wb');
+ if (!$fp) {
+ return PEAR::raiseError('cannot open php.ini "' . $phpini . '" for writing');
+ }
+ foreach ($newini as $line) {
+ fwrite($fp, $line);
+ }
+ fclose($fp);
+ return true;
+ }
+
+ function disableExtension($binaries, $type)
+ {
+ if (!($phpini = $this->config->get('php_ini', null, 'pear.php.net'))) {
+ return PEAR::raiseError('configuration option "php_ini" is not set to php.ini location');
+ }
+ $ini = $this->_parseIni($phpini);
+ if (PEAR::isError($ini)) {
+ return $ini;
+ }
+ $line = 0;
+ if ($type == 'extsrc' || $type == 'extbin') {
+ $search = 'extensions';
+ $enable = 'extension';
+ } else {
+ $search = 'zend_extensions';
+ ob_start();
+ phpinfo(INFO_GENERAL);
+ $info = ob_get_contents();
+ ob_end_clean();
+ $debug = function_exists('leak') ? '_debug' : '';
+ $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : '';
+ $enable = 'zend_extension' . $debug . $ts;
+ }
+ $found = false;
+ foreach ($ini[$search] as $line => $extension) {
+ if (in_array($extension, $binaries, true) || in_array(
+ $ini['extension_dir'] . DIRECTORY_SEPARATOR . $extension, $binaries, true)) {
+ $found = true;
+ break;
+ }
+ }
+ if (!$found) {
+ // not enabled
+ return true;
+ }
+ $fp = @fopen($phpini, 'wb');
+ if (!$fp) {
+ return PEAR::raiseError('cannot open php.ini "' . $phpini . '" for writing');
+ }
+ if ($line) {
+ $newini = array_slice($ini['all'], 0, $line);
+ // delete the enable line
+ $newini = array_merge($newini, array_slice($ini['all'], $line + 1));
+ } else {
+ $newini = array_slice($ini['all'], 1);
+ }
+ foreach ($newini as $line) {
+ fwrite($fp, $line);
+ }
+ fclose($fp);
+ return true;
+ }
+
+ function _parseIni($filename)
+ {
+ if (!file_exists($filename)) {
+ return PEAR::raiseError('php.ini "' . $filename . '" does not exist');
+ }
+
+ if (filesize($filename) > 300000) {
+ return PEAR::raiseError('php.ini "' . $filename . '" is too large, aborting');
+ }
+
+ ob_start();
+ phpinfo(INFO_GENERAL);
+ $info = ob_get_contents();
+ ob_end_clean();
+ $debug = function_exists('leak') ? '_debug' : '';
+ $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : '';
+ $zend_extension_line = 'zend_extension' . $debug . $ts;
+ $all = @file($filename);
+ if (!$all) {
+ return PEAR::raiseError('php.ini "' . $filename .'" could not be read');
+ }
+ $zend_extensions = $extensions = array();
+ // assume this is right, but pull from the php.ini if it is found
+ $extension_dir = ini_get('extension_dir');
+ foreach ($all as $linenum => $line) {
+ $line = trim($line);
+ if (!$line) {
+ continue;
+ }
+ if ($line[0] == ';') {
+ continue;
+ }
+ if (strtolower(substr($line, 0, 13)) == 'extension_dir') {
+ $line = trim(substr($line, 13));
+ if ($line[0] == '=') {
+ $x = trim(substr($line, 1));
+ $x = explode(';', $x);
+ $extension_dir = str_replace('"', '', array_shift($x));
+ continue;
+ }
+ }
+ if (strtolower(substr($line, 0, 9)) == 'extension') {
+ $line = trim(substr($line, 9));
+ if ($line[0] == '=') {
+ $x = trim(substr($line, 1));
+ $x = explode(';', $x);
+ $extensions[$linenum] = str_replace('"', '', array_shift($x));
+ continue;
+ }
+ }
+ if (strtolower(substr($line, 0, strlen($zend_extension_line))) ==
+ $zend_extension_line) {
+ $line = trim(substr($line, strlen($zend_extension_line)));
+ if ($line[0] == '=') {
+ $x = trim(substr($line, 1));
+ $x = explode(';', $x);
+ $zend_extensions[$linenum] = str_replace('"', '', array_shift($x));
+ continue;
+ }
+ }
+ }
+ return array(
+ 'extensions' => $extensions,
+ 'zend_extensions' => $zend_extensions,
+ 'extension_dir' => $extension_dir,
+ 'all' => $all,
+ );
+ }
+
+ // {{{ doInstall()
+
+ function doInstall($command, $options, $params)
+ {
+ if (!class_exists('PEAR_PackageFile')) {
+ require_once 'PEAR/PackageFile.php';
+ }
+
+ if (isset($options['installroot']) && isset($options['packagingroot'])) {
+ return $this->raiseError('ERROR: cannot use both --installroot and --packagingroot');
+ }
+
+ $reg = &$this->config->getRegistry();
+ $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel');
+ if (!$reg->channelExists($channel)) {
+ return $this->raiseError('Channel "' . $channel . '" does not exist');
+ }
+
+ if (empty($this->installer)) {
+ $this->installer = &$this->getInstaller($this->ui);
+ }
+
+ if ($command == 'upgrade' || $command == 'upgrade-all') {
+ // If people run the upgrade command but pass nothing, emulate a upgrade-all
+ if ($command == 'upgrade' && empty($params)) {
+ return $this->doUpgradeAll($command, $options, $params);
+ }
+ $options['upgrade'] = true;
+ } else {
+ $packages = $params;
+ }
+
+ $instreg = &$reg; // instreg used to check if package is installed
+ if (isset($options['packagingroot']) && !isset($options['upgrade'])) {
+ $packrootphp_dir = $this->installer->_prependPath(
+ $this->config->get('php_dir', null, 'pear.php.net'),
+ $options['packagingroot']);
+ $instreg = new PEAR_Registry($packrootphp_dir); // other instreg!
+
+ if ($this->config->get('verbose') > 2) {
+ $this->ui->outputData('using package root: ' . $options['packagingroot']);
+ }
+ }
+
+ $abstractpackages = $otherpackages = array();
+ // parse params
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+
+ foreach ($params as $param) {
+ if (strpos($param, 'http://') === 0) {
+ $otherpackages[] = $param;
+ continue;
+ }
+
+ if (strpos($param, 'channel://') === false && @file_exists($param)) {
+ if (isset($options['force'])) {
+ $otherpackages[] = $param;
+ continue;
+ }
+
+ $pkg = new PEAR_PackageFile($this->config);
+ $pf = $pkg->fromAnyFile($param, PEAR_VALIDATE_DOWNLOADING);
+ if (PEAR::isError($pf)) {
+ $otherpackages[] = $param;
+ continue;
+ }
+
+ $exists = $reg->packageExists($pf->getPackage(), $pf->getChannel());
+ $pversion = $reg->packageInfo($pf->getPackage(), 'version', $pf->getChannel());
+ $version_compare = version_compare($pf->getVersion(), $pversion, '<=');
+ if ($exists && $version_compare) {
+ if ($this->config->get('verbose')) {
+ $this->ui->outputData('Ignoring installed package ' .
+ $reg->parsedPackageNameToString(
+ array('package' => $pf->getPackage(),
+ 'channel' => $pf->getChannel()), true));
+ }
+ continue;
+ }
+ $otherpackages[] = $param;
+ continue;
+ }
+
+ $e = $reg->parsePackageName($param, $channel);
+ if (PEAR::isError($e)) {
+ $otherpackages[] = $param;
+ } else {
+ $abstractpackages[] = $e;
+ }
+ }
+ PEAR::staticPopErrorHandling();
+
+ // if there are any local package .tgz or remote static url, we can't
+ // filter. The filter only works for abstract packages
+ if (count($abstractpackages) && !isset($options['force'])) {
+ // when not being forced, only do necessary upgrades/installs
+ if (isset($options['upgrade'])) {
+ $abstractpackages = $this->_filterUptodatePackages($abstractpackages, $command);
+ } else {
+ $count = count($abstractpackages);
+ foreach ($abstractpackages as $i => $package) {
+ if (isset($package['group'])) {
+ // do not filter out install groups
+ continue;
+ }
+
+ if ($instreg->packageExists($package['package'], $package['channel'])) {
+ if ($count > 1) {
+ if ($this->config->get('verbose')) {
+ $this->ui->outputData('Ignoring installed package ' .
+ $reg->parsedPackageNameToString($package, true));
+ }
+ unset($abstractpackages[$i]);
+ } elseif ($count === 1) {
+ // Lets try to upgrade it since it's already installed
+ $options['upgrade'] = true;
+ }
+ }
+ }
+ }
+ $abstractpackages =
+ array_map(array($reg, 'parsedPackageNameToString'), $abstractpackages);
+ } elseif (count($abstractpackages)) {
+ $abstractpackages =
+ array_map(array($reg, 'parsedPackageNameToString'), $abstractpackages);
+ }
+
+ $packages = array_merge($abstractpackages, $otherpackages);
+ if (!count($packages)) {
+ $c = '';
+ if (isset($options['channel'])){
+ $c .= ' in channel "' . $options['channel'] . '"';
+ }
+ $this->ui->outputData('Nothing to ' . $command . $c);
+ return true;
+ }
+
+ $this->downloader = &$this->getDownloader($this->ui, $options, $this->config);
+ $errors = $downloaded = $binaries = array();
+ $downloaded = &$this->downloader->download($packages);
+ if (PEAR::isError($downloaded)) {
+ return $this->raiseError($downloaded);
+ }
+
+ $errors = $this->downloader->getErrorMsgs();
+ if (count($errors)) {
+ $err = array();
+ $err['data'] = array();
+ foreach ($errors as $error) {
+ if ($error !== null) {
+ $err['data'][] = array($error);
+ }
+ }
+
+ if (!empty($err['data'])) {
+ $err['headline'] = 'Install Errors';
+ $this->ui->outputData($err);
+ }
+
+ if (!count($downloaded)) {
+ return $this->raiseError("$command failed");
+ }
+ }
+
+ $data = array(
+ 'headline' => 'Packages that would be Installed'
+ );
+
+ if (isset($options['pretend'])) {
+ foreach ($downloaded as $package) {
+ $data['data'][] = array($reg->parsedPackageNameToString($package->getParsedPackage()));
+ }
+ $this->ui->outputData($data, 'pretend');
+ return true;
+ }
+
+ $this->installer->setOptions($options);
+ $this->installer->sortPackagesForInstall($downloaded);
+ if (PEAR::isError($err = $this->installer->setDownloadedPackages($downloaded))) {
+ $this->raiseError($err->getMessage());
+ return true;
+ }
+
+ $binaries = $extrainfo = array();
+ foreach ($downloaded as $param) {
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $info = $this->installer->install($param, $options);
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($info)) {
+ $oldinfo = $info;
+ $pkg = &$param->getPackageFile();
+ if ($info->getCode() != PEAR_INSTALLER_NOBINARY) {
+ if (!($info = $pkg->installBinary($this->installer))) {
+ $this->ui->outputData('ERROR: ' .$oldinfo->getMessage());
+ continue;
+ }
+
+ // we just installed a different package than requested,
+ // let's change the param and info so that the rest of this works
+ $param = $info[0];
+ $info = $info[1];
+ }
+ }
+
+ if (!is_array($info)) {
+ return $this->raiseError("$command failed");
+ }
+
+ if ($param->getPackageType() == 'extsrc' ||
+ $param->getPackageType() == 'extbin' ||
+ $param->getPackageType() == 'zendextsrc' ||
+ $param->getPackageType() == 'zendextbin'
+ ) {
+ $pkg = &$param->getPackageFile();
+ if ($instbin = $pkg->getInstalledBinary()) {
+ $instpkg = &$instreg->getPackage($instbin, $pkg->getChannel());
+ } else {
+ $instpkg = &$instreg->getPackage($pkg->getPackage(), $pkg->getChannel());
+ }
+
+ foreach ($instpkg->getFilelist() as $name => $atts) {
+ $pinfo = pathinfo($atts['installed_as']);
+ if (!isset($pinfo['extension']) ||
+ in_array($pinfo['extension'], array('c', 'h'))
+ ) {
+ continue; // make sure we don't match php_blah.h
+ }
+
+ if ((strpos($pinfo['basename'], 'php_') === 0 &&
+ $pinfo['extension'] == 'dll') ||
+ // most unices
+ $pinfo['extension'] == 'so' ||
+ // hp-ux
+ $pinfo['extension'] == 'sl') {
+ $binaries[] = array($atts['installed_as'], $pinfo);
+ break;
+ }
+ }
+
+ if (count($binaries)) {
+ foreach ($binaries as $pinfo) {
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $ret = $this->enableExtension(array($pinfo[0]), $param->getPackageType());
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($ret)) {
+ $extrainfo[] = $ret->getMessage();
+ if ($param->getPackageType() == 'extsrc' ||
+ $param->getPackageType() == 'extbin') {
+ $exttype = 'extension';
+ } else {
+ ob_start();
+ phpinfo(INFO_GENERAL);
+ $info = ob_get_contents();
+ ob_end_clean();
+ $debug = function_exists('leak') ? '_debug' : '';
+ $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : '';
+ $exttype = 'zend_extension' . $debug . $ts;
+ }
+ $extrainfo[] = 'You should add "' . $exttype . '=' .
+ $pinfo[1]['basename'] . '" to php.ini';
+ } else {
+ $extrainfo[] = 'Extension ' . $instpkg->getProvidesExtension() .
+ ' enabled in php.ini';
+ }
+ }
+ }
+ }
+
+ if ($this->config->get('verbose') > 0) {
+ $chan = $param->getChannel();
+ $label = $reg->parsedPackageNameToString(
+ array(
+ 'channel' => $chan,
+ 'package' => $param->getPackage(),
+ 'version' => $param->getVersion(),
+ ));
+ $out = array('data' => "$command ok: $label");
+ if (isset($info['release_warnings'])) {
+ $out['release_warnings'] = $info['release_warnings'];
+ }
+ $this->ui->outputData($out, $command);
+
+ if (!isset($options['register-only']) && !isset($options['offline'])) {
+ if ($this->config->isDefinedLayer('ftp')) {
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $info = $this->installer->ftpInstall($param);
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($info)) {
+ $this->ui->outputData($info->getMessage());
+ $this->ui->outputData("remote install failed: $label");
+ } else {
+ $this->ui->outputData("remote install ok: $label");
+ }
+ }
+ }
+ }
+
+ $deps = $param->getDeps();
+ if ($deps) {
+ if (isset($deps['group'])) {
+ $groups = $deps['group'];
+ if (!isset($groups[0])) {
+ $groups = array($groups);
+ }
+
+ foreach ($groups as $group) {
+ if ($group['attribs']['name'] == 'default') {
+ // default group is always installed, unless the user
+ // explicitly chooses to install another group
+ continue;
+ }
+ $extrainfo[] = $param->getPackage() . ': Optional feature ' .
+ $group['attribs']['name'] . ' available (' .
+ $group['attribs']['hint'] . ')';
+ }
+
+ $extrainfo[] = $param->getPackage() .
+ ': To install optional features use "pear install ' .
+ $reg->parsedPackageNameToString(
+ array('package' => $param->getPackage(),
+ 'channel' => $param->getChannel()), true) .
+ '#featurename"';
+ }
+ }
+
+ $pkg = &$instreg->getPackage($param->getPackage(), $param->getChannel());
+ // $pkg may be NULL if install is a 'fake' install via --packagingroot
+ if (is_object($pkg)) {
+ $pkg->setConfig($this->config);
+ if ($list = $pkg->listPostinstallScripts()) {
+ $pn = $reg->parsedPackageNameToString(array('channel' =>
+ $param->getChannel(), 'package' => $param->getPackage()), true);
+ $extrainfo[] = $pn . ' has post-install scripts:';
+ foreach ($list as $file) {
+ $extrainfo[] = $file;
+ }
+ $extrainfo[] = $param->getPackage() .
+ ': Use "pear run-scripts ' . $pn . '" to finish setup.';
+ $extrainfo[] = 'DO NOT RUN SCRIPTS FROM UNTRUSTED SOURCES';
+ }
+ }
+ }
+
+ if (count($extrainfo)) {
+ foreach ($extrainfo as $info) {
+ $this->ui->outputData($info);
+ }
+ }
+
+ return true;
+ }
+
+ // }}}
+ // {{{ doUpgradeAll()
+
+ function doUpgradeAll($command, $options, $params)
+ {
+ $reg = &$this->config->getRegistry();
+ $upgrade = array();
+
+ if (isset($options['channel'])) {
+ $channels = array($options['channel']);
+ } else {
+ $channels = $reg->listChannels();
+ }
+
+ foreach ($channels as $channel) {
+ if ($channel == '__uri') {
+ continue;
+ }
+
+ // parse name with channel
+ foreach ($reg->listPackages($channel) as $name) {
+ $upgrade[] = $reg->parsedPackageNameToString(array(
+ 'channel' => $channel,
+ 'package' => $name
+ ));
+ }
+ }
+
+ $err = $this->doInstall($command, $options, $upgrade);
+ if (PEAR::isError($err)) {
+ $this->ui->outputData($err->getMessage(), $command);
+ }
+ }
+
+ // }}}
+ // {{{ doUninstall()
+
+ function doUninstall($command, $options, $params)
+ {
+ if (count($params) < 1) {
+ return $this->raiseError("Please supply the package(s) you want to uninstall");
+ }
+
+ if (empty($this->installer)) {
+ $this->installer = &$this->getInstaller($this->ui);
+ }
+
+ if (isset($options['remoteconfig'])) {
+ $e = $this->config->readFTPConfigFile($options['remoteconfig']);
+ if (!PEAR::isError($e)) {
+ $this->installer->setConfig($this->config);
+ }
+ }
+
+ $reg = &$this->config->getRegistry();
+ $newparams = array();
+ $binaries = array();
+ $badparams = array();
+ foreach ($params as $pkg) {
+ $channel = $this->config->get('default_channel');
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $parsed = $reg->parsePackageName($pkg, $channel);
+ PEAR::staticPopErrorHandling();
+ if (!$parsed || PEAR::isError($parsed)) {
+ $badparams[] = $pkg;
+ continue;
+ }
+ $package = $parsed['package'];
+ $channel = $parsed['channel'];
+ $info = &$reg->getPackage($package, $channel);
+ if ($info === null &&
+ ($channel == 'pear.php.net' || $channel == 'pecl.php.net')) {
+ // make sure this isn't a package that has flipped from pear to pecl but
+ // used a package.xml 1.0
+ $testc = ($channel == 'pear.php.net') ? 'pecl.php.net' : 'pear.php.net';
+ $info = &$reg->getPackage($package, $testc);
+ if ($info !== null) {
+ $channel = $testc;
+ }
+ }
+ if ($info === null) {
+ $badparams[] = $pkg;
+ } else {
+ $newparams[] = &$info;
+ // check for binary packages (this is an alias for those packages if so)
+ if ($installedbinary = $info->getInstalledBinary()) {
+ $this->ui->log('adding binary package ' .
+ $reg->parsedPackageNameToString(array('channel' => $channel,
+ 'package' => $installedbinary), true));
+ $newparams[] = &$reg->getPackage($installedbinary, $channel);
+ }
+ // add the contents of a dependency group to the list of installed packages
+ if (isset($parsed['group'])) {
+ $group = $info->getDependencyGroup($parsed['group']);
+ if ($group) {
+ $installed = $reg->getInstalledGroup($group);
+ if ($installed) {
+ foreach ($installed as $i => $p) {
+ $newparams[] = &$installed[$i];
+ }
+ }
+ }
+ }
+ }
+ }
+ $err = $this->installer->sortPackagesForUninstall($newparams);
+ if (PEAR::isError($err)) {
+ $this->ui->outputData($err->getMessage(), $command);
+ return true;
+ }
+ $params = $newparams;
+ // twist this to use it to check on whether dependent packages are also being uninstalled
+ // for circular dependencies like subpackages
+ $this->installer->setUninstallPackages($newparams);
+ $params = array_merge($params, $badparams);
+ $binaries = array();
+ foreach ($params as $pkg) {
+ $this->installer->pushErrorHandling(PEAR_ERROR_RETURN);
+ if ($err = $this->installer->uninstall($pkg, $options)) {
+ $this->installer->popErrorHandling();
+ if (PEAR::isError($err)) {
+ $this->ui->outputData($err->getMessage(), $command);
+ continue;
+ }
+ if ($pkg->getPackageType() == 'extsrc' ||
+ $pkg->getPackageType() == 'extbin' ||
+ $pkg->getPackageType() == 'zendextsrc' ||
+ $pkg->getPackageType() == 'zendextbin') {
+ if ($instbin = $pkg->getInstalledBinary()) {
+ continue; // this will be uninstalled later
+ }
+
+ foreach ($pkg->getFilelist() as $name => $atts) {
+ $pinfo = pathinfo($atts['installed_as']);
+ if (!isset($pinfo['extension']) ||
+ in_array($pinfo['extension'], array('c', 'h'))) {
+ continue; // make sure we don't match php_blah.h
+ }
+ if ((strpos($pinfo['basename'], 'php_') === 0 &&
+ $pinfo['extension'] == 'dll') ||
+ // most unices
+ $pinfo['extension'] == 'so' ||
+ // hp-ux
+ $pinfo['extension'] == 'sl') {
+ $binaries[] = array($atts['installed_as'], $pinfo);
+ break;
+ }
+ }
+ if (count($binaries)) {
+ foreach ($binaries as $pinfo) {
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $ret = $this->disableExtension(array($pinfo[0]), $pkg->getPackageType());
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($ret)) {
+ $extrainfo[] = $ret->getMessage();
+ if ($pkg->getPackageType() == 'extsrc' ||
+ $pkg->getPackageType() == 'extbin') {
+ $exttype = 'extension';
+ } else {
+ ob_start();
+ phpinfo(INFO_GENERAL);
+ $info = ob_get_contents();
+ ob_end_clean();
+ $debug = function_exists('leak') ? '_debug' : '';
+ $ts = preg_match('/Thread Safety.+enabled/', $info) ? '_ts' : '';
+ $exttype = 'zend_extension' . $debug . $ts;
+ }
+ $this->ui->outputData('Unable to remove "' . $exttype . '=' .
+ $pinfo[1]['basename'] . '" from php.ini', $command);
+ } else {
+ $this->ui->outputData('Extension ' . $pkg->getProvidesExtension() .
+ ' disabled in php.ini', $command);
+ }
+ }
+ }
+ }
+ $savepkg = $pkg;
+ if ($this->config->get('verbose') > 0) {
+ if (is_object($pkg)) {
+ $pkg = $reg->parsedPackageNameToString($pkg);
+ }
+ $this->ui->outputData("uninstall ok: $pkg", $command);
+ }
+ if (!isset($options['offline']) && is_object($savepkg) &&
+ defined('PEAR_REMOTEINSTALL_OK')) {
+ if ($this->config->isDefinedLayer('ftp')) {
+ $this->installer->pushErrorHandling(PEAR_ERROR_RETURN);
+ $info = $this->installer->ftpUninstall($savepkg);
+ $this->installer->popErrorHandling();
+ if (PEAR::isError($info)) {
+ $this->ui->outputData($info->getMessage());
+ $this->ui->outputData("remote uninstall failed: $pkg");
+ } else {
+ $this->ui->outputData("remote uninstall ok: $pkg");
+ }
+ }
+ }
+ } else {
+ $this->installer->popErrorHandling();
+ if (!is_object($pkg)) {
+ return $this->raiseError("uninstall failed: $pkg");
+ }
+ $pkg = $reg->parsedPackageNameToString($pkg);
+ }
+ }
+
+ return true;
+ }
+
+ // }}}
+
+
+ // }}}
+ // {{{ doBundle()
+ /*
+ (cox) It just downloads and untars the package, does not do
+ any check that the PEAR_Installer::_installFile() does.
+ */
+
+ function doBundle($command, $options, $params)
+ {
+ $opts = array(
+ 'force' => true,
+ 'nodeps' => true,
+ 'soft' => true,
+ 'downloadonly' => true
+ );
+ $downloader = &$this->getDownloader($this->ui, $opts, $this->config);
+ $reg = &$this->config->getRegistry();
+ if (count($params) < 1) {
+ return $this->raiseError("Please supply the package you want to bundle");
+ }
+
+ if (isset($options['destination'])) {
+ if (!is_dir($options['destination'])) {
+ System::mkdir('-p ' . $options['destination']);
+ }
+ $dest = realpath($options['destination']);
+ } else {
+ $pwd = getcwd();
+ $dir = $pwd . DIRECTORY_SEPARATOR . 'ext';
+ $dest = is_dir($dir) ? $dir : $pwd;
+ }
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $err = $downloader->setDownloadDir($dest);
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($err)) {
+ return PEAR::raiseError('download directory "' . $dest .
+ '" is not writeable.');
+ }
+ $result = &$downloader->download(array($params[0]));
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ if (!isset($result[0])) {
+ return $this->raiseError('unable to unpack ' . $params[0]);
+ }
+ $pkgfile = &$result[0]->getPackageFile();
+ $pkgname = $pkgfile->getName();
+ $pkgversion = $pkgfile->getVersion();
+
+ // Unpacking -------------------------------------------------
+ $dest .= DIRECTORY_SEPARATOR . $pkgname;
+ $orig = $pkgname . '-' . $pkgversion;
+
+ $tar = &new Archive_Tar($pkgfile->getArchiveFile());
+ if (!$tar->extractModify($dest, $orig)) {
+ return $this->raiseError('unable to unpack ' . $pkgfile->getArchiveFile());
+ }
+ $this->ui->outputData("Package ready at '$dest'");
+ // }}}
+ }
+
+ // }}}
+
+ function doRunScripts($command, $options, $params)
+ {
+ if (!isset($params[0])) {
+ return $this->raiseError('run-scripts expects 1 parameter: a package name');
+ }
+
+ $reg = &$this->config->getRegistry();
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel'));
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($parsed)) {
+ return $this->raiseError($parsed);
+ }
+
+ $package = &$reg->getPackage($parsed['package'], $parsed['channel']);
+ if (!is_object($package)) {
+ return $this->raiseError('Could not retrieve package "' . $params[0] . '" from registry');
+ }
+
+ $package->setConfig($this->config);
+ $package->runPostinstallScripts();
+ $this->ui->outputData('Install scripts complete', $command);
+ return true;
+ }
+
+ /**
+ * Given a list of packages, filter out those ones that are already up to date
+ *
+ * @param $packages: packages, in parsed array format !
+ * @return list of packages that can be upgraded
+ */
+ function _filterUptodatePackages($packages, $command)
+ {
+ $reg = &$this->config->getRegistry();
+ $latestReleases = array();
+
+ $ret = array();
+ foreach ($packages as $package) {
+ if (isset($package['group'])) {
+ $ret[] = $package;
+ continue;
+ }
+
+ $channel = $package['channel'];
+ $name = $package['package'];
+ if (!$reg->packageExists($name, $channel)) {
+ $ret[] = $package;
+ continue;
+ }
+
+ if (!isset($latestReleases[$channel])) {
+ // fill in cache for this channel
+ $chan = &$reg->getChannel($channel);
+ if (PEAR::isError($chan)) {
+ return $this->raiseError($chan);
+ }
+
+ $base2 = false;
+ $preferred_mirror = $this->config->get('preferred_mirror', null, $channel);
+ if ($chan->supportsREST($preferred_mirror) &&
+ (
+ //($base2 = $chan->getBaseURL('REST1.4', $preferred_mirror)) ||
+ ($base = $chan->getBaseURL('REST1.0', $preferred_mirror))
+ )
+ ) {
+ $dorest = true;
+ }
+
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ if (!isset($package['state'])) {
+ $state = $this->config->get('preferred_state', null, $channel);
+ } else {
+ $state = $package['state'];
+ }
+
+ if ($dorest) {
+ if ($base2) {
+ $rest = &$this->config->getREST('1.4', array());
+ $base = $base2;
+ } else {
+ $rest = &$this->config->getREST('1.0', array());
+ }
+
+ $installed = array_flip($reg->listPackages($channel));
+ $latest = $rest->listLatestUpgrades($base, $state, $installed, $channel, $reg);
+ }
+
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($latest)) {
+ $this->ui->outputData('Error getting channel info from ' . $channel .
+ ': ' . $latest->getMessage());
+ continue;
+ }
+
+ $latestReleases[$channel] = array_change_key_case($latest);
+ }
+
+ // check package for latest release
+ $name_lower = strtolower($name);
+ if (isset($latestReleases[$channel][$name_lower])) {
+ // if not set, up to date
+ $inst_version = $reg->packageInfo($name, 'version', $channel);
+ $channel_version = $latestReleases[$channel][$name_lower]['version'];
+ if (version_compare($channel_version, $inst_version, 'le')) {
+ // installed version is up-to-date
+ continue;
+ }
+
+ // maintain BC
+ if ($command == 'upgrade-all') {
+ $this->ui->outputData(array('data' => 'Will upgrade ' .
+ $reg->parsedPackageNameToString($package)), $command);
+ }
+ $ret[] = $package;
+ }
+ }
+
+ return $ret;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Install.xml b/includes/pear/PEAR/Command/Install.xml
new file mode 100644
index 0000000..1b1e933
--- /dev/null
+++ b/includes/pear/PEAR/Command/Install.xml
@@ -0,0 +1,276 @@
+<commands version="1.0">
+ <install>
+ <summary>Install Package</summary>
+ <function>doInstall</function>
+ <shortcut>i</shortcut>
+ <options>
+ <force>
+ <shortopt>f</shortopt>
+ <doc>will overwrite newer installed packages</doc>
+ </force>
+ <loose>
+ <shortopt>l</shortopt>
+ <doc>do not check for recommended dependency version</doc>
+ </loose>
+ <nodeps>
+ <shortopt>n</shortopt>
+ <doc>ignore dependencies, install anyway</doc>
+ </nodeps>
+ <register-only>
+ <shortopt>r</shortopt>
+ <doc>do not install files, only register the package as installed</doc>
+ </register-only>
+ <soft>
+ <shortopt>s</shortopt>
+ <doc>soft install, fail silently, or upgrade if already installed</doc>
+ </soft>
+ <nobuild>
+ <shortopt>B</shortopt>
+ <doc>don&#039;t build C extensions</doc>
+ </nobuild>
+ <nocompress>
+ <shortopt>Z</shortopt>
+ <doc>request uncompressed files when downloading</doc>
+ </nocompress>
+ <installroot>
+ <shortopt>R</shortopt>
+ <doc>root directory used when installing files (ala PHP&#039;s INSTALL_ROOT), use packagingroot for RPM</doc>
+ <arg>DIR</arg>
+ </installroot>
+ <packagingroot>
+ <shortopt>P</shortopt>
+ <doc>root directory used when packaging files, like RPM packaging</doc>
+ <arg>DIR</arg>
+ </packagingroot>
+ <ignore-errors>
+ <shortopt></shortopt>
+ <doc>force install even if there were errors</doc>
+ </ignore-errors>
+ <alldeps>
+ <shortopt>a</shortopt>
+ <doc>install all required and optional dependencies</doc>
+ </alldeps>
+ <onlyreqdeps>
+ <shortopt>o</shortopt>
+ <doc>install all required dependencies</doc>
+ </onlyreqdeps>
+ <offline>
+ <shortopt>O</shortopt>
+ <doc>do not attempt to download any urls or contact channels</doc>
+ </offline>
+ <pretend>
+ <shortopt>p</shortopt>
+ <doc>Only list the packages that would be downloaded</doc>
+ </pretend>
+ </options>
+ <doc>[channel/]&lt;package&gt; ...
+Installs one or more PEAR packages. You can specify a package to
+install in four ways:
+
+&quot;Package-1.0.tgz&quot; : installs from a local file
+
+&quot;http://example.com/Package-1.0.tgz&quot; : installs from
+anywhere on the net.
+
+&quot;package.xml&quot; : installs the package described in
+package.xml. Useful for testing, or for wrapping a PEAR package in
+another package manager such as RPM.
+
+&quot;Package[-version/state][.tar]&quot; : queries your default channel&#039;s server
+({config master_server}) and downloads the newest package with
+the preferred quality/state ({config preferred_state}).
+
+To retrieve Package version 1.1, use &quot;Package-1.1,&quot; to retrieve
+Package state beta, use &quot;Package-beta.&quot; To retrieve an uncompressed
+file, append .tar (make sure there is no file by the same name first)
+
+To download a package from another channel, prefix with the channel name like
+&quot;channel/Package&quot;
+
+More than one package may be specified at once. It is ok to mix these
+four ways of specifying packages.
+</doc>
+ </install>
+ <upgrade>
+ <summary>Upgrade Package</summary>
+ <function>doInstall</function>
+ <shortcut>up</shortcut>
+ <options>
+ <channel>
+ <shortopt>c</shortopt>
+ <doc>upgrade packages from a specific channel</doc>
+ <arg>CHAN</arg>
+ </channel>
+ <force>
+ <shortopt>f</shortopt>
+ <doc>overwrite newer installed packages</doc>
+ </force>
+ <loose>
+ <shortopt>l</shortopt>
+ <doc>do not check for recommended dependency version</doc>
+ </loose>
+ <nodeps>
+ <shortopt>n</shortopt>
+ <doc>ignore dependencies, upgrade anyway</doc>
+ </nodeps>
+ <register-only>
+ <shortopt>r</shortopt>
+ <doc>do not install files, only register the package as upgraded</doc>
+ </register-only>
+ <nobuild>
+ <shortopt>B</shortopt>
+ <doc>don&#039;t build C extensions</doc>
+ </nobuild>
+ <nocompress>
+ <shortopt>Z</shortopt>
+ <doc>request uncompressed files when downloading</doc>
+ </nocompress>
+ <installroot>
+ <shortopt>R</shortopt>
+ <doc>root directory used when installing files (ala PHP&#039;s INSTALL_ROOT)</doc>
+ <arg>DIR</arg>
+ </installroot>
+ <ignore-errors>
+ <shortopt></shortopt>
+ <doc>force install even if there were errors</doc>
+ </ignore-errors>
+ <alldeps>
+ <shortopt>a</shortopt>
+ <doc>install all required and optional dependencies</doc>
+ </alldeps>
+ <onlyreqdeps>
+ <shortopt>o</shortopt>
+ <doc>install all required dependencies</doc>
+ </onlyreqdeps>
+ <offline>
+ <shortopt>O</shortopt>
+ <doc>do not attempt to download any urls or contact channels</doc>
+ </offline>
+ <pretend>
+ <shortopt>p</shortopt>
+ <doc>Only list the packages that would be downloaded</doc>
+ </pretend>
+ </options>
+ <doc>&lt;package&gt; ...
+Upgrades one or more PEAR packages. See documentation for the
+&quot;install&quot; command for ways to specify a package.
+
+When upgrading, your package will be updated if the provided new
+package has a higher version number (use the -f option if you need to
+upgrade anyway).
+
+More than one package may be specified at once.
+</doc>
+ </upgrade>
+ <upgrade-all>
+ <summary>Upgrade All Packages [Deprecated in favor of calling upgrade with no parameters]</summary>
+ <function>doUpgradeAll</function>
+ <shortcut>ua</shortcut>
+ <options>
+ <channel>
+ <shortopt>c</shortopt>
+ <doc>upgrade packages from a specific channel</doc>
+ <arg>CHAN</arg>
+ </channel>
+ <nodeps>
+ <shortopt>n</shortopt>
+ <doc>ignore dependencies, upgrade anyway</doc>
+ </nodeps>
+ <register-only>
+ <shortopt>r</shortopt>
+ <doc>do not install files, only register the package as upgraded</doc>
+ </register-only>
+ <nobuild>
+ <shortopt>B</shortopt>
+ <doc>don&#039;t build C extensions</doc>
+ </nobuild>
+ <nocompress>
+ <shortopt>Z</shortopt>
+ <doc>request uncompressed files when downloading</doc>
+ </nocompress>
+ <installroot>
+ <shortopt>R</shortopt>
+ <doc>root directory used when installing files (ala PHP&#039;s INSTALL_ROOT), use packagingroot for RPM</doc>
+ <arg>DIR</arg>
+ </installroot>
+ <ignore-errors>
+ <shortopt></shortopt>
+ <doc>force install even if there were errors</doc>
+ </ignore-errors>
+ <loose>
+ <shortopt></shortopt>
+ <doc>do not check for recommended dependency version</doc>
+ </loose>
+ </options>
+ <doc>
+WARNING: This function is deprecated in favor of using the upgrade command with no params
+
+Upgrades all packages that have a newer release available. Upgrades are
+done only if there is a release available of the state specified in
+&quot;preferred_state&quot; (currently {config preferred_state}), or a state considered
+more stable.
+</doc>
+ </upgrade-all>
+ <uninstall>
+ <summary>Un-install Package</summary>
+ <function>doUninstall</function>
+ <shortcut>un</shortcut>
+ <options>
+ <nodeps>
+ <shortopt>n</shortopt>
+ <doc>ignore dependencies, uninstall anyway</doc>
+ </nodeps>
+ <register-only>
+ <shortopt>r</shortopt>
+ <doc>do not remove files, only register the packages as not installed</doc>
+ </register-only>
+ <installroot>
+ <shortopt>R</shortopt>
+ <doc>root directory used when installing files (ala PHP&#039;s INSTALL_ROOT)</doc>
+ <arg>DIR</arg>
+ </installroot>
+ <ignore-errors>
+ <shortopt></shortopt>
+ <doc>force install even if there were errors</doc>
+ </ignore-errors>
+ <offline>
+ <shortopt>O</shortopt>
+ <doc>do not attempt to uninstall remotely</doc>
+ </offline>
+ </options>
+ <doc>[channel/]&lt;package&gt; ...
+Uninstalls one or more PEAR packages. More than one package may be
+specified at once. Prefix with channel name to uninstall from a
+channel not in your default channel ({config default_channel})
+</doc>
+ </uninstall>
+ <bundle>
+ <summary>Unpacks a Pecl Package</summary>
+ <function>doBundle</function>
+ <shortcut>bun</shortcut>
+ <options>
+ <destination>
+ <shortopt>d</shortopt>
+ <doc>Optional destination directory for unpacking (defaults to current path or &quot;ext&quot; if exists)</doc>
+ <arg>DIR</arg>
+ </destination>
+ <force>
+ <shortopt>f</shortopt>
+ <doc>Force the unpacking even if there were errors in the package</doc>
+ </force>
+ </options>
+ <doc>&lt;package&gt;
+Unpacks a Pecl Package into the selected location. It will download the
+package if needed.
+</doc>
+ </bundle>
+ <run-scripts>
+ <summary>Run Post-Install Scripts bundled with a package</summary>
+ <function>doRunScripts</function>
+ <shortcut>rs</shortcut>
+ <options />
+ <doc>&lt;package&gt;
+Run post-installation scripts in package &lt;package&gt;, if any exist.
+</doc>
+ </run-scripts>
+</commands> \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Mirror.php b/includes/pear/PEAR/Command/Mirror.php
new file mode 100644
index 0000000..4f646b2
--- /dev/null
+++ b/includes/pear/PEAR/Command/Mirror.php
@@ -0,0 +1,139 @@
+<?php
+/**
+ * PEAR_Command_Mirror (download-all command)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Alexander Merz <alexmerz@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.2.0
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+/**
+ * PEAR commands for providing file mirrors
+ *
+ * @category pear
+ * @package PEAR
+ * @author Alexander Merz <alexmerz@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.2.0
+ */
+class PEAR_Command_Mirror extends PEAR_Command_Common
+{
+ var $commands = array(
+ 'download-all' => array(
+ 'summary' => 'Downloads each available package from the default channel',
+ 'function' => 'doDownloadAll',
+ 'shortcut' => 'da',
+ 'options' => array(
+ 'channel' =>
+ array(
+ 'shortopt' => 'c',
+ 'doc' => 'specify a channel other than the default channel',
+ 'arg' => 'CHAN',
+ ),
+ ),
+ 'doc' => '
+Requests a list of available packages from the default channel ({config default_channel})
+and downloads them to current working directory. Note: only
+packages within preferred_state ({config preferred_state}) will be downloaded'
+ ),
+ );
+
+ /**
+ * PEAR_Command_Mirror constructor.
+ *
+ * @access public
+ * @param object PEAR_Frontend a reference to an frontend
+ * @param object PEAR_Config a reference to the configuration data
+ */
+ function PEAR_Command_Mirror(&$ui, &$config)
+ {
+ parent::PEAR_Command_Common($ui, $config);
+ }
+
+ /**
+ * For unit-testing
+ */
+ function &factory($a)
+ {
+ $a = &PEAR_Command::factory($a, $this->config);
+ return $a;
+ }
+
+ /**
+ * retrieves a list of avaible Packages from master server
+ * and downloads them
+ *
+ * @access public
+ * @param string $command the command
+ * @param array $options the command options before the command
+ * @param array $params the stuff after the command name
+ * @return bool true if succesful
+ * @throw PEAR_Error
+ */
+ function doDownloadAll($command, $options, $params)
+ {
+ $savechannel = $this->config->get('default_channel');
+ $reg = &$this->config->getRegistry();
+ $channel = isset($options['channel']) ? $options['channel'] :
+ $this->config->get('default_channel');
+ if (!$reg->channelExists($channel)) {
+ $this->config->set('default_channel', $savechannel);
+ return $this->raiseError('Channel "' . $channel . '" does not exist');
+ }
+ $this->config->set('default_channel', $channel);
+
+ $this->ui->outputData('Using Channel ' . $this->config->get('default_channel'));
+ $chan = $reg->getChannel($channel);
+ if (PEAR::isError($chan)) {
+ return $this->raiseError($chan);
+ }
+
+ if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
+ $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
+ $rest = &$this->config->getREST('1.0', array());
+ $remoteInfo = array_flip($rest->listPackages($base, $channel));
+ }
+
+ if (PEAR::isError($remoteInfo)) {
+ return $remoteInfo;
+ }
+
+ $cmd = &$this->factory("download");
+ if (PEAR::isError($cmd)) {
+ return $cmd;
+ }
+
+ $this->ui->outputData('Using Preferred State of ' .
+ $this->config->get('preferred_state'));
+ $this->ui->outputData('Gathering release information, please wait...');
+
+ /**
+ * Error handling not necessary, because already done by
+ * the download command
+ */
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $err = $cmd->run('download', array('downloadonly' => true), array_keys($remoteInfo));
+ PEAR::staticPopErrorHandling();
+ $this->config->set('default_channel', $savechannel);
+ if (PEAR::isError($err)) {
+ $this->ui->outputData($err->getMessage());
+ }
+
+ return true;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Mirror.xml b/includes/pear/PEAR/Command/Mirror.xml
new file mode 100644
index 0000000..fe8be9d
--- /dev/null
+++ b/includes/pear/PEAR/Command/Mirror.xml
@@ -0,0 +1,18 @@
+<commands version="1.0">
+ <download-all>
+ <summary>Downloads each available package from the default channel</summary>
+ <function>doDownloadAll</function>
+ <shortcut>da</shortcut>
+ <options>
+ <channel>
+ <shortopt>c</shortopt>
+ <doc>specify a channel other than the default channel</doc>
+ <arg>CHAN</arg>
+ </channel>
+ </options>
+ <doc>
+Requests a list of available packages from the default channel ({config default_channel})
+and downloads them to current working directory. Note: only
+packages within preferred_state ({config preferred_state}) will be downloaded</doc>
+ </download-all>
+</commands> \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Package.php b/includes/pear/PEAR/Command/Package.php
new file mode 100644
index 0000000..44c647b
--- /dev/null
+++ b/includes/pear/PEAR/Command/Package.php
@@ -0,0 +1,1124 @@
+<?php
+/**
+ * PEAR_Command_Package (package, package-validate, cvsdiff, cvstag, package-dependencies,
+ * sign, makerpm, convert commands)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Martin Jansen <mj@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+/**
+ * PEAR commands for login/logout
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Martin Jansen <mj@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ */
+
+class PEAR_Command_Package extends PEAR_Command_Common
+{
+ var $commands = array(
+ 'package' => array(
+ 'summary' => 'Build Package',
+ 'function' => 'doPackage',
+ 'shortcut' => 'p',
+ 'options' => array(
+ 'nocompress' => array(
+ 'shortopt' => 'Z',
+ 'doc' => 'Do not gzip the package file'
+ ),
+ 'showname' => array(
+ 'shortopt' => 'n',
+ 'doc' => 'Print the name of the packaged file.',
+ ),
+ ),
+ 'doc' => '[descfile] [descfile2]
+Creates a PEAR package from its description file (usually called
+package.xml). If a second packagefile is passed in, then
+the packager will check to make sure that one is a package.xml
+version 1.0, and the other is a package.xml version 2.0. The
+package.xml version 1.0 will be saved as "package.xml" in the archive,
+and the other as "package2.xml" in the archive"
+'
+ ),
+ 'package-validate' => array(
+ 'summary' => 'Validate Package Consistency',
+ 'function' => 'doPackageValidate',
+ 'shortcut' => 'pv',
+ 'options' => array(),
+ 'doc' => '
+',
+ ),
+ 'cvsdiff' => array(
+ 'summary' => 'Run a "cvs diff" for all files in a package',
+ 'function' => 'doCvsDiff',
+ 'shortcut' => 'cd',
+ 'options' => array(
+ 'quiet' => array(
+ 'shortopt' => 'q',
+ 'doc' => 'Be quiet',
+ ),
+ 'reallyquiet' => array(
+ 'shortopt' => 'Q',
+ 'doc' => 'Be really quiet',
+ ),
+ 'date' => array(
+ 'shortopt' => 'D',
+ 'doc' => 'Diff against revision of DATE',
+ 'arg' => 'DATE',
+ ),
+ 'release' => array(
+ 'shortopt' => 'R',
+ 'doc' => 'Diff against tag for package release REL',
+ 'arg' => 'REL',
+ ),
+ 'revision' => array(
+ 'shortopt' => 'r',
+ 'doc' => 'Diff against revision REV',
+ 'arg' => 'REV',
+ ),
+ 'context' => array(
+ 'shortopt' => 'c',
+ 'doc' => 'Generate context diff',
+ ),
+ 'unified' => array(
+ 'shortopt' => 'u',
+ 'doc' => 'Generate unified diff',
+ ),
+ 'ignore-case' => array(
+ 'shortopt' => 'i',
+ 'doc' => 'Ignore case, consider upper- and lower-case letters equivalent',
+ ),
+ 'ignore-whitespace' => array(
+ 'shortopt' => 'b',
+ 'doc' => 'Ignore changes in amount of white space',
+ ),
+ 'ignore-blank-lines' => array(
+ 'shortopt' => 'B',
+ 'doc' => 'Ignore changes that insert or delete blank lines',
+ ),
+ 'brief' => array(
+ 'doc' => 'Report only whether the files differ, no details',
+ ),
+ 'dry-run' => array(
+ 'shortopt' => 'n',
+ 'doc' => 'Don\'t do anything, just pretend',
+ ),
+ ),
+ 'doc' => '<package.xml>
+Compares all the files in a package. Without any options, this
+command will compare the current code with the last checked-in code.
+Using the -r or -R option you may compare the current code with that
+of a specific release.
+',
+ ),
+ 'svntag' => array(
+ 'summary' => 'Set SVN Release Tag',
+ 'function' => 'doSvnTag',
+ 'shortcut' => 'sv',
+ 'options' => array(
+ 'quiet' => array(
+ 'shortopt' => 'q',
+ 'doc' => 'Be quiet',
+ ),
+ 'slide' => array(
+ 'shortopt' => 'F',
+ 'doc' => 'Move (slide) tag if it exists',
+ ),
+ 'delete' => array(
+ 'shortopt' => 'd',
+ 'doc' => 'Remove tag',
+ ),
+ 'dry-run' => array(
+ 'shortopt' => 'n',
+ 'doc' => 'Don\'t do anything, just pretend',
+ ),
+ ),
+ 'doc' => '<package.xml> [files...]
+ Sets a SVN tag on all files in a package. Use this command after you have
+ packaged a distribution tarball with the "package" command to tag what
+ revisions of what files were in that release. If need to fix something
+ after running svntag once, but before the tarball is released to the public,
+ use the "slide" option to move the release tag.
+
+ to include files (such as a second package.xml, or tests not included in the
+ release), pass them as additional parameters.
+ ',
+ ),
+ 'cvstag' => array(
+ 'summary' => 'Set CVS Release Tag',
+ 'function' => 'doCvsTag',
+ 'shortcut' => 'ct',
+ 'options' => array(
+ 'quiet' => array(
+ 'shortopt' => 'q',
+ 'doc' => 'Be quiet',
+ ),
+ 'reallyquiet' => array(
+ 'shortopt' => 'Q',
+ 'doc' => 'Be really quiet',
+ ),
+ 'slide' => array(
+ 'shortopt' => 'F',
+ 'doc' => 'Move (slide) tag if it exists',
+ ),
+ 'delete' => array(
+ 'shortopt' => 'd',
+ 'doc' => 'Remove tag',
+ ),
+ 'dry-run' => array(
+ 'shortopt' => 'n',
+ 'doc' => 'Don\'t do anything, just pretend',
+ ),
+ ),
+ 'doc' => '<package.xml> [files...]
+Sets a CVS tag on all files in a package. Use this command after you have
+packaged a distribution tarball with the "package" command to tag what
+revisions of what files were in that release. If need to fix something
+after running cvstag once, but before the tarball is released to the public,
+use the "slide" option to move the release tag.
+
+to include files (such as a second package.xml, or tests not included in the
+release), pass them as additional parameters.
+',
+ ),
+ 'package-dependencies' => array(
+ 'summary' => 'Show package dependencies',
+ 'function' => 'doPackageDependencies',
+ 'shortcut' => 'pd',
+ 'options' => array(),
+ 'doc' => '<package-file> or <package.xml> or <install-package-name>
+List all dependencies the package has.
+Can take a tgz / tar file, package.xml or a package name of an installed package.'
+ ),
+ 'sign' => array(
+ 'summary' => 'Sign a package distribution file',
+ 'function' => 'doSign',
+ 'shortcut' => 'si',
+ 'options' => array(
+ 'verbose' => array(
+ 'shortopt' => 'v',
+ 'doc' => 'Display GnuPG output',
+ ),
+ ),
+ 'doc' => '<package-file>
+Signs a package distribution (.tar or .tgz) file with GnuPG.',
+ ),
+ 'makerpm' => array(
+ 'summary' => 'Builds an RPM spec file from a PEAR package',
+ 'function' => 'doMakeRPM',
+ 'shortcut' => 'rpm',
+ 'options' => array(
+ 'spec-template' => array(
+ 'shortopt' => 't',
+ 'arg' => 'FILE',
+ 'doc' => 'Use FILE as RPM spec file template'
+ ),
+ 'rpm-pkgname' => array(
+ 'shortopt' => 'p',
+ 'arg' => 'FORMAT',
+ 'doc' => 'Use FORMAT as format string for RPM package name, %s is replaced
+by the PEAR package name, defaults to "PEAR::%s".',
+ ),
+ ),
+ 'doc' => '<package-file>
+
+Creates an RPM .spec file for wrapping a PEAR package inside an RPM
+package. Intended to be used from the SPECS directory, with the PEAR
+package tarball in the SOURCES directory:
+
+$ pear makerpm ../SOURCES/Net_Socket-1.0.tgz
+Wrote RPM spec file PEAR::Net_Geo-1.0.spec
+$ rpm -bb PEAR::Net_Socket-1.0.spec
+...
+Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm
+',
+ ),
+ 'convert' => array(
+ 'summary' => 'Convert a package.xml 1.0 to package.xml 2.0 format',
+ 'function' => 'doConvert',
+ 'shortcut' => 'c2',
+ 'options' => array(
+ 'flat' => array(
+ 'shortopt' => 'f',
+ 'doc' => 'do not beautify the filelist.',
+ ),
+ ),
+ 'doc' => '[descfile] [descfile2]
+Converts a package.xml in 1.0 format into a package.xml
+in 2.0 format. The new file will be named package2.xml by default,
+and package.xml will be used as the old file by default.
+This is not the most intelligent conversion, and should only be
+used for automated conversion or learning the format.
+'
+ ),
+ );
+
+ var $output;
+
+ /**
+ * PEAR_Command_Package constructor.
+ *
+ * @access public
+ */
+ function PEAR_Command_Package(&$ui, &$config)
+ {
+ parent::PEAR_Command_Common($ui, $config);
+ }
+
+ function _displayValidationResults($err, $warn, $strict = false)
+ {
+ foreach ($err as $e) {
+ $this->output .= "Error: $e\n";
+ }
+ foreach ($warn as $w) {
+ $this->output .= "Warning: $w\n";
+ }
+ $this->output .= sprintf('Validation: %d error(s), %d warning(s)'."\n",
+ sizeof($err), sizeof($warn));
+ if ($strict && count($err) > 0) {
+ $this->output .= "Fix these errors and try again.";
+ return false;
+ }
+ return true;
+ }
+
+ function &getPackager()
+ {
+ if (!class_exists('PEAR_Packager')) {
+ require_once 'PEAR/Packager.php';
+ }
+ $a = &new PEAR_Packager;
+ return $a;
+ }
+
+ function &getPackageFile($config, $debug = false)
+ {
+ if (!class_exists('PEAR_Common')) {
+ require_once 'PEAR/Common.php';
+ }
+ if (!class_exists('PEAR_PackageFile')) {
+ require_once 'PEAR/PackageFile.php';
+ }
+ $a = &new PEAR_PackageFile($config, $debug);
+ $common = new PEAR_Common;
+ $common->ui = $this->ui;
+ $a->setLogger($common);
+ return $a;
+ }
+
+ function doPackage($command, $options, $params)
+ {
+ $this->output = '';
+ $pkginfofile = isset($params[0]) ? $params[0] : 'package.xml';
+ $pkg2 = isset($params[1]) ? $params[1] : null;
+ if (!$pkg2 && !isset($params[0]) && file_exists('package2.xml')) {
+ $pkg2 = 'package2.xml';
+ }
+
+ $packager = &$this->getPackager();
+ $compress = empty($options['nocompress']) ? true : false;
+ $result = $packager->package($pkginfofile, $compress, $pkg2);
+ if (PEAR::isError($result)) {
+ return $this->raiseError($result);
+ }
+
+ // Don't want output, only the package file name just created
+ if (isset($options['showname'])) {
+ $this->output = $result;
+ }
+
+ if ($this->output) {
+ $this->ui->outputData($this->output, $command);
+ }
+
+ return true;
+ }
+
+ function doPackageValidate($command, $options, $params)
+ {
+ $this->output = '';
+ if (count($params) < 1) {
+ $params[0] = 'package.xml';
+ }
+
+ $obj = &$this->getPackageFile($this->config, $this->_debug);
+ $obj->rawReturn();
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL);
+ if (PEAR::isError($info)) {
+ $info = $obj->fromPackageFile($params[0], PEAR_VALIDATE_NORMAL);
+ } else {
+ $archive = $info->getArchiveFile();
+ $tar = &new Archive_Tar($archive);
+ $tar->extract(dirname($info->getPackageFile()));
+ $info->setPackageFile(dirname($info->getPackageFile()) . DIRECTORY_SEPARATOR .
+ $info->getPackage() . '-' . $info->getVersion() . DIRECTORY_SEPARATOR .
+ basename($info->getPackageFile()));
+ }
+
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($info)) {
+ return $this->raiseError($info);
+ }
+
+ $valid = false;
+ if ($info->getPackagexmlVersion() == '2.0') {
+ if ($valid = $info->validate(PEAR_VALIDATE_NORMAL)) {
+ $info->flattenFileList();
+ $valid = $info->validate(PEAR_VALIDATE_PACKAGING);
+ }
+ } else {
+ $valid = $info->validate(PEAR_VALIDATE_PACKAGING);
+ }
+
+ $err = $warn = array();
+ if ($errors = $info->getValidationWarnings()) {
+ foreach ($errors as $error) {
+ if ($error['level'] == 'warning') {
+ $warn[] = $error['message'];
+ } else {
+ $err[] = $error['message'];
+ }
+ }
+ }
+
+ $this->_displayValidationResults($err, $warn);
+ $this->ui->outputData($this->output, $command);
+ return true;
+ }
+
+ function doSvnTag($command, $options, $params)
+ {
+ $this->output = '';
+ $_cmd = $command;
+ if (count($params) < 1) {
+ $help = $this->getHelp($command);
+ return $this->raiseError("$command: missing parameter: $help[0]");
+ }
+
+ $packageFile = realpath($params[0]);
+ $dir = dirname($packageFile);
+ $dir = substr($dir, strrpos($dir, DIRECTORY_SEPARATOR) + 1);
+ $obj = &$this->getPackageFile($this->config, $this->_debug);
+ $info = $obj->fromAnyFile($packageFile, PEAR_VALIDATE_NORMAL);
+ if (PEAR::isError($info)) {
+ return $this->raiseError($info);
+ }
+
+ $err = $warn = array();
+ if (!$info->validate()) {
+ foreach ($info->getValidationWarnings() as $error) {
+ if ($error['level'] == 'warning') {
+ $warn[] = $error['message'];
+ } else {
+ $err[] = $error['message'];
+ }
+ }
+ }
+
+ if (!$this->_displayValidationResults($err, $warn, true)) {
+ $this->ui->outputData($this->output, $command);
+ return $this->raiseError('SVN tag failed');
+ }
+
+ $version = $info->getVersion();
+ $package = $info->getName();
+ $svntag = "$package-$version";
+
+ if (isset($options['delete'])) {
+ return $this->_svnRemoveTag($version, $package, $svntag, $packageFile, $options);
+ }
+
+ $path = $this->_svnFindPath($packageFile);
+
+ // Check if there are any modified files
+ $fp = popen('svn st --xml ' . dirname($packageFile), "r");
+ $out = '';
+ while ($line = fgets($fp, 1024)) {
+ $out .= rtrim($line)."\n";
+ }
+ pclose($fp);
+
+ if (!isset($options['quiet']) && strpos($out, 'item="modified"')) {
+ $params = array(array(
+ 'name' => 'modified',
+ 'type' => 'yesno',
+ 'default' => 'no',
+ 'prompt' => 'You have files in your SVN checkout (' . $path['from'] . ') that have been modified but not commited, do you still want to tag ' . $version . '?',
+ ));
+ $answers = $this->ui->confirmDialog($params);
+
+ if (!in_array($answers['modified'], array('y', 'yes', 'on', '1'))) {
+ return true;
+ }
+ }
+
+ if (isset($options['slide'])) {
+ $this->_svnRemoveTag($version, $package, $svntag, $packageFile, $options);
+ }
+
+ // Check if tag already exists
+ $releaseTag = $path['local']['base'] . 'tags' . DIRECTORY_SEPARATOR . $svntag;
+ $existsCommand = 'svn ls ' . $path['base'] . 'tags/';
+
+ $fp = popen($existsCommand, "r");
+ $out = '';
+ while ($line = fgets($fp, 1024)) {
+ $out .= rtrim($line)."\n";
+ }
+ pclose($fp);
+
+ if (in_array($svntag . DIRECTORY_SEPARATOR, explode("\n", $out))) {
+ $this->ui->outputData($this->output, $command);
+ return $this->raiseError('SVN tag ' . $svntag . ' for ' . $package . ' already exists.');
+ } elseif (file_exists($path['local']['base'] . 'tags') === false) {
+ return $this->raiseError('Can not locate the tags directory at ' . $path['local']['base'] . 'tags');
+ } elseif (is_writeable($path['local']['base'] . 'tags') === false) {
+ return $this->raiseError('Can not write to the tag directory at ' . $path['local']['base'] . 'tags');
+ } else {
+ $makeCommand = 'svn mkdir ' . $releaseTag;
+ $this->output .= "+ $makeCommand\n";
+ if (empty($options['dry-run'])) {
+ // We need to create the tag dir.
+ $fp = popen($makeCommand, "r");
+ $out = '';
+ while ($line = fgets($fp, 1024)) {
+ $out .= rtrim($line)."\n";
+ }
+ pclose($fp);
+ $this->output .= "$out\n";
+ }
+ }
+
+ $command = 'svn';
+ if (isset($options['quiet'])) {
+ $command .= ' -q';
+ }
+
+ $command .= ' copy --parents ';
+
+ $dir = dirname($packageFile);
+ $dir = substr($dir, strrpos($dir, DIRECTORY_SEPARATOR) + 1);
+ $files = array_keys($info->getFilelist());
+ if (!in_array(basename($packageFile), $files)) {
+ $files[] = basename($packageFile);
+ }
+
+ array_shift($params);
+ if (count($params)) {
+ // add in additional files to be tagged (package files and such)
+ $files = array_merge($files, $params);
+ }
+
+ $commands = array();
+ foreach ($files as $file) {
+ if (!file_exists($file)) {
+ $file = $dir . DIRECTORY_SEPARATOR . $file;
+ }
+ $commands[] = $command . ' ' . escapeshellarg($file) . ' ' .
+ escapeshellarg($releaseTag . DIRECTORY_SEPARATOR . $file);
+ }
+
+ $this->output .= implode("\n", $commands) . "\n";
+ if (empty($options['dry-run'])) {
+ foreach ($commands as $command) {
+ $fp = popen($command, "r");
+ while ($line = fgets($fp, 1024)) {
+ $this->output .= rtrim($line)."\n";
+ }
+ pclose($fp);
+ }
+ }
+
+ $command = 'svn ci -m "Tagging the ' . $version . ' release" ' . $releaseTag . "\n";
+ $this->output .= "+ $command\n";
+ if (empty($options['dry-run'])) {
+ $fp = popen($command, "r");
+ while ($line = fgets($fp, 1024)) {
+ $this->output .= rtrim($line)."\n";
+ }
+ pclose($fp);
+ }
+
+ $this->ui->outputData($this->output, $_cmd);
+ return true;
+ }
+
+ function _svnFindPath($file)
+ {
+ $xml = '';
+ $command = "svn info --xml $file";
+ $fp = popen($command, "r");
+ while ($line = fgets($fp, 1024)) {
+ $xml .= rtrim($line)."\n";
+ }
+ pclose($fp);
+ $url_tag = strpos($xml, '<url>');
+ $url = substr($xml, $url_tag + 5, strpos($xml, '</url>', $url_tag + 5) - ($url_tag + 5));
+
+ $path = array();
+ $path['from'] = substr($url, 0, strrpos($url, '/'));
+ $path['base'] = substr($path['from'], 0, strrpos($path['from'], '/') + 1);
+
+ // Figure out the local paths - see http://pear.php.net/bugs/17463
+ $pos = strpos($file, DIRECTORY_SEPARATOR . 'trunk' . DIRECTORY_SEPARATOR);
+ if ($pos === false) {
+ $pos = strpos($file, DIRECTORY_SEPARATOR . 'branches' . DIRECTORY_SEPARATOR);
+ }
+ $path['local']['base'] = substr($file, 0, $pos + 1);
+
+ return $path;
+ }
+
+ function _svnRemoveTag($version, $package, $tag, $packageFile, $options)
+ {
+ $command = 'svn';
+
+ if (isset($options['quiet'])) {
+ $command .= ' -q';
+ }
+
+ $command .= ' remove';
+ $command .= ' -m "Removing tag for the ' . $version . ' release."';
+
+ $path = $this->_svnFindPath($packageFile);
+ $command .= ' ' . $path['base'] . 'tags/' . $tag;
+
+
+ if ($this->config->get('verbose') > 1) {
+ $this->output .= "+ $command\n";
+ }
+
+ $this->output .= "+ $command\n";
+ if (empty($options['dry-run'])) {
+ $fp = popen($command, "r");
+ while ($line = fgets($fp, 1024)) {
+ $this->output .= rtrim($line)."\n";
+ }
+ pclose($fp);
+ }
+
+ $this->ui->outputData($this->output, $command);
+ return true;
+ }
+
+ function doCvsTag($command, $options, $params)
+ {
+ $this->output = '';
+ $_cmd = $command;
+ if (count($params) < 1) {
+ $help = $this->getHelp($command);
+ return $this->raiseError("$command: missing parameter: $help[0]");
+ }
+
+ $packageFile = realpath($params[0]);
+ $obj = &$this->getPackageFile($this->config, $this->_debug);
+ $info = $obj->fromAnyFile($packageFile, PEAR_VALIDATE_NORMAL);
+ if (PEAR::isError($info)) {
+ return $this->raiseError($info);
+ }
+
+ $err = $warn = array();
+ if (!$info->validate()) {
+ foreach ($info->getValidationWarnings() as $error) {
+ if ($error['level'] == 'warning') {
+ $warn[] = $error['message'];
+ } else {
+ $err[] = $error['message'];
+ }
+ }
+ }
+
+ if (!$this->_displayValidationResults($err, $warn, true)) {
+ $this->ui->outputData($this->output, $command);
+ return $this->raiseError('CVS tag failed');
+ }
+
+ $version = $info->getVersion();
+ $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $version);
+ $cvstag = "RELEASE_$cvsversion";
+ $files = array_keys($info->getFilelist());
+ $command = 'cvs';
+ if (isset($options['quiet'])) {
+ $command .= ' -q';
+ }
+
+ if (isset($options['reallyquiet'])) {
+ $command .= ' -Q';
+ }
+
+ $command .= ' tag';
+ if (isset($options['slide'])) {
+ $command .= ' -F';
+ }
+
+ if (isset($options['delete'])) {
+ $command .= ' -d';
+ }
+
+ $command .= ' ' . $cvstag . ' ' . escapeshellarg($params[0]);
+ array_shift($params);
+ if (count($params)) {
+ // add in additional files to be tagged
+ $files = array_merge($files, $params);
+ }
+
+ $dir = dirname($packageFile);
+ $dir = substr($dir, strrpos($dir, '/') + 1);
+ foreach ($files as $file) {
+ if (!file_exists($file)) {
+ $file = $dir . DIRECTORY_SEPARATOR . $file;
+ }
+ $command .= ' ' . escapeshellarg($file);
+ }
+
+ if ($this->config->get('verbose') > 1) {
+ $this->output .= "+ $command\n";
+ }
+
+ $this->output .= "+ $command\n";
+ if (empty($options['dry-run'])) {
+ $fp = popen($command, "r");
+ while ($line = fgets($fp, 1024)) {
+ $this->output .= rtrim($line)."\n";
+ }
+ pclose($fp);
+ }
+
+ $this->ui->outputData($this->output, $_cmd);
+ return true;
+ }
+
+ function doCvsDiff($command, $options, $params)
+ {
+ $this->output = '';
+ if (sizeof($params) < 1) {
+ $help = $this->getHelp($command);
+ return $this->raiseError("$command: missing parameter: $help[0]");
+ }
+
+ $file = realpath($params[0]);
+ $obj = &$this->getPackageFile($this->config, $this->_debug);
+ $info = $obj->fromAnyFile($file, PEAR_VALIDATE_NORMAL);
+ if (PEAR::isError($info)) {
+ return $this->raiseError($info);
+ }
+
+ $err = $warn = array();
+ if (!$info->validate()) {
+ foreach ($info->getValidationWarnings() as $error) {
+ if ($error['level'] == 'warning') {
+ $warn[] = $error['message'];
+ } else {
+ $err[] = $error['message'];
+ }
+ }
+ }
+
+ if (!$this->_displayValidationResults($err, $warn, true)) {
+ $this->ui->outputData($this->output, $command);
+ return $this->raiseError('CVS diff failed');
+ }
+
+ $info1 = $info->getFilelist();
+ $files = $info1;
+ $cmd = "cvs";
+ if (isset($options['quiet'])) {
+ $cmd .= ' -q';
+ unset($options['quiet']);
+ }
+
+ if (isset($options['reallyquiet'])) {
+ $cmd .= ' -Q';
+ unset($options['reallyquiet']);
+ }
+
+ if (isset($options['release'])) {
+ $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $options['release']);
+ $cvstag = "RELEASE_$cvsversion";
+ $options['revision'] = $cvstag;
+ unset($options['release']);
+ }
+
+ $execute = true;
+ if (isset($options['dry-run'])) {
+ $execute = false;
+ unset($options['dry-run']);
+ }
+
+ $cmd .= ' diff';
+ // the rest of the options are passed right on to "cvs diff"
+ foreach ($options as $option => $optarg) {
+ $arg = $short = false;
+ if (isset($this->commands[$command]['options'][$option])) {
+ $arg = $this->commands[$command]['options'][$option]['arg'];
+ $short = $this->commands[$command]['options'][$option]['shortopt'];
+ }
+ $cmd .= $short ? " -$short" : " --$option";
+ if ($arg && $optarg) {
+ $cmd .= ($short ? '' : '=') . escapeshellarg($optarg);
+ }
+ }
+
+ foreach ($files as $file) {
+ $cmd .= ' ' . escapeshellarg($file['name']);
+ }
+
+ if ($this->config->get('verbose') > 1) {
+ $this->output .= "+ $cmd\n";
+ }
+
+ if ($execute) {
+ $fp = popen($cmd, "r");
+ while ($line = fgets($fp, 1024)) {
+ $this->output .= rtrim($line)."\n";
+ }
+ pclose($fp);
+ }
+
+ $this->ui->outputData($this->output, $command);
+ return true;
+ }
+
+ function doPackageDependencies($command, $options, $params)
+ {
+ // $params[0] -> the PEAR package to list its information
+ if (count($params) !== 1) {
+ return $this->raiseError("bad parameter(s), try \"help $command\"");
+ }
+
+ $obj = &$this->getPackageFile($this->config, $this->_debug);
+ if (is_file($params[0]) || strpos($params[0], '.xml') > 0) {
+ $info = $obj->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL);
+ } else {
+ $reg = $this->config->getRegistry();
+ $info = $obj->fromArray($reg->packageInfo($params[0]));
+ }
+
+ if (PEAR::isError($info)) {
+ return $this->raiseError($info);
+ }
+
+ $deps = $info->getDeps();
+ if (is_array($deps)) {
+ if ($info->getPackagexmlVersion() == '1.0') {
+ $data = array(
+ 'caption' => 'Dependencies for pear/' . $info->getPackage(),
+ 'border' => true,
+ 'headline' => array("Required?", "Type", "Name", "Relation", "Version"),
+ );
+
+ foreach ($deps as $d) {
+ if (isset($d['optional'])) {
+ if ($d['optional'] == 'yes') {
+ $req = 'No';
+ } else {
+ $req = 'Yes';
+ }
+ } else {
+ $req = 'Yes';
+ }
+
+ if (isset($this->_deps_rel_trans[$d['rel']])) {
+ $rel = $this->_deps_rel_trans[$d['rel']];
+ } else {
+ $rel = $d['rel'];
+ }
+
+ if (isset($this->_deps_type_trans[$d['type']])) {
+ $type = ucfirst($this->_deps_type_trans[$d['type']]);
+ } else {
+ $type = $d['type'];
+ }
+
+ if (isset($d['name'])) {
+ $name = $d['name'];
+ } else {
+ $name = '';
+ }
+
+ if (isset($d['version'])) {
+ $version = $d['version'];
+ } else {
+ $version = '';
+ }
+
+ $data['data'][] = array($req, $type, $name, $rel, $version);
+ }
+ } else { // package.xml 2.0 dependencies display
+ require_once 'PEAR/Dependency2.php';
+ $deps = $info->getDependencies();
+ $reg = &$this->config->getRegistry();
+ if (is_array($deps)) {
+ $d = new PEAR_Dependency2($this->config, array(), '');
+ $data = array(
+ 'caption' => 'Dependencies for ' . $info->getPackage(),
+ 'border' => true,
+ 'headline' => array("Required?", "Type", "Name", 'Versioning', 'Group'),
+ );
+ foreach ($deps as $type => $subd) {
+ $req = ($type == 'required') ? 'Yes' : 'No';
+ if ($type == 'group') {
+ $group = $subd['attribs']['name'];
+ } else {
+ $group = '';
+ }
+
+ if (!isset($subd[0])) {
+ $subd = array($subd);
+ }
+
+ foreach ($subd as $groupa) {
+ foreach ($groupa as $deptype => $depinfo) {
+ if ($deptype == 'attribs') {
+ continue;
+ }
+
+ if ($deptype == 'pearinstaller') {
+ $deptype = 'pear Installer';
+ }
+
+ if (!isset($depinfo[0])) {
+ $depinfo = array($depinfo);
+ }
+
+ foreach ($depinfo as $inf) {
+ $name = '';
+ if (isset($inf['channel'])) {
+ $alias = $reg->channelAlias($inf['channel']);
+ if (!$alias) {
+ $alias = '(channel?) ' .$inf['channel'];
+ }
+ $name = $alias . '/';
+
+ }
+ if (isset($inf['name'])) {
+ $name .= $inf['name'];
+ } elseif (isset($inf['pattern'])) {
+ $name .= $inf['pattern'];
+ } else {
+ $name .= '';
+ }
+
+ if (isset($inf['uri'])) {
+ $name .= ' [' . $inf['uri'] . ']';
+ }
+
+ if (isset($inf['conflicts'])) {
+ $ver = 'conflicts';
+ } else {
+ $ver = $d->_getExtraString($inf);
+ }
+
+ $data['data'][] = array($req, ucfirst($deptype), $name,
+ $ver, $group);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ $this->ui->outputData($data, $command);
+ return true;
+ }
+
+ // Fallback
+ $this->ui->outputData("This package does not have any dependencies.", $command);
+ }
+
+ function doSign($command, $options, $params)
+ {
+ // should move most of this code into PEAR_Packager
+ // so it'll be easy to implement "pear package --sign"
+ if (count($params) !== 1) {
+ return $this->raiseError("bad parameter(s), try \"help $command\"");
+ }
+
+ require_once 'System.php';
+ require_once 'Archive/Tar.php';
+
+ if (!file_exists($params[0])) {
+ return $this->raiseError("file does not exist: $params[0]");
+ }
+
+ $obj = $this->getPackageFile($this->config, $this->_debug);
+ $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL);
+ if (PEAR::isError($info)) {
+ return $this->raiseError($info);
+ }
+
+ $tar = new Archive_Tar($params[0]);
+
+ $tmpdir = $this->config->get('temp_dir');
+ $tmpdir = System::mktemp(' -t "' . $tmpdir . '" -d pearsign');
+ if (!$tar->extractList('package2.xml package.xml package.sig', $tmpdir)) {
+ return $this->raiseError("failed to extract tar file");
+ }
+
+ if (file_exists("$tmpdir/package.sig")) {
+ return $this->raiseError("package already signed");
+ }
+
+ $packagexml = 'package.xml';
+ if (file_exists("$tmpdir/package2.xml")) {
+ $packagexml = 'package2.xml';
+ }
+
+ if (file_exists("$tmpdir/package.sig")) {
+ unlink("$tmpdir/package.sig");
+ }
+
+ if (!file_exists("$tmpdir/$packagexml")) {
+ return $this->raiseError("Extracted file $tmpdir/$packagexml not found.");
+ }
+
+ $input = $this->ui->userDialog($command,
+ array('GnuPG Passphrase'),
+ array('password'));
+ if (!isset($input[0])) {
+ //use empty passphrase
+ $input[0] = '';
+ }
+
+ $devnull = (isset($options['verbose'])) ? '' : ' 2>/dev/null';
+ $gpg = popen("gpg --batch --passphrase-fd 0 --armor --detach-sign --output $tmpdir/package.sig $tmpdir/$packagexml" . $devnull, "w");
+ if (!$gpg) {
+ return $this->raiseError("gpg command failed");
+ }
+
+ fwrite($gpg, "$input[0]\n");
+ if (pclose($gpg) || !file_exists("$tmpdir/package.sig")) {
+ return $this->raiseError("gpg sign failed");
+ }
+
+ if (!$tar->addModify("$tmpdir/package.sig", '', $tmpdir)) {
+ return $this->raiseError('failed adding signature to file');
+ }
+
+ $this->ui->outputData("Package signed.", $command);
+ return true;
+ }
+
+ /**
+ * For unit testing purposes
+ */
+ function &getInstaller(&$ui)
+ {
+ if (!class_exists('PEAR_Installer')) {
+ require_once 'PEAR/Installer.php';
+ }
+ $a = &new PEAR_Installer($ui);
+ return $a;
+ }
+
+ /**
+ * For unit testing purposes
+ */
+ function &getCommandPackaging(&$ui, &$config)
+ {
+ if (!class_exists('PEAR_Command_Packaging')) {
+ if ($fp = @fopen('PEAR/Command/Packaging.php', 'r', true)) {
+ fclose($fp);
+ include_once 'PEAR/Command/Packaging.php';
+ }
+ }
+
+ if (class_exists('PEAR_Command_Packaging')) {
+ $a = &new PEAR_Command_Packaging($ui, $config);
+ } else {
+ $a = null;
+ }
+
+ return $a;
+ }
+
+ function doMakeRPM($command, $options, $params)
+ {
+
+ // Check to see if PEAR_Command_Packaging is installed, and
+ // transparently switch to use the "make-rpm-spec" command from it
+ // instead, if it does. Otherwise, continue to use the old version
+ // of "makerpm" supplied with this package (PEAR).
+ $packaging_cmd = $this->getCommandPackaging($this->ui, $this->config);
+ if ($packaging_cmd !== null) {
+ $this->ui->outputData('PEAR_Command_Packaging is installed; using '.
+ 'newer "make-rpm-spec" command instead');
+ return $packaging_cmd->run('make-rpm-spec', $options, $params);
+ }
+
+ $this->ui->outputData('WARNING: "pear makerpm" is no longer available; an '.
+ 'improved version is available via "pear make-rpm-spec", which '.
+ 'is available by installing PEAR_Command_Packaging');
+ return true;
+ }
+
+ function doConvert($command, $options, $params)
+ {
+ $packagexml = isset($params[0]) ? $params[0] : 'package.xml';
+ $newpackagexml = isset($params[1]) ? $params[1] : dirname($packagexml) .
+ DIRECTORY_SEPARATOR . 'package2.xml';
+ $pkg = &$this->getPackageFile($this->config, $this->_debug);
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $pf = $pkg->fromPackageFile($packagexml, PEAR_VALIDATE_NORMAL);
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($pf)) {
+ if (is_array($pf->getUserInfo())) {
+ foreach ($pf->getUserInfo() as $warning) {
+ $this->ui->outputData($warning['message']);
+ }
+ }
+ return $this->raiseError($pf);
+ }
+
+ if (is_a($pf, 'PEAR_PackageFile_v2')) {
+ $this->ui->outputData($packagexml . ' is already a package.xml version 2.0');
+ return true;
+ }
+
+ $gen = &$pf->getDefaultGenerator();
+ $newpf = &$gen->toV2();
+ $newpf->setPackagefile($newpackagexml);
+ $gen = &$newpf->getDefaultGenerator();
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $state = (isset($options['flat']) ? PEAR_VALIDATE_PACKAGING : PEAR_VALIDATE_NORMAL);
+ $saved = $gen->toPackageFile(dirname($newpackagexml), $state, basename($newpackagexml));
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($saved)) {
+ if (is_array($saved->getUserInfo())) {
+ foreach ($saved->getUserInfo() as $warning) {
+ $this->ui->outputData($warning['message']);
+ }
+ }
+
+ $this->ui->outputData($saved->getMessage());
+ return true;
+ }
+
+ $this->ui->outputData('Wrote new version 2.0 package.xml to "' . $saved . '"');
+ return true;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Package.xml b/includes/pear/PEAR/Command/Package.xml
new file mode 100644
index 0000000..d1aff9d
--- /dev/null
+++ b/includes/pear/PEAR/Command/Package.xml
@@ -0,0 +1,237 @@
+<commands version="1.0">
+ <package>
+ <summary>Build Package</summary>
+ <function>doPackage</function>
+ <shortcut>p</shortcut>
+ <options>
+ <nocompress>
+ <shortopt>Z</shortopt>
+ <doc>Do not gzip the package file</doc>
+ </nocompress>
+ <showname>
+ <shortopt>n</shortopt>
+ <doc>Print the name of the packaged file.</doc>
+ </showname>
+ </options>
+ <doc>[descfile] [descfile2]
+Creates a PEAR package from its description file (usually called
+package.xml). If a second packagefile is passed in, then
+the packager will check to make sure that one is a package.xml
+version 1.0, and the other is a package.xml version 2.0. The
+package.xml version 1.0 will be saved as &quot;package.xml&quot; in the archive,
+and the other as &quot;package2.xml&quot; in the archive&quot;
+</doc>
+ </package>
+ <package-validate>
+ <summary>Validate Package Consistency</summary>
+ <function>doPackageValidate</function>
+ <shortcut>pv</shortcut>
+ <options />
+ <doc>
+</doc>
+ </package-validate>
+ <cvsdiff>
+ <summary>Run a &quot;cvs diff&quot; for all files in a package</summary>
+ <function>doCvsDiff</function>
+ <shortcut>cd</shortcut>
+ <options>
+ <quiet>
+ <shortopt>q</shortopt>
+ <doc>Be quiet</doc>
+ </quiet>
+ <reallyquiet>
+ <shortopt>Q</shortopt>
+ <doc>Be really quiet</doc>
+ </reallyquiet>
+ <date>
+ <shortopt>D</shortopt>
+ <doc>Diff against revision of DATE</doc>
+ <arg>DATE</arg>
+ </date>
+ <release>
+ <shortopt>R</shortopt>
+ <doc>Diff against tag for package release REL</doc>
+ <arg>REL</arg>
+ </release>
+ <revision>
+ <shortopt>r</shortopt>
+ <doc>Diff against revision REV</doc>
+ <arg>REV</arg>
+ </revision>
+ <context>
+ <shortopt>c</shortopt>
+ <doc>Generate context diff</doc>
+ </context>
+ <unified>
+ <shortopt>u</shortopt>
+ <doc>Generate unified diff</doc>
+ </unified>
+ <ignore-case>
+ <shortopt>i</shortopt>
+ <doc>Ignore case, consider upper- and lower-case letters equivalent</doc>
+ </ignore-case>
+ <ignore-whitespace>
+ <shortopt>b</shortopt>
+ <doc>Ignore changes in amount of white space</doc>
+ </ignore-whitespace>
+ <ignore-blank-lines>
+ <shortopt>B</shortopt>
+ <doc>Ignore changes that insert or delete blank lines</doc>
+ </ignore-blank-lines>
+ <brief>
+ <shortopt></shortopt>
+ <doc>Report only whether the files differ, no details</doc>
+ </brief>
+ <dry-run>
+ <shortopt>n</shortopt>
+ <doc>Don&#039;t do anything, just pretend</doc>
+ </dry-run>
+ </options>
+ <doc>&lt;package.xml&gt;
+Compares all the files in a package. Without any options, this
+command will compare the current code with the last checked-in code.
+Using the -r or -R option you may compare the current code with that
+of a specific release.
+</doc>
+ </cvsdiff>
+ <svntag>
+ <summary>Set SVN Release Tag</summary>
+ <function>doSvnTag</function>
+ <shortcut>sv</shortcut>
+ <options>
+ <quiet>
+ <shortopt>q</shortopt>
+ <doc>Be quiet</doc>
+ </quiet>
+ <slide>
+ <shortopt>F</shortopt>
+ <doc>Move (slide) tag if it exists</doc>
+ </slide>
+ <delete>
+ <shortopt>d</shortopt>
+ <doc>Remove tag</doc>
+ </delete>
+ <dry-run>
+ <shortopt>n</shortopt>
+ <doc>Don&#039;t do anything, just pretend</doc>
+ </dry-run>
+ </options>
+ <doc>&lt;package.xml&gt; [files...]
+ Sets a SVN tag on all files in a package. Use this command after you have
+ packaged a distribution tarball with the &quot;package&quot; command to tag what
+ revisions of what files were in that release. If need to fix something
+ after running svntag once, but before the tarball is released to the public,
+ use the &quot;slide&quot; option to move the release tag.
+
+ to include files (such as a second package.xml, or tests not included in the
+ release), pass them as additional parameters.
+ </doc>
+ </svntag>
+ <cvstag>
+ <summary>Set CVS Release Tag</summary>
+ <function>doCvsTag</function>
+ <shortcut>ct</shortcut>
+ <options>
+ <quiet>
+ <shortopt>q</shortopt>
+ <doc>Be quiet</doc>
+ </quiet>
+ <reallyquiet>
+ <shortopt>Q</shortopt>
+ <doc>Be really quiet</doc>
+ </reallyquiet>
+ <slide>
+ <shortopt>F</shortopt>
+ <doc>Move (slide) tag if it exists</doc>
+ </slide>
+ <delete>
+ <shortopt>d</shortopt>
+ <doc>Remove tag</doc>
+ </delete>
+ <dry-run>
+ <shortopt>n</shortopt>
+ <doc>Don&#039;t do anything, just pretend</doc>
+ </dry-run>
+ </options>
+ <doc>&lt;package.xml&gt; [files...]
+Sets a CVS tag on all files in a package. Use this command after you have
+packaged a distribution tarball with the &quot;package&quot; command to tag what
+revisions of what files were in that release. If need to fix something
+after running cvstag once, but before the tarball is released to the public,
+use the &quot;slide&quot; option to move the release tag.
+
+to include files (such as a second package.xml, or tests not included in the
+release), pass them as additional parameters.
+</doc>
+ </cvstag>
+ <package-dependencies>
+ <summary>Show package dependencies</summary>
+ <function>doPackageDependencies</function>
+ <shortcut>pd</shortcut>
+ <options />
+ <doc>&lt;package-file&gt; or &lt;package.xml&gt; or &lt;install-package-name&gt;
+List all dependencies the package has.
+Can take a tgz / tar file, package.xml or a package name of an installed package.</doc>
+ </package-dependencies>
+ <sign>
+ <summary>Sign a package distribution file</summary>
+ <function>doSign</function>
+ <shortcut>si</shortcut>
+ <options>
+ <verbose>
+ <shortopt>v</shortopt>
+ <doc>Display GnuPG output</doc>
+ </verbose>
+ </options>
+ <doc>&lt;package-file&gt;
+Signs a package distribution (.tar or .tgz) file with GnuPG.</doc>
+ </sign>
+ <makerpm>
+ <summary>Builds an RPM spec file from a PEAR package</summary>
+ <function>doMakeRPM</function>
+ <shortcut>rpm</shortcut>
+ <options>
+ <spec-template>
+ <shortopt>t</shortopt>
+ <doc>Use FILE as RPM spec file template</doc>
+ <arg>FILE</arg>
+ </spec-template>
+ <rpm-pkgname>
+ <shortopt>p</shortopt>
+ <doc>Use FORMAT as format string for RPM package name, %s is replaced
+by the PEAR package name, defaults to &quot;PEAR::%s&quot;.</doc>
+ <arg>FORMAT</arg>
+ </rpm-pkgname>
+ </options>
+ <doc>&lt;package-file&gt;
+
+Creates an RPM .spec file for wrapping a PEAR package inside an RPM
+package. Intended to be used from the SPECS directory, with the PEAR
+package tarball in the SOURCES directory:
+
+$ pear makerpm ../SOURCES/Net_Socket-1.0.tgz
+Wrote RPM spec file PEAR::Net_Geo-1.0.spec
+$ rpm -bb PEAR::Net_Socket-1.0.spec
+...
+Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm
+</doc>
+ </makerpm>
+ <convert>
+ <summary>Convert a package.xml 1.0 to package.xml 2.0 format</summary>
+ <function>doConvert</function>
+ <shortcut>c2</shortcut>
+ <options>
+ <flat>
+ <shortopt>f</shortopt>
+ <doc>do not beautify the filelist.</doc>
+ </flat>
+ </options>
+ <doc>[descfile] [descfile2]
+Converts a package.xml in 1.0 format into a package.xml
+in 2.0 format. The new file will be named package2.xml by default,
+and package.xml will be used as the old file by default.
+This is not the most intelligent conversion, and should only be
+used for automated conversion or learning the format.
+</doc>
+ </convert>
+</commands> \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Pickle.php b/includes/pear/PEAR/Command/Pickle.php
new file mode 100644
index 0000000..7fe8da7
--- /dev/null
+++ b/includes/pear/PEAR/Command/Pickle.php
@@ -0,0 +1,421 @@
+<?php
+/**
+ * PEAR_Command_Pickle (pickle command)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 2005-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+/**
+ * PEAR commands for login/logout
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 2005-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.1
+ */
+
+class PEAR_Command_Pickle extends PEAR_Command_Common
+{
+ var $commands = array(
+ 'pickle' => array(
+ 'summary' => 'Build PECL Package',
+ 'function' => 'doPackage',
+ 'shortcut' => 'pi',
+ 'options' => array(
+ 'nocompress' => array(
+ 'shortopt' => 'Z',
+ 'doc' => 'Do not gzip the package file'
+ ),
+ 'showname' => array(
+ 'shortopt' => 'n',
+ 'doc' => 'Print the name of the packaged file.',
+ ),
+ ),
+ 'doc' => '[descfile]
+Creates a PECL package from its package2.xml file.
+
+An automatic conversion will be made to a package.xml 1.0 and written out to
+disk in the current directory as "package.xml". Note that
+only simple package.xml 2.0 will be converted. package.xml 2.0 with:
+
+ - dependency types other than required/optional PECL package/ext/php/pearinstaller
+ - more than one extsrcrelease or zendextsrcrelease
+ - zendextbinrelease, extbinrelease, phprelease, or bundle release type
+ - dependency groups
+ - ignore tags in release filelist
+ - tasks other than replace
+ - custom roles
+
+will cause pickle to fail, and output an error message. If your package2.xml
+uses any of these features, you are best off using PEAR_PackageFileManager to
+generate both package.xml.
+'
+ ),
+ );
+
+ /**
+ * PEAR_Command_Package constructor.
+ *
+ * @access public
+ */
+ function PEAR_Command_Pickle(&$ui, &$config)
+ {
+ parent::PEAR_Command_Common($ui, $config);
+ }
+
+ /**
+ * For unit-testing ease
+ *
+ * @return PEAR_Packager
+ */
+ function &getPackager()
+ {
+ if (!class_exists('PEAR_Packager')) {
+ require_once 'PEAR/Packager.php';
+ }
+
+ $a = &new PEAR_Packager;
+ return $a;
+ }
+
+ /**
+ * For unit-testing ease
+ *
+ * @param PEAR_Config $config
+ * @param bool $debug
+ * @param string|null $tmpdir
+ * @return PEAR_PackageFile
+ */
+ function &getPackageFile($config, $debug = false)
+ {
+ if (!class_exists('PEAR_Common')) {
+ require_once 'PEAR/Common.php';
+ }
+
+ if (!class_exists('PEAR_PackageFile')) {
+ require_once 'PEAR/PackageFile.php';
+ }
+
+ $a = &new PEAR_PackageFile($config, $debug);
+ $common = new PEAR_Common;
+ $common->ui = $this->ui;
+ $a->setLogger($common);
+ return $a;
+ }
+
+ function doPackage($command, $options, $params)
+ {
+ $this->output = '';
+ $pkginfofile = isset($params[0]) ? $params[0] : 'package2.xml';
+ $packager = &$this->getPackager();
+ if (PEAR::isError($err = $this->_convertPackage($pkginfofile))) {
+ return $err;
+ }
+
+ $compress = empty($options['nocompress']) ? true : false;
+ $result = $packager->package($pkginfofile, $compress, 'package.xml');
+ if (PEAR::isError($result)) {
+ return $this->raiseError($result);
+ }
+
+ // Don't want output, only the package file name just created
+ if (isset($options['showname'])) {
+ $this->ui->outputData($result, $command);
+ }
+
+ return true;
+ }
+
+ function _convertPackage($packagexml)
+ {
+ $pkg = &$this->getPackageFile($this->config);
+ $pf2 = &$pkg->fromPackageFile($packagexml, PEAR_VALIDATE_NORMAL);
+ if (!is_a($pf2, 'PEAR_PackageFile_v2')) {
+ return $this->raiseError('Cannot process "' .
+ $packagexml . '", is not a package.xml 2.0');
+ }
+
+ require_once 'PEAR/PackageFile/v1.php';
+ $pf = new PEAR_PackageFile_v1;
+ $pf->setConfig($this->config);
+ if ($pf2->getPackageType() != 'extsrc' && $pf2->getPackageType() != 'zendextsrc') {
+ return $this->raiseError('Cannot safely convert "' . $packagexml .
+ '", is not an extension source package. Using a PEAR_PackageFileManager-based ' .
+ 'script is an option');
+ }
+
+ if (is_array($pf2->getUsesRole())) {
+ return $this->raiseError('Cannot safely convert "' . $packagexml .
+ '", contains custom roles. Using a PEAR_PackageFileManager-based script or ' .
+ 'the convert command is an option');
+ }
+
+ if (is_array($pf2->getUsesTask())) {
+ return $this->raiseError('Cannot safely convert "' . $packagexml .
+ '", contains custom tasks. Using a PEAR_PackageFileManager-based script or ' .
+ 'the convert command is an option');
+ }
+
+ $deps = $pf2->getDependencies();
+ if (isset($deps['group'])) {
+ return $this->raiseError('Cannot safely convert "' . $packagexml .
+ '", contains dependency groups. Using a PEAR_PackageFileManager-based script ' .
+ 'or the convert command is an option');
+ }
+
+ if (isset($deps['required']['subpackage']) ||
+ isset($deps['optional']['subpackage'])) {
+ return $this->raiseError('Cannot safely convert "' . $packagexml .
+ '", contains subpackage dependencies. Using a PEAR_PackageFileManager-based '.
+ 'script is an option');
+ }
+
+ if (isset($deps['required']['os'])) {
+ return $this->raiseError('Cannot safely convert "' . $packagexml .
+ '", contains os dependencies. Using a PEAR_PackageFileManager-based '.
+ 'script is an option');
+ }
+
+ if (isset($deps['required']['arch'])) {
+ return $this->raiseError('Cannot safely convert "' . $packagexml .
+ '", contains arch dependencies. Using a PEAR_PackageFileManager-based '.
+ 'script is an option');
+ }
+
+ $pf->setPackage($pf2->getPackage());
+ $pf->setSummary($pf2->getSummary());
+ $pf->setDescription($pf2->getDescription());
+ foreach ($pf2->getMaintainers() as $maintainer) {
+ $pf->addMaintainer($maintainer['role'], $maintainer['handle'],
+ $maintainer['name'], $maintainer['email']);
+ }
+
+ $pf->setVersion($pf2->getVersion());
+ $pf->setDate($pf2->getDate());
+ $pf->setLicense($pf2->getLicense());
+ $pf->setState($pf2->getState());
+ $pf->setNotes($pf2->getNotes());
+ $pf->addPhpDep($deps['required']['php']['min'], 'ge');
+ if (isset($deps['required']['php']['max'])) {
+ $pf->addPhpDep($deps['required']['php']['max'], 'le');
+ }
+
+ if (isset($deps['required']['package'])) {
+ if (!isset($deps['required']['package'][0])) {
+ $deps['required']['package'] = array($deps['required']['package']);
+ }
+
+ foreach ($deps['required']['package'] as $dep) {
+ if (!isset($dep['channel'])) {
+ return $this->raiseError('Cannot safely convert "' . $packagexml . '"' .
+ ' contains uri-based dependency on a package. Using a ' .
+ 'PEAR_PackageFileManager-based script is an option');
+ }
+
+ if ($dep['channel'] != 'pear.php.net'
+ && $dep['channel'] != 'pecl.php.net'
+ && $dep['channel'] != 'doc.php.net') {
+ return $this->raiseError('Cannot safely convert "' . $packagexml . '"' .
+ ' contains dependency on a non-standard channel package. Using a ' .
+ 'PEAR_PackageFileManager-based script is an option');
+ }
+
+ if (isset($dep['conflicts'])) {
+ return $this->raiseError('Cannot safely convert "' . $packagexml . '"' .
+ ' contains conflicts dependency. Using a ' .
+ 'PEAR_PackageFileManager-based script is an option');
+ }
+
+ if (isset($dep['exclude'])) {
+ $this->ui->outputData('WARNING: exclude tags are ignored in conversion');
+ }
+
+ if (isset($dep['min'])) {
+ $pf->addPackageDep($dep['name'], $dep['min'], 'ge');
+ }
+
+ if (isset($dep['max'])) {
+ $pf->addPackageDep($dep['name'], $dep['max'], 'le');
+ }
+ }
+ }
+
+ if (isset($deps['required']['extension'])) {
+ if (!isset($deps['required']['extension'][0])) {
+ $deps['required']['extension'] = array($deps['required']['extension']);
+ }
+
+ foreach ($deps['required']['extension'] as $dep) {
+ if (isset($dep['conflicts'])) {
+ return $this->raiseError('Cannot safely convert "' . $packagexml . '"' .
+ ' contains conflicts dependency. Using a ' .
+ 'PEAR_PackageFileManager-based script is an option');
+ }
+
+ if (isset($dep['exclude'])) {
+ $this->ui->outputData('WARNING: exclude tags are ignored in conversion');
+ }
+
+ if (isset($dep['min'])) {
+ $pf->addExtensionDep($dep['name'], $dep['min'], 'ge');
+ }
+
+ if (isset($dep['max'])) {
+ $pf->addExtensionDep($dep['name'], $dep['max'], 'le');
+ }
+ }
+ }
+
+ if (isset($deps['optional']['package'])) {
+ if (!isset($deps['optional']['package'][0])) {
+ $deps['optional']['package'] = array($deps['optional']['package']);
+ }
+
+ foreach ($deps['optional']['package'] as $dep) {
+ if (!isset($dep['channel'])) {
+ return $this->raiseError('Cannot safely convert "' . $packagexml . '"' .
+ ' contains uri-based dependency on a package. Using a ' .
+ 'PEAR_PackageFileManager-based script is an option');
+ }
+
+ if ($dep['channel'] != 'pear.php.net'
+ && $dep['channel'] != 'pecl.php.net'
+ && $dep['channel'] != 'doc.php.net') {
+ return $this->raiseError('Cannot safely convert "' . $packagexml . '"' .
+ ' contains dependency on a non-standard channel package. Using a ' .
+ 'PEAR_PackageFileManager-based script is an option');
+ }
+
+ if (isset($dep['exclude'])) {
+ $this->ui->outputData('WARNING: exclude tags are ignored in conversion');
+ }
+
+ if (isset($dep['min'])) {
+ $pf->addPackageDep($dep['name'], $dep['min'], 'ge', 'yes');
+ }
+
+ if (isset($dep['max'])) {
+ $pf->addPackageDep($dep['name'], $dep['max'], 'le', 'yes');
+ }
+ }
+ }
+
+ if (isset($deps['optional']['extension'])) {
+ if (!isset($deps['optional']['extension'][0])) {
+ $deps['optional']['extension'] = array($deps['optional']['extension']);
+ }
+
+ foreach ($deps['optional']['extension'] as $dep) {
+ if (isset($dep['exclude'])) {
+ $this->ui->outputData('WARNING: exclude tags are ignored in conversion');
+ }
+
+ if (isset($dep['min'])) {
+ $pf->addExtensionDep($dep['name'], $dep['min'], 'ge', 'yes');
+ }
+
+ if (isset($dep['max'])) {
+ $pf->addExtensionDep($dep['name'], $dep['max'], 'le', 'yes');
+ }
+ }
+ }
+
+ $contents = $pf2->getContents();
+ $release = $pf2->getReleases();
+ if (isset($releases[0])) {
+ return $this->raiseError('Cannot safely process "' . $packagexml . '" contains '
+ . 'multiple extsrcrelease/zendextsrcrelease tags. Using a PEAR_PackageFileManager-based script ' .
+ 'or the convert command is an option');
+ }
+
+ if ($configoptions = $pf2->getConfigureOptions()) {
+ foreach ($configoptions as $option) {
+ $default = isset($option['default']) ? $option['default'] : false;
+ $pf->addConfigureOption($option['name'], $option['prompt'], $default);
+ }
+ }
+
+ if (isset($release['filelist']['ignore'])) {
+ return $this->raiseError('Cannot safely process "' . $packagexml . '" contains '
+ . 'ignore tags. Using a PEAR_PackageFileManager-based script or the convert' .
+ ' command is an option');
+ }
+
+ if (isset($release['filelist']['install']) &&
+ !isset($release['filelist']['install'][0])) {
+ $release['filelist']['install'] = array($release['filelist']['install']);
+ }
+
+ if (isset($contents['dir']['attribs']['baseinstalldir'])) {
+ $baseinstalldir = $contents['dir']['attribs']['baseinstalldir'];
+ } else {
+ $baseinstalldir = false;
+ }
+
+ if (!isset($contents['dir']['file'][0])) {
+ $contents['dir']['file'] = array($contents['dir']['file']);
+ }
+
+ foreach ($contents['dir']['file'] as $file) {
+ if ($baseinstalldir && !isset($file['attribs']['baseinstalldir'])) {
+ $file['attribs']['baseinstalldir'] = $baseinstalldir;
+ }
+
+ $processFile = $file;
+ unset($processFile['attribs']);
+ if (count($processFile)) {
+ foreach ($processFile as $name => $task) {
+ if ($name != $pf2->getTasksNs() . ':replace') {
+ return $this->raiseError('Cannot safely process "' . $packagexml .
+ '" contains tasks other than replace. Using a ' .
+ 'PEAR_PackageFileManager-based script is an option.');
+ }
+ $file['attribs']['replace'][] = $task;
+ }
+ }
+
+ if (!in_array($file['attribs']['role'], PEAR_Common::getFileRoles())) {
+ return $this->raiseError('Cannot safely convert "' . $packagexml .
+ '", contains custom roles. Using a PEAR_PackageFileManager-based script ' .
+ 'or the convert command is an option');
+ }
+
+ if (isset($release['filelist']['install'])) {
+ foreach ($release['filelist']['install'] as $installas) {
+ if ($installas['attribs']['name'] == $file['attribs']['name']) {
+ $file['attribs']['install-as'] = $installas['attribs']['as'];
+ }
+ }
+ }
+
+ $pf->addFile('/', $file['attribs']['name'], $file['attribs']);
+ }
+
+ if ($pf2->getChangeLog()) {
+ $this->ui->outputData('WARNING: changelog is not translated to package.xml ' .
+ '1.0, use PEAR_PackageFileManager-based script if you need changelog-' .
+ 'translation for package.xml 1.0');
+ }
+
+ $gen = &$pf->getDefaultGenerator();
+ $gen->toPackageFile('.');
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Pickle.xml b/includes/pear/PEAR/Command/Pickle.xml
new file mode 100644
index 0000000..721ecea
--- /dev/null
+++ b/includes/pear/PEAR/Command/Pickle.xml
@@ -0,0 +1,36 @@
+<commands version="1.0">
+ <pickle>
+ <summary>Build PECL Package</summary>
+ <function>doPackage</function>
+ <shortcut>pi</shortcut>
+ <options>
+ <nocompress>
+ <shortopt>Z</shortopt>
+ <doc>Do not gzip the package file</doc>
+ </nocompress>
+ <showname>
+ <shortopt>n</shortopt>
+ <doc>Print the name of the packaged file.</doc>
+ </showname>
+ </options>
+ <doc>[descfile]
+Creates a PECL package from its package2.xml file.
+
+An automatic conversion will be made to a package.xml 1.0 and written out to
+disk in the current directory as &quot;package.xml&quot;. Note that
+only simple package.xml 2.0 will be converted. package.xml 2.0 with:
+
+ - dependency types other than required/optional PECL package/ext/php/pearinstaller
+ - more than one extsrcrelease or zendextsrcrelease
+ - zendextbinrelease, extbinrelease, phprelease, or bundle release type
+ - dependency groups
+ - ignore tags in release filelist
+ - tasks other than replace
+ - custom roles
+
+will cause pickle to fail, and output an error message. If your package2.xml
+uses any of these features, you are best off using PEAR_PackageFileManager to
+generate both package.xml.
+</doc>
+ </pickle>
+</commands> \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Registry.php b/includes/pear/PEAR/Command/Registry.php
new file mode 100644
index 0000000..4110123
--- /dev/null
+++ b/includes/pear/PEAR/Command/Registry.php
@@ -0,0 +1,1145 @@
+<?php
+/**
+ * PEAR_Command_Registry (list, list-files, shell-test, info commands)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+/**
+ * PEAR commands for registry manipulation
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ */
+class PEAR_Command_Registry extends PEAR_Command_Common
+{
+ var $commands = array(
+ 'list' => array(
+ 'summary' => 'List Installed Packages In The Default Channel',
+ 'function' => 'doList',
+ 'shortcut' => 'l',
+ 'options' => array(
+ 'channel' => array(
+ 'shortopt' => 'c',
+ 'doc' => 'list installed packages from this channel',
+ 'arg' => 'CHAN',
+ ),
+ 'allchannels' => array(
+ 'shortopt' => 'a',
+ 'doc' => 'list installed packages from all channels',
+ ),
+ 'channelinfo' => array(
+ 'shortopt' => 'i',
+ 'doc' => 'output fully channel-aware data, even on failure',
+ ),
+ ),
+ 'doc' => '<package>
+If invoked without parameters, this command lists the PEAR packages
+installed in your php_dir ({config php_dir}). With a parameter, it
+lists the files in a package.
+',
+ ),
+ 'list-files' => array(
+ 'summary' => 'List Files In Installed Package',
+ 'function' => 'doFileList',
+ 'shortcut' => 'fl',
+ 'options' => array(),
+ 'doc' => '<package>
+List the files in an installed package.
+'
+ ),
+ 'shell-test' => array(
+ 'summary' => 'Shell Script Test',
+ 'function' => 'doShellTest',
+ 'shortcut' => 'st',
+ 'options' => array(),
+ 'doc' => '<package> [[relation] version]
+Tests if a package is installed in the system. Will exit(1) if it is not.
+ <relation> The version comparison operator. One of:
+ <, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne
+ <version> The version to compare with
+'),
+ 'info' => array(
+ 'summary' => 'Display information about a package',
+ 'function' => 'doInfo',
+ 'shortcut' => 'in',
+ 'options' => array(),
+ 'doc' => '<package>
+Displays information about a package. The package argument may be a
+local package file, an URL to a package file, or the name of an
+installed package.'
+ )
+ );
+
+ /**
+ * PEAR_Command_Registry constructor.
+ *
+ * @access public
+ */
+ function PEAR_Command_Registry(&$ui, &$config)
+ {
+ parent::PEAR_Command_Common($ui, $config);
+ }
+
+ function _sortinfo($a, $b)
+ {
+ $apackage = isset($a['package']) ? $a['package'] : $a['name'];
+ $bpackage = isset($b['package']) ? $b['package'] : $b['name'];
+ return strcmp($apackage, $bpackage);
+ }
+
+ function doList($command, $options, $params)
+ {
+ $reg = &$this->config->getRegistry();
+ $channelinfo = isset($options['channelinfo']);
+ if (isset($options['allchannels']) && !$channelinfo) {
+ return $this->doListAll($command, array(), $params);
+ }
+
+ if (isset($options['allchannels']) && $channelinfo) {
+ // allchannels with $channelinfo
+ unset($options['allchannels']);
+ $channels = $reg->getChannels();
+ $errors = array();
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ foreach ($channels as $channel) {
+ $options['channel'] = $channel->getName();
+ $ret = $this->doList($command, $options, $params);
+
+ if (PEAR::isError($ret)) {
+ $errors[] = $ret;
+ }
+ }
+
+ PEAR::staticPopErrorHandling();
+ if (count($errors)) {
+ // for now, only give first error
+ return PEAR::raiseError($errors[0]);
+ }
+
+ return true;
+ }
+
+ if (count($params) === 1) {
+ return $this->doFileList($command, $options, $params);
+ }
+
+ if (isset($options['channel'])) {
+ if (!$reg->channelExists($options['channel'])) {
+ return $this->raiseError('Channel "' . $options['channel'] .'" does not exist');
+ }
+
+ $channel = $reg->channelName($options['channel']);
+ } else {
+ $channel = $this->config->get('default_channel');
+ }
+
+ $installed = $reg->packageInfo(null, null, $channel);
+ usort($installed, array(&$this, '_sortinfo'));
+
+ $data = array(
+ 'caption' => 'Installed packages, channel ' .
+ $channel . ':',
+ 'border' => true,
+ 'headline' => array('Package', 'Version', 'State'),
+ 'channel' => $channel,
+ );
+ if ($channelinfo) {
+ $data['headline'] = array('Channel', 'Package', 'Version', 'State');
+ }
+
+ if (count($installed) && !isset($data['data'])) {
+ $data['data'] = array();
+ }
+
+ foreach ($installed as $package) {
+ $pobj = $reg->getPackage(isset($package['package']) ?
+ $package['package'] : $package['name'], $channel);
+ if ($channelinfo) {
+ $packageinfo = array($pobj->getChannel(), $pobj->getPackage(), $pobj->getVersion(),
+ $pobj->getState() ? $pobj->getState() : null);
+ } else {
+ $packageinfo = array($pobj->getPackage(), $pobj->getVersion(),
+ $pobj->getState() ? $pobj->getState() : null);
+ }
+ $data['data'][] = $packageinfo;
+ }
+
+ if (count($installed) === 0) {
+ if (!$channelinfo) {
+ $data = '(no packages installed from channel ' . $channel . ')';
+ } else {
+ $data = array(
+ 'caption' => 'Installed packages, channel ' .
+ $channel . ':',
+ 'border' => true,
+ 'channel' => $channel,
+ 'data' => array(array('(no packages installed)')),
+ );
+ }
+ }
+
+ $this->ui->outputData($data, $command);
+ return true;
+ }
+
+ function doListAll($command, $options, $params)
+ {
+ // This duplicate code is deprecated over
+ // list --channelinfo, which gives identical
+ // output for list and list --allchannels.
+ $reg = &$this->config->getRegistry();
+ $installed = $reg->packageInfo(null, null, null);
+ foreach ($installed as $channel => $packages) {
+ usort($packages, array($this, '_sortinfo'));
+ $data = array(
+ 'caption' => 'Installed packages, channel ' . $channel . ':',
+ 'border' => true,
+ 'headline' => array('Package', 'Version', 'State'),
+ 'channel' => $channel
+ );
+
+ foreach ($packages as $package) {
+ $p = isset($package['package']) ? $package['package'] : $package['name'];
+ $pobj = $reg->getPackage($p, $channel);
+ $data['data'][] = array($pobj->getPackage(), $pobj->getVersion(),
+ $pobj->getState() ? $pobj->getState() : null);
+ }
+
+ // Adds a blank line after each section
+ $data['data'][] = array();
+
+ if (count($packages) === 0) {
+ $data = array(
+ 'caption' => 'Installed packages, channel ' . $channel . ':',
+ 'border' => true,
+ 'data' => array(array('(no packages installed)'), array()),
+ 'channel' => $channel
+ );
+ }
+ $this->ui->outputData($data, $command);
+ }
+ return true;
+ }
+
+ function doFileList($command, $options, $params)
+ {
+ if (count($params) !== 1) {
+ return $this->raiseError('list-files expects 1 parameter');
+ }
+
+ $reg = &$this->config->getRegistry();
+ $fp = false;
+ if (!is_dir($params[0]) && (file_exists($params[0]) || $fp = @fopen($params[0], 'r'))) {
+ if ($fp) {
+ fclose($fp);
+ }
+
+ if (!class_exists('PEAR_PackageFile')) {
+ require_once 'PEAR/PackageFile.php';
+ }
+
+ $pkg = &new PEAR_PackageFile($this->config, $this->_debug);
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $info = &$pkg->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL);
+ PEAR::staticPopErrorHandling();
+ $headings = array('Package File', 'Install Path');
+ $installed = false;
+ } else {
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel'));
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($parsed)) {
+ return $this->raiseError($parsed);
+ }
+
+ $info = &$reg->getPackage($parsed['package'], $parsed['channel']);
+ $headings = array('Type', 'Install Path');
+ $installed = true;
+ }
+
+ if (PEAR::isError($info)) {
+ return $this->raiseError($info);
+ }
+
+ if ($info === null) {
+ return $this->raiseError("`$params[0]' not installed");
+ }
+
+ $list = ($info->getPackagexmlVersion() == '1.0' || $installed) ?
+ $info->getFilelist() : $info->getContents();
+ if ($installed) {
+ $caption = 'Installed Files For ' . $params[0];
+ } else {
+ $caption = 'Contents of ' . basename($params[0]);
+ }
+
+ $data = array(
+ 'caption' => $caption,
+ 'border' => true,
+ 'headline' => $headings);
+ if ($info->getPackagexmlVersion() == '1.0' || $installed) {
+ foreach ($list as $file => $att) {
+ if ($installed) {
+ if (empty($att['installed_as'])) {
+ continue;
+ }
+ $data['data'][] = array($att['role'], $att['installed_as']);
+ } else {
+ if (isset($att['baseinstalldir']) && !in_array($att['role'],
+ array('test', 'data', 'doc'))) {
+ $dest = $att['baseinstalldir'] . DIRECTORY_SEPARATOR .
+ $file;
+ } else {
+ $dest = $file;
+ }
+ switch ($att['role']) {
+ case 'test':
+ case 'data':
+ case 'doc':
+ $role = $att['role'];
+ if ($role == 'test') {
+ $role .= 's';
+ }
+ $dest = $this->config->get($role . '_dir') . DIRECTORY_SEPARATOR .
+ $info->getPackage() . DIRECTORY_SEPARATOR . $dest;
+ break;
+ case 'php':
+ default:
+ $dest = $this->config->get('php_dir') . DIRECTORY_SEPARATOR .
+ $dest;
+ }
+ $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
+ $dest = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
+ array(DIRECTORY_SEPARATOR,
+ DIRECTORY_SEPARATOR,
+ DIRECTORY_SEPARATOR),
+ $dest);
+ $file = preg_replace('!/+!', '/', $file);
+ $data['data'][] = array($file, $dest);
+ }
+ }
+ } else { // package.xml 2.0, not installed
+ if (!isset($list['dir']['file'][0])) {
+ $list['dir']['file'] = array($list['dir']['file']);
+ }
+
+ foreach ($list['dir']['file'] as $att) {
+ $att = $att['attribs'];
+ $file = $att['name'];
+ $role = &PEAR_Installer_Role::factory($info, $att['role'], $this->config);
+ $role->setup($this, $info, $att, $file);
+ if (!$role->isInstallable()) {
+ $dest = '(not installable)';
+ } else {
+ $dest = $role->processInstallation($info, $att, $file, '');
+ if (PEAR::isError($dest)) {
+ $dest = '(Unknown role "' . $att['role'] . ')';
+ } else {
+ list(,, $dest) = $dest;
+ }
+ }
+ $data['data'][] = array($file, $dest);
+ }
+ }
+
+ $this->ui->outputData($data, $command);
+ return true;
+ }
+
+ function doShellTest($command, $options, $params)
+ {
+ if (count($params) < 1) {
+ return PEAR::raiseError('ERROR, usage: pear shell-test packagename [[relation] version]');
+ }
+
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $reg = &$this->config->getRegistry();
+ $info = $reg->parsePackageName($params[0], $this->config->get('default_channel'));
+ if (PEAR::isError($info)) {
+ exit(1); // invalid package name
+ }
+
+ $package = $info['package'];
+ $channel = $info['channel'];
+ // "pear shell-test Foo"
+ if (!$reg->packageExists($package, $channel)) {
+ if ($channel == 'pecl.php.net') {
+ if ($reg->packageExists($package, 'pear.php.net')) {
+ $channel = 'pear.php.net'; // magically change channels for extensions
+ }
+ }
+ }
+
+ if (count($params) === 1) {
+ if (!$reg->packageExists($package, $channel)) {
+ exit(1);
+ }
+ // "pear shell-test Foo 1.0"
+ } elseif (count($params) === 2) {
+ $v = $reg->packageInfo($package, 'version', $channel);
+ if (!$v || !version_compare("$v", "{$params[1]}", "ge")) {
+ exit(1);
+ }
+ // "pear shell-test Foo ge 1.0"
+ } elseif (count($params) === 3) {
+ $v = $reg->packageInfo($package, 'version', $channel);
+ if (!$v || !version_compare("$v", "{$params[2]}", $params[1])) {
+ exit(1);
+ }
+ } else {
+ PEAR::staticPopErrorHandling();
+ $this->raiseError("$command: expects 1 to 3 parameters");
+ exit(1);
+ }
+ }
+
+ function doInfo($command, $options, $params)
+ {
+ if (count($params) !== 1) {
+ return $this->raiseError('pear info expects 1 parameter');
+ }
+
+ $info = $fp = false;
+ $reg = &$this->config->getRegistry();
+ if (is_file($params[0]) && !is_dir($params[0]) &&
+ (file_exists($params[0]) || $fp = @fopen($params[0], 'r'))
+ ) {
+ if ($fp) {
+ fclose($fp);
+ }
+
+ if (!class_exists('PEAR_PackageFile')) {
+ require_once 'PEAR/PackageFile.php';
+ }
+
+ $pkg = &new PEAR_PackageFile($this->config, $this->_debug);
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $obj = &$pkg->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL);
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($obj)) {
+ $uinfo = $obj->getUserInfo();
+ if (is_array($uinfo)) {
+ foreach ($uinfo as $message) {
+ if (is_array($message)) {
+ $message = $message['message'];
+ }
+ $this->ui->outputData($message);
+ }
+ }
+
+ return $this->raiseError($obj);
+ }
+
+ if ($obj->getPackagexmlVersion() != '1.0') {
+ return $this->_doInfo2($command, $options, $params, $obj, false);
+ }
+
+ $info = $obj->toArray();
+ } else {
+ $parsed = $reg->parsePackageName($params[0], $this->config->get('default_channel'));
+ if (PEAR::isError($parsed)) {
+ return $this->raiseError($parsed);
+ }
+
+ $package = $parsed['package'];
+ $channel = $parsed['channel'];
+ $info = $reg->packageInfo($package, null, $channel);
+ if (isset($info['old'])) {
+ $obj = $reg->getPackage($package, $channel);
+ return $this->_doInfo2($command, $options, $params, $obj, true);
+ }
+ }
+
+ if (PEAR::isError($info)) {
+ return $info;
+ }
+
+ if (empty($info)) {
+ $this->raiseError("No information found for `$params[0]'");
+ return;
+ }
+
+ unset($info['filelist']);
+ unset($info['dirtree']);
+ unset($info['changelog']);
+ if (isset($info['xsdversion'])) {
+ $info['package.xml version'] = $info['xsdversion'];
+ unset($info['xsdversion']);
+ }
+
+ if (isset($info['packagerversion'])) {
+ $info['packaged with PEAR version'] = $info['packagerversion'];
+ unset($info['packagerversion']);
+ }
+
+ $keys = array_keys($info);
+ $longtext = array('description', 'summary');
+ foreach ($keys as $key) {
+ if (is_array($info[$key])) {
+ switch ($key) {
+ case 'maintainers': {
+ $i = 0;
+ $mstr = '';
+ foreach ($info[$key] as $m) {
+ if ($i++ > 0) {
+ $mstr .= "\n";
+ }
+ $mstr .= $m['name'] . " <";
+ if (isset($m['email'])) {
+ $mstr .= $m['email'];
+ } else {
+ $mstr .= $m['handle'] . '@php.net';
+ }
+ $mstr .= "> ($m[role])";
+ }
+ $info[$key] = $mstr;
+ break;
+ }
+ case 'release_deps': {
+ $i = 0;
+ $dstr = '';
+ foreach ($info[$key] as $d) {
+ if (isset($this->_deps_rel_trans[$d['rel']])) {
+ $rel = $this->_deps_rel_trans[$d['rel']];
+ } else {
+ $rel = $d['rel'];
+ }
+ if (isset($this->_deps_type_trans[$d['type']])) {
+ $type = ucfirst($this->_deps_type_trans[$d['type']]);
+ } else {
+ $type = $d['type'];
+ }
+ if (isset($d['name'])) {
+ $name = $d['name'] . ' ';
+ } else {
+ $name = '';
+ }
+ if (isset($d['version'])) {
+ $version = $d['version'] . ' ';
+ } else {
+ $version = '';
+ }
+ if (isset($d['optional']) && $d['optional'] == 'yes') {
+ $optional = ' (optional)';
+ } else {
+ $optional = '';
+ }
+ $dstr .= "$type $name$rel $version$optional\n";
+ }
+ $info[$key] = $dstr;
+ break;
+ }
+ case 'provides' : {
+ $debug = $this->config->get('verbose');
+ if ($debug < 2) {
+ $pstr = 'Classes: ';
+ } else {
+ $pstr = '';
+ }
+ $i = 0;
+ foreach ($info[$key] as $p) {
+ if ($debug < 2 && $p['type'] != "class") {
+ continue;
+ }
+ // Only print classes when verbosity mode is < 2
+ if ($debug < 2) {
+ if ($i++ > 0) {
+ $pstr .= ", ";
+ }
+ $pstr .= $p['name'];
+ } else {
+ if ($i++ > 0) {
+ $pstr .= "\n";
+ }
+ $pstr .= ucfirst($p['type']) . " " . $p['name'];
+ if (isset($p['explicit']) && $p['explicit'] == 1) {
+ $pstr .= " (explicit)";
+ }
+ }
+ }
+ $info[$key] = $pstr;
+ break;
+ }
+ case 'configure_options' : {
+ foreach ($info[$key] as $i => $p) {
+ $info[$key][$i] = array_map(null, array_keys($p), array_values($p));
+ $info[$key][$i] = array_map(create_function('$a',
+ 'return join(" = ",$a);'), $info[$key][$i]);
+ $info[$key][$i] = implode(', ', $info[$key][$i]);
+ }
+ $info[$key] = implode("\n", $info[$key]);
+ break;
+ }
+ default: {
+ $info[$key] = implode(", ", $info[$key]);
+ break;
+ }
+ }
+ }
+
+ if ($key == '_lastmodified') {
+ $hdate = date('Y-m-d', $info[$key]);
+ unset($info[$key]);
+ $info['Last Modified'] = $hdate;
+ } elseif ($key == '_lastversion') {
+ $info['Previous Installed Version'] = $info[$key] ? $info[$key] : '- None -';
+ unset($info[$key]);
+ } else {
+ $info[$key] = trim($info[$key]);
+ if (in_array($key, $longtext)) {
+ $info[$key] = preg_replace('/ +/', ' ', $info[$key]);
+ }
+ }
+ }
+
+ $caption = 'About ' . $info['package'] . '-' . $info['version'];
+ $data = array(
+ 'caption' => $caption,
+ 'border' => true);
+ foreach ($info as $key => $value) {
+ $key = ucwords(trim(str_replace('_', ' ', $key)));
+ $data['data'][] = array($key, $value);
+ }
+ $data['raw'] = $info;
+
+ $this->ui->outputData($data, 'package-info');
+ }
+
+ /**
+ * @access private
+ */
+ function _doInfo2($command, $options, $params, &$obj, $installed)
+ {
+ $reg = &$this->config->getRegistry();
+ $caption = 'About ' . $obj->getChannel() . '/' .$obj->getPackage() . '-' .
+ $obj->getVersion();
+ $data = array(
+ 'caption' => $caption,
+ 'border' => true);
+ switch ($obj->getPackageType()) {
+ case 'php' :
+ $release = 'PEAR-style PHP-based Package';
+ break;
+ case 'extsrc' :
+ $release = 'PECL-style PHP extension (source code)';
+ break;
+ case 'zendextsrc' :
+ $release = 'PECL-style Zend extension (source code)';
+ break;
+ case 'extbin' :
+ $release = 'PECL-style PHP extension (binary)';
+ break;
+ case 'zendextbin' :
+ $release = 'PECL-style Zend extension (binary)';
+ break;
+ case 'bundle' :
+ $release = 'Package bundle (collection of packages)';
+ break;
+ }
+ $extends = $obj->getExtends();
+ $extends = $extends ?
+ $obj->getPackage() . ' (extends ' . $extends . ')' : $obj->getPackage();
+ if ($src = $obj->getSourcePackage()) {
+ $extends .= ' (source package ' . $src['channel'] . '/' . $src['package'] . ')';
+ }
+
+ $info = array(
+ 'Release Type' => $release,
+ 'Name' => $extends,
+ 'Channel' => $obj->getChannel(),
+ 'Summary' => preg_replace('/ +/', ' ', $obj->getSummary()),
+ 'Description' => preg_replace('/ +/', ' ', $obj->getDescription()),
+ );
+ $info['Maintainers'] = '';
+ foreach (array('lead', 'developer', 'contributor', 'helper') as $role) {
+ $leads = $obj->{"get{$role}s"}();
+ if (!$leads) {
+ continue;
+ }
+
+ if (isset($leads['active'])) {
+ $leads = array($leads);
+ }
+
+ foreach ($leads as $lead) {
+ if (!empty($info['Maintainers'])) {
+ $info['Maintainers'] .= "\n";
+ }
+
+ $active = $lead['active'] == 'no' ? ', inactive' : '';
+ $info['Maintainers'] .= $lead['name'] . ' <';
+ $info['Maintainers'] .= $lead['email'] . "> ($role$active)";
+ }
+ }
+
+ $info['Release Date'] = $obj->getDate();
+ if ($time = $obj->getTime()) {
+ $info['Release Date'] .= ' ' . $time;
+ }
+
+ $info['Release Version'] = $obj->getVersion() . ' (' . $obj->getState() . ')';
+ $info['API Version'] = $obj->getVersion('api') . ' (' . $obj->getState('api') . ')';
+ $info['License'] = $obj->getLicense();
+ $uri = $obj->getLicenseLocation();
+ if ($uri) {
+ if (isset($uri['uri'])) {
+ $info['License'] .= ' (' . $uri['uri'] . ')';
+ } else {
+ $extra = $obj->getInstalledLocation($info['filesource']);
+ if ($extra) {
+ $info['License'] .= ' (' . $uri['filesource'] . ')';
+ }
+ }
+ }
+
+ $info['Release Notes'] = $obj->getNotes();
+ if ($compat = $obj->getCompatible()) {
+ if (!isset($compat[0])) {
+ $compat = array($compat);
+ }
+
+ $info['Compatible with'] = '';
+ foreach ($compat as $package) {
+ $info['Compatible with'] .= $package['channel'] . '/' . $package['name'] .
+ "\nVersions >= " . $package['min'] . ', <= ' . $package['max'];
+ if (isset($package['exclude'])) {
+ if (is_array($package['exclude'])) {
+ $package['exclude'] = implode(', ', $package['exclude']);
+ }
+
+ if (!isset($info['Not Compatible with'])) {
+ $info['Not Compatible with'] = '';
+ } else {
+ $info['Not Compatible with'] .= "\n";
+ }
+ $info['Not Compatible with'] .= $package['channel'] . '/' .
+ $package['name'] . "\nVersions " . $package['exclude'];
+ }
+ }
+ }
+
+ $usesrole = $obj->getUsesrole();
+ if ($usesrole) {
+ if (!isset($usesrole[0])) {
+ $usesrole = array($usesrole);
+ }
+
+ foreach ($usesrole as $roledata) {
+ if (isset($info['Uses Custom Roles'])) {
+ $info['Uses Custom Roles'] .= "\n";
+ } else {
+ $info['Uses Custom Roles'] = '';
+ }
+
+ if (isset($roledata['package'])) {
+ $rolepackage = $reg->parsedPackageNameToString($roledata, true);
+ } else {
+ $rolepackage = $roledata['uri'];
+ }
+ $info['Uses Custom Roles'] .= $roledata['role'] . ' (' . $rolepackage . ')';
+ }
+ }
+
+ $usestask = $obj->getUsestask();
+ if ($usestask) {
+ if (!isset($usestask[0])) {
+ $usestask = array($usestask);
+ }
+
+ foreach ($usestask as $taskdata) {
+ if (isset($info['Uses Custom Tasks'])) {
+ $info['Uses Custom Tasks'] .= "\n";
+ } else {
+ $info['Uses Custom Tasks'] = '';
+ }
+
+ if (isset($taskdata['package'])) {
+ $taskpackage = $reg->parsedPackageNameToString($taskdata, true);
+ } else {
+ $taskpackage = $taskdata['uri'];
+ }
+ $info['Uses Custom Tasks'] .= $taskdata['task'] . ' (' . $taskpackage . ')';
+ }
+ }
+
+ $deps = $obj->getDependencies();
+ $info['Required Dependencies'] = 'PHP version ' . $deps['required']['php']['min'];
+ if (isset($deps['required']['php']['max'])) {
+ $info['Required Dependencies'] .= '-' . $deps['required']['php']['max'] . "\n";
+ } else {
+ $info['Required Dependencies'] .= "\n";
+ }
+
+ if (isset($deps['required']['php']['exclude'])) {
+ if (!isset($info['Not Compatible with'])) {
+ $info['Not Compatible with'] = '';
+ } else {
+ $info['Not Compatible with'] .= "\n";
+ }
+
+ if (is_array($deps['required']['php']['exclude'])) {
+ $deps['required']['php']['exclude'] =
+ implode(', ', $deps['required']['php']['exclude']);
+ }
+ $info['Not Compatible with'] .= "PHP versions\n " .
+ $deps['required']['php']['exclude'];
+ }
+
+ $info['Required Dependencies'] .= 'PEAR installer version';
+ if (isset($deps['required']['pearinstaller']['max'])) {
+ $info['Required Dependencies'] .= 's ' .
+ $deps['required']['pearinstaller']['min'] . '-' .
+ $deps['required']['pearinstaller']['max'];
+ } else {
+ $info['Required Dependencies'] .= ' ' .
+ $deps['required']['pearinstaller']['min'] . ' or newer';
+ }
+
+ if (isset($deps['required']['pearinstaller']['exclude'])) {
+ if (!isset($info['Not Compatible with'])) {
+ $info['Not Compatible with'] = '';
+ } else {
+ $info['Not Compatible with'] .= "\n";
+ }
+
+ if (is_array($deps['required']['pearinstaller']['exclude'])) {
+ $deps['required']['pearinstaller']['exclude'] =
+ implode(', ', $deps['required']['pearinstaller']['exclude']);
+ }
+ $info['Not Compatible with'] .= "PEAR installer\n Versions " .
+ $deps['required']['pearinstaller']['exclude'];
+ }
+
+ foreach (array('Package', 'Extension') as $type) {
+ $index = strtolower($type);
+ if (isset($deps['required'][$index])) {
+ if (isset($deps['required'][$index]['name'])) {
+ $deps['required'][$index] = array($deps['required'][$index]);
+ }
+
+ foreach ($deps['required'][$index] as $package) {
+ if (isset($package['conflicts'])) {
+ $infoindex = 'Not Compatible with';
+ if (!isset($info['Not Compatible with'])) {
+ $info['Not Compatible with'] = '';
+ } else {
+ $info['Not Compatible with'] .= "\n";
+ }
+ } else {
+ $infoindex = 'Required Dependencies';
+ $info[$infoindex] .= "\n";
+ }
+
+ if ($index == 'extension') {
+ $name = $package['name'];
+ } else {
+ if (isset($package['channel'])) {
+ $name = $package['channel'] . '/' . $package['name'];
+ } else {
+ $name = '__uri/' . $package['name'] . ' (static URI)';
+ }
+ }
+
+ $info[$infoindex] .= "$type $name";
+ if (isset($package['uri'])) {
+ $info[$infoindex] .= "\n Download URI: $package[uri]";
+ continue;
+ }
+
+ if (isset($package['max']) && isset($package['min'])) {
+ $info[$infoindex] .= " \n Versions " .
+ $package['min'] . '-' . $package['max'];
+ } elseif (isset($package['min'])) {
+ $info[$infoindex] .= " \n Version " .
+ $package['min'] . ' or newer';
+ } elseif (isset($package['max'])) {
+ $info[$infoindex] .= " \n Version " .
+ $package['max'] . ' or older';
+ }
+
+ if (isset($package['recommended'])) {
+ $info[$infoindex] .= "\n Recommended version: $package[recommended]";
+ }
+
+ if (isset($package['exclude'])) {
+ if (!isset($info['Not Compatible with'])) {
+ $info['Not Compatible with'] = '';
+ } else {
+ $info['Not Compatible with'] .= "\n";
+ }
+
+ if (is_array($package['exclude'])) {
+ $package['exclude'] = implode(', ', $package['exclude']);
+ }
+
+ $package['package'] = $package['name']; // for parsedPackageNameToString
+ if (isset($package['conflicts'])) {
+ $info['Not Compatible with'] .= '=> except ';
+ }
+ $info['Not Compatible with'] .= 'Package ' .
+ $reg->parsedPackageNameToString($package, true);
+ $info['Not Compatible with'] .= "\n Versions " . $package['exclude'];
+ }
+ }
+ }
+ }
+
+ if (isset($deps['required']['os'])) {
+ if (isset($deps['required']['os']['name'])) {
+ $dep['required']['os']['name'] = array($dep['required']['os']['name']);
+ }
+
+ foreach ($dep['required']['os'] as $os) {
+ if (isset($os['conflicts']) && $os['conflicts'] == 'yes') {
+ if (!isset($info['Not Compatible with'])) {
+ $info['Not Compatible with'] = '';
+ } else {
+ $info['Not Compatible with'] .= "\n";
+ }
+ $info['Not Compatible with'] .= "$os[name] Operating System";
+ } else {
+ $info['Required Dependencies'] .= "\n";
+ $info['Required Dependencies'] .= "$os[name] Operating System";
+ }
+ }
+ }
+
+ if (isset($deps['required']['arch'])) {
+ if (isset($deps['required']['arch']['pattern'])) {
+ $dep['required']['arch']['pattern'] = array($dep['required']['os']['pattern']);
+ }
+
+ foreach ($dep['required']['arch'] as $os) {
+ if (isset($os['conflicts']) && $os['conflicts'] == 'yes') {
+ if (!isset($info['Not Compatible with'])) {
+ $info['Not Compatible with'] = '';
+ } else {
+ $info['Not Compatible with'] .= "\n";
+ }
+ $info['Not Compatible with'] .= "OS/Arch matching pattern '/$os[pattern]/'";
+ } else {
+ $info['Required Dependencies'] .= "\n";
+ $info['Required Dependencies'] .= "OS/Arch matching pattern '/$os[pattern]/'";
+ }
+ }
+ }
+
+ if (isset($deps['optional'])) {
+ foreach (array('Package', 'Extension') as $type) {
+ $index = strtolower($type);
+ if (isset($deps['optional'][$index])) {
+ if (isset($deps['optional'][$index]['name'])) {
+ $deps['optional'][$index] = array($deps['optional'][$index]);
+ }
+
+ foreach ($deps['optional'][$index] as $package) {
+ if (isset($package['conflicts']) && $package['conflicts'] == 'yes') {
+ $infoindex = 'Not Compatible with';
+ if (!isset($info['Not Compatible with'])) {
+ $info['Not Compatible with'] = '';
+ } else {
+ $info['Not Compatible with'] .= "\n";
+ }
+ } else {
+ $infoindex = 'Optional Dependencies';
+ if (!isset($info['Optional Dependencies'])) {
+ $info['Optional Dependencies'] = '';
+ } else {
+ $info['Optional Dependencies'] .= "\n";
+ }
+ }
+
+ if ($index == 'extension') {
+ $name = $package['name'];
+ } else {
+ if (isset($package['channel'])) {
+ $name = $package['channel'] . '/' . $package['name'];
+ } else {
+ $name = '__uri/' . $package['name'] . ' (static URI)';
+ }
+ }
+
+ $info[$infoindex] .= "$type $name";
+ if (isset($package['uri'])) {
+ $info[$infoindex] .= "\n Download URI: $package[uri]";
+ continue;
+ }
+
+ if ($infoindex == 'Not Compatible with') {
+ // conflicts is only used to say that all versions conflict
+ continue;
+ }
+
+ if (isset($package['max']) && isset($package['min'])) {
+ $info[$infoindex] .= " \n Versions " .
+ $package['min'] . '-' . $package['max'];
+ } elseif (isset($package['min'])) {
+ $info[$infoindex] .= " \n Version " .
+ $package['min'] . ' or newer';
+ } elseif (isset($package['max'])) {
+ $info[$infoindex] .= " \n Version " .
+ $package['min'] . ' or older';
+ }
+
+ if (isset($package['recommended'])) {
+ $info[$infoindex] .= "\n Recommended version: $package[recommended]";
+ }
+
+ if (isset($package['exclude'])) {
+ if (!isset($info['Not Compatible with'])) {
+ $info['Not Compatible with'] = '';
+ } else {
+ $info['Not Compatible with'] .= "\n";
+ }
+
+ if (is_array($package['exclude'])) {
+ $package['exclude'] = implode(', ', $package['exclude']);
+ }
+
+ $info['Not Compatible with'] .= "Package $package\n Versions " .
+ $package['exclude'];
+ }
+ }
+ }
+ }
+ }
+
+ if (isset($deps['group'])) {
+ if (!isset($deps['group'][0])) {
+ $deps['group'] = array($deps['group']);
+ }
+
+ foreach ($deps['group'] as $group) {
+ $info['Dependency Group ' . $group['attribs']['name']] = $group['attribs']['hint'];
+ $groupindex = $group['attribs']['name'] . ' Contents';
+ $info[$groupindex] = '';
+ foreach (array('Package', 'Extension') as $type) {
+ $index = strtolower($type);
+ if (isset($group[$index])) {
+ if (isset($group[$index]['name'])) {
+ $group[$index] = array($group[$index]);
+ }
+
+ foreach ($group[$index] as $package) {
+ if (!empty($info[$groupindex])) {
+ $info[$groupindex] .= "\n";
+ }
+
+ if ($index == 'extension') {
+ $name = $package['name'];
+ } else {
+ if (isset($package['channel'])) {
+ $name = $package['channel'] . '/' . $package['name'];
+ } else {
+ $name = '__uri/' . $package['name'] . ' (static URI)';
+ }
+ }
+
+ if (isset($package['uri'])) {
+ if (isset($package['conflicts']) && $package['conflicts'] == 'yes') {
+ $info[$groupindex] .= "Not Compatible with $type $name";
+ } else {
+ $info[$groupindex] .= "$type $name";
+ }
+
+ $info[$groupindex] .= "\n Download URI: $package[uri]";
+ continue;
+ }
+
+ if (isset($package['conflicts']) && $package['conflicts'] == 'yes') {
+ $info[$groupindex] .= "Not Compatible with $type $name";
+ continue;
+ }
+
+ $info[$groupindex] .= "$type $name";
+ if (isset($package['max']) && isset($package['min'])) {
+ $info[$groupindex] .= " \n Versions " .
+ $package['min'] . '-' . $package['max'];
+ } elseif (isset($package['min'])) {
+ $info[$groupindex] .= " \n Version " .
+ $package['min'] . ' or newer';
+ } elseif (isset($package['max'])) {
+ $info[$groupindex] .= " \n Version " .
+ $package['min'] . ' or older';
+ }
+
+ if (isset($package['recommended'])) {
+ $info[$groupindex] .= "\n Recommended version: $package[recommended]";
+ }
+
+ if (isset($package['exclude'])) {
+ if (!isset($info['Not Compatible with'])) {
+ $info['Not Compatible with'] = '';
+ } else {
+ $info[$groupindex] .= "Not Compatible with\n";
+ }
+
+ if (is_array($package['exclude'])) {
+ $package['exclude'] = implode(', ', $package['exclude']);
+ }
+ $info[$groupindex] .= " Package $package\n Versions " .
+ $package['exclude'];
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ($obj->getPackageType() == 'bundle') {
+ $info['Bundled Packages'] = '';
+ foreach ($obj->getBundledPackages() as $package) {
+ if (!empty($info['Bundled Packages'])) {
+ $info['Bundled Packages'] .= "\n";
+ }
+
+ if (isset($package['uri'])) {
+ $info['Bundled Packages'] .= '__uri/' . $package['name'];
+ $info['Bundled Packages'] .= "\n (URI: $package[uri]";
+ } else {
+ $info['Bundled Packages'] .= $package['channel'] . '/' . $package['name'];
+ }
+ }
+ }
+
+ $info['package.xml version'] = '2.0';
+ if ($installed) {
+ if ($obj->getLastModified()) {
+ $info['Last Modified'] = date('Y-m-d H:i', $obj->getLastModified());
+ }
+
+ $v = $obj->getLastInstalledVersion();
+ $info['Previous Installed Version'] = $v ? $v : '- None -';
+ }
+
+ foreach ($info as $key => $value) {
+ $data['data'][] = array($key, $value);
+ }
+
+ $data['raw'] = $obj->getArray(); // no validation needed
+ $this->ui->outputData($data, 'package-info');
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Registry.xml b/includes/pear/PEAR/Command/Registry.xml
new file mode 100644
index 0000000..9f4e214
--- /dev/null
+++ b/includes/pear/PEAR/Command/Registry.xml
@@ -0,0 +1,58 @@
+<commands version="1.0">
+ <list>
+ <summary>List Installed Packages In The Default Channel</summary>
+ <function>doList</function>
+ <shortcut>l</shortcut>
+ <options>
+ <channel>
+ <shortopt>c</shortopt>
+ <doc>list installed packages from this channel</doc>
+ <arg>CHAN</arg>
+ </channel>
+ <allchannels>
+ <shortopt>a</shortopt>
+ <doc>list installed packages from all channels</doc>
+ </allchannels>
+ <channelinfo>
+ <shortopt>i</shortopt>
+ <doc>output fully channel-aware data, even on failure</doc>
+ </channelinfo>
+ </options>
+ <doc>&lt;package&gt;
+If invoked without parameters, this command lists the PEAR packages
+installed in your php_dir ({config php_dir}). With a parameter, it
+lists the files in a package.
+</doc>
+ </list>
+ <list-files>
+ <summary>List Files In Installed Package</summary>
+ <function>doFileList</function>
+ <shortcut>fl</shortcut>
+ <options />
+ <doc>&lt;package&gt;
+List the files in an installed package.
+</doc>
+ </list-files>
+ <shell-test>
+ <summary>Shell Script Test</summary>
+ <function>doShellTest</function>
+ <shortcut>st</shortcut>
+ <options />
+ <doc>&lt;package&gt; [[relation] version]
+Tests if a package is installed in the system. Will exit(1) if it is not.
+ &lt;relation&gt; The version comparison operator. One of:
+ &lt;, lt, &lt;=, le, &gt;, gt, &gt;=, ge, ==, =, eq, !=, &lt;&gt;, ne
+ &lt;version&gt; The version to compare with
+</doc>
+ </shell-test>
+ <info>
+ <summary>Display information about a package</summary>
+ <function>doInfo</function>
+ <shortcut>in</shortcut>
+ <options />
+ <doc>&lt;package&gt;
+Displays information about a package. The package argument may be a
+local package file, an URL to a package file, or the name of an
+installed package.</doc>
+ </info>
+</commands> \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Remote.php b/includes/pear/PEAR/Command/Remote.php
new file mode 100644
index 0000000..52d0aee
--- /dev/null
+++ b/includes/pear/PEAR/Command/Remote.php
@@ -0,0 +1,810 @@
+<?php
+/**
+ * PEAR_Command_Remote (remote-info, list-upgrades, remote-list, search, list-all, download,
+ * clear-cache commands)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+require_once 'PEAR/REST.php';
+
+/**
+ * PEAR commands for remote server querying
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ */
+class PEAR_Command_Remote extends PEAR_Command_Common
+{
+ var $commands = array(
+ 'remote-info' => array(
+ 'summary' => 'Information About Remote Packages',
+ 'function' => 'doRemoteInfo',
+ 'shortcut' => 'ri',
+ 'options' => array(),
+ 'doc' => '<package>
+Get details on a package from the server.',
+ ),
+ 'list-upgrades' => array(
+ 'summary' => 'List Available Upgrades',
+ 'function' => 'doListUpgrades',
+ 'shortcut' => 'lu',
+ 'options' => array(
+ 'channelinfo' => array(
+ 'shortopt' => 'i',
+ 'doc' => 'output fully channel-aware data, even on failure',
+ ),
+ ),
+ 'doc' => '[preferred_state]
+List releases on the server of packages you have installed where
+a newer version is available with the same release state (stable etc.)
+or the state passed as the second parameter.'
+ ),
+ 'remote-list' => array(
+ 'summary' => 'List Remote Packages',
+ 'function' => 'doRemoteList',
+ 'shortcut' => 'rl',
+ 'options' => array(
+ 'channel' =>
+ array(
+ 'shortopt' => 'c',
+ 'doc' => 'specify a channel other than the default channel',
+ 'arg' => 'CHAN',
+ )
+ ),
+ 'doc' => '
+Lists the packages available on the configured server along with the
+latest stable release of each package.',
+ ),
+ 'search' => array(
+ 'summary' => 'Search remote package database',
+ 'function' => 'doSearch',
+ 'shortcut' => 'sp',
+ 'options' => array(
+ 'channel' =>
+ array(
+ 'shortopt' => 'c',
+ 'doc' => 'specify a channel other than the default channel',
+ 'arg' => 'CHAN',
+ ),
+ 'allchannels' => array(
+ 'shortopt' => 'a',
+ 'doc' => 'search packages from all known channels',
+ ),
+ 'channelinfo' => array(
+ 'shortopt' => 'i',
+ 'doc' => 'output fully channel-aware data, even on failure',
+ ),
+ ),
+ 'doc' => '[packagename] [packageinfo]
+Lists all packages which match the search parameters. The first
+parameter is a fragment of a packagename. The default channel
+will be used unless explicitly overridden. The second parameter
+will be used to match any portion of the summary/description',
+ ),
+ 'list-all' => array(
+ 'summary' => 'List All Packages',
+ 'function' => 'doListAll',
+ 'shortcut' => 'la',
+ 'options' => array(
+ 'channel' =>
+ array(
+ 'shortopt' => 'c',
+ 'doc' => 'specify a channel other than the default channel',
+ 'arg' => 'CHAN',
+ ),
+ 'channelinfo' => array(
+ 'shortopt' => 'i',
+ 'doc' => 'output fully channel-aware data, even on failure',
+ ),
+ ),
+ 'doc' => '
+Lists the packages available on the configured server along with the
+latest stable release of each package.',
+ ),
+ 'download' => array(
+ 'summary' => 'Download Package',
+ 'function' => 'doDownload',
+ 'shortcut' => 'd',
+ 'options' => array(
+ 'nocompress' => array(
+ 'shortopt' => 'Z',
+ 'doc' => 'download an uncompressed (.tar) file',
+ ),
+ ),
+ 'doc' => '<package>...
+Download package tarballs. The files will be named as suggested by the
+server, for example if you download the DB package and the latest stable
+version of DB is 1.6.5, the downloaded file will be DB-1.6.5.tgz.',
+ ),
+ 'clear-cache' => array(
+ 'summary' => 'Clear Web Services Cache',
+ 'function' => 'doClearCache',
+ 'shortcut' => 'cc',
+ 'options' => array(),
+ 'doc' => '
+Clear the REST cache. See also the cache_ttl configuration
+parameter.
+',
+ ),
+ );
+
+ /**
+ * PEAR_Command_Remote constructor.
+ *
+ * @access public
+ */
+ function PEAR_Command_Remote(&$ui, &$config)
+ {
+ parent::PEAR_Command_Common($ui, $config);
+ }
+
+ function _checkChannelForStatus($channel, $chan)
+ {
+ if (PEAR::isError($chan)) {
+ $this->raiseError($chan);
+ }
+ if (!is_a($chan, 'PEAR_ChannelFile')) {
+ return $this->raiseError('Internal corruption error: invalid channel "' .
+ $channel . '"');
+ }
+ $rest = new PEAR_REST($this->config);
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $mirror = $this->config->get('preferred_mirror', null,
+ $channel);
+ $a = $rest->downloadHttp('http://' . $channel .
+ '/channel.xml', $chan->lastModified());
+ PEAR::staticPopErrorHandling();
+ if (!PEAR::isError($a) && $a) {
+ $this->ui->outputData('WARNING: channel "' . $channel . '" has ' .
+ 'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $channel .
+ '" to update');
+ }
+ }
+
+ function doRemoteInfo($command, $options, $params)
+ {
+ if (sizeof($params) != 1) {
+ return $this->raiseError("$command expects one param: the remote package name");
+ }
+ $savechannel = $channel = $this->config->get('default_channel');
+ $reg = &$this->config->getRegistry();
+ $package = $params[0];
+ $parsed = $reg->parsePackageName($package, $channel);
+ if (PEAR::isError($parsed)) {
+ return $this->raiseError('Invalid package name "' . $package . '"');
+ }
+
+ $channel = $parsed['channel'];
+ $this->config->set('default_channel', $channel);
+ $chan = $reg->getChannel($channel);
+ if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
+ return $e;
+ }
+
+ $mirror = $this->config->get('preferred_mirror');
+ if ($chan->supportsREST($mirror) && $base = $chan->getBaseURL('REST1.0', $mirror)) {
+ $rest = &$this->config->getREST('1.0', array());
+ $info = $rest->packageInfo($base, $parsed['package'], $channel);
+ }
+
+ if (!isset($info)) {
+ return $this->raiseError('No supported protocol was found');
+ }
+
+ if (PEAR::isError($info)) {
+ $this->config->set('default_channel', $savechannel);
+ return $this->raiseError($info);
+ }
+
+ if (!isset($info['name'])) {
+ return $this->raiseError('No remote package "' . $package . '" was found');
+ }
+
+ $installed = $reg->packageInfo($info['name'], null, $channel);
+ $info['installed'] = $installed['version'] ? $installed['version'] : '- no -';
+ if (is_array($info['installed'])) {
+ $info['installed'] = $info['installed']['release'];
+ }
+
+ $this->ui->outputData($info, $command);
+ $this->config->set('default_channel', $savechannel);
+
+ return true;
+ }
+
+ function doRemoteList($command, $options, $params)
+ {
+ $savechannel = $channel = $this->config->get('default_channel');
+ $reg = &$this->config->getRegistry();
+ if (isset($options['channel'])) {
+ $channel = $options['channel'];
+ if (!$reg->channelExists($channel)) {
+ return $this->raiseError('Channel "' . $channel . '" does not exist');
+ }
+
+ $this->config->set('default_channel', $channel);
+ }
+
+ $chan = $reg->getChannel($channel);
+ if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
+ return $e;
+ }
+
+ $list_options = false;
+ if ($this->config->get('preferred_state') == 'stable') {
+ $list_options = true;
+ }
+
+ $available = array();
+ if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
+ $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror'))
+ ) {
+ // use faster list-all if available
+ $rest = &$this->config->getREST('1.1', array());
+ $available = $rest->listAll($base, $list_options, true, false, false, $chan->getName());
+ } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) &&
+ $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
+ $rest = &$this->config->getREST('1.0', array());
+ $available = $rest->listAll($base, $list_options, true, false, false, $chan->getName());
+ }
+
+ if (PEAR::isError($available)) {
+ $this->config->set('default_channel', $savechannel);
+ return $this->raiseError($available);
+ }
+
+ $i = $j = 0;
+ $data = array(
+ 'caption' => 'Channel ' . $channel . ' Available packages:',
+ 'border' => true,
+ 'headline' => array('Package', 'Version'),
+ 'channel' => $channel
+ );
+
+ if (count($available) == 0) {
+ $data = '(no packages available yet)';
+ } else {
+ foreach ($available as $name => $info) {
+ $version = (isset($info['stable']) && $info['stable']) ? $info['stable'] : '-n/a-';
+ $data['data'][] = array($name, $version);
+ }
+ }
+ $this->ui->outputData($data, $command);
+ $this->config->set('default_channel', $savechannel);
+ return true;
+ }
+
+ function doListAll($command, $options, $params)
+ {
+ $savechannel = $channel = $this->config->get('default_channel');
+ $reg = &$this->config->getRegistry();
+ if (isset($options['channel'])) {
+ $channel = $options['channel'];
+ if (!$reg->channelExists($channel)) {
+ return $this->raiseError("Channel \"$channel\" does not exist");
+ }
+
+ $this->config->set('default_channel', $channel);
+ }
+
+ $list_options = false;
+ if ($this->config->get('preferred_state') == 'stable') {
+ $list_options = true;
+ }
+
+ $chan = $reg->getChannel($channel);
+ if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
+ return $e;
+ }
+
+ if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
+ $base = $chan->getBaseURL('REST1.1', $this->config->get('preferred_mirror'))) {
+ // use faster list-all if available
+ $rest = &$this->config->getREST('1.1', array());
+ $available = $rest->listAll($base, $list_options, false, false, false, $chan->getName());
+ } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) &&
+ $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
+ $rest = &$this->config->getREST('1.0', array());
+ $available = $rest->listAll($base, $list_options, false, false, false, $chan->getName());
+ }
+
+ if (PEAR::isError($available)) {
+ $this->config->set('default_channel', $savechannel);
+ return $this->raiseError('The package list could not be fetched from the remote server. Please try again. (Debug info: "' . $available->getMessage() . '")');
+ }
+
+ $data = array(
+ 'caption' => 'All packages [Channel ' . $channel . ']:',
+ 'border' => true,
+ 'headline' => array('Package', 'Latest', 'Local'),
+ 'channel' => $channel,
+ );
+
+ if (isset($options['channelinfo'])) {
+ // add full channelinfo
+ $data['caption'] = 'Channel ' . $channel . ' All packages:';
+ $data['headline'] = array('Channel', 'Package', 'Latest', 'Local',
+ 'Description', 'Dependencies');
+ }
+ $local_pkgs = $reg->listPackages($channel);
+
+ foreach ($available as $name => $info) {
+ $installed = $reg->packageInfo($name, null, $channel);
+ if (is_array($installed['version'])) {
+ $installed['version'] = $installed['version']['release'];
+ }
+ $desc = $info['summary'];
+ if (isset($params[$name])) {
+ $desc .= "\n\n".$info['description'];
+ }
+
+ if (isset($options['mode'])) {
+ if ($options['mode'] == 'installed' && !isset($installed['version'])) {
+ continue;
+ }
+ if ($options['mode'] == 'notinstalled' && isset($installed['version'])) {
+ continue;
+ }
+ if ($options['mode'] == 'upgrades'
+ && (!isset($installed['version']) || version_compare($installed['version'],
+ $info['stable'], '>='))) {
+ continue;
+ }
+ }
+ $pos = array_search(strtolower($name), $local_pkgs);
+ if ($pos !== false) {
+ unset($local_pkgs[$pos]);
+ }
+
+ if (isset($info['stable']) && !$info['stable']) {
+ $info['stable'] = null;
+ }
+
+ if (isset($options['channelinfo'])) {
+ // add full channelinfo
+ if ($info['stable'] === $info['unstable']) {
+ $state = $info['state'];
+ } else {
+ $state = 'stable';
+ }
+ $latest = $info['stable'].' ('.$state.')';
+ $local = '';
+ if (isset($installed['version'])) {
+ $inst_state = $reg->packageInfo($name, 'release_state', $channel);
+ $local = $installed['version'].' ('.$inst_state.')';
+ }
+
+ $packageinfo = array(
+ $channel,
+ $name,
+ $latest,
+ $local,
+ isset($desc) ? $desc : null,
+ isset($info['deps']) ? $info['deps'] : null,
+ );
+ } else {
+ $packageinfo = array(
+ $reg->channelAlias($channel) . '/' . $name,
+ isset($info['stable']) ? $info['stable'] : null,
+ isset($installed['version']) ? $installed['version'] : null,
+ isset($desc) ? $desc : null,
+ isset($info['deps']) ? $info['deps'] : null,
+ );
+ }
+ $data['data'][$info['category']][] = $packageinfo;
+ }
+
+ if (isset($options['mode']) && in_array($options['mode'], array('notinstalled', 'upgrades'))) {
+ $this->config->set('default_channel', $savechannel);
+ $this->ui->outputData($data, $command);
+ return true;
+ }
+
+ foreach ($local_pkgs as $name) {
+ $info = &$reg->getPackage($name, $channel);
+ $data['data']['Local'][] = array(
+ $reg->channelAlias($channel) . '/' . $info->getPackage(),
+ '',
+ $info->getVersion(),
+ $info->getSummary(),
+ $info->getDeps()
+ );
+ }
+
+ $this->config->set('default_channel', $savechannel);
+ $this->ui->outputData($data, $command);
+ return true;
+ }
+
+ function doSearch($command, $options, $params)
+ {
+ if ((!isset($params[0]) || empty($params[0]))
+ && (!isset($params[1]) || empty($params[1])))
+ {
+ return $this->raiseError('no valid search string supplied');
+ }
+
+ $channelinfo = isset($options['channelinfo']);
+ $reg = &$this->config->getRegistry();
+ if (isset($options['allchannels'])) {
+ // search all channels
+ unset($options['allchannels']);
+ $channels = $reg->getChannels();
+ $errors = array();
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ foreach ($channels as $channel) {
+ if ($channel->getName() != '__uri') {
+ $options['channel'] = $channel->getName();
+ $ret = $this->doSearch($command, $options, $params);
+ if (PEAR::isError($ret)) {
+ $errors[] = $ret;
+ }
+ }
+ }
+
+ PEAR::staticPopErrorHandling();
+ if (count($errors) !== 0) {
+ // for now, only give first error
+ return PEAR::raiseError($errors[0]);
+ }
+
+ return true;
+ }
+
+ $savechannel = $channel = $this->config->get('default_channel');
+ $package = strtolower($params[0]);
+ $summary = isset($params[1]) ? $params[1] : false;
+ if (isset($options['channel'])) {
+ $reg = &$this->config->getRegistry();
+ $channel = $options['channel'];
+ if (!$reg->channelExists($channel)) {
+ return $this->raiseError('Channel "' . $channel . '" does not exist');
+ }
+
+ $this->config->set('default_channel', $channel);
+ }
+
+ $chan = $reg->getChannel($channel);
+ if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
+ return $e;
+ }
+
+ if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
+ $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
+ $rest = &$this->config->getREST('1.0', array());
+ $available = $rest->listAll($base, false, false, $package, $summary, $chan->getName());
+ }
+
+ if (PEAR::isError($available)) {
+ $this->config->set('default_channel', $savechannel);
+ return $this->raiseError($available);
+ }
+
+ if (!$available && !$channelinfo) {
+ // clean exit when not found, no error !
+ $data = 'no packages found that match pattern "' . $package . '", for channel '.$channel.'.';
+ $this->ui->outputData($data);
+ $this->config->set('default_channel', $channel);
+ return true;
+ }
+
+ if ($channelinfo) {
+ $data = array(
+ 'caption' => 'Matched packages, channel ' . $channel . ':',
+ 'border' => true,
+ 'headline' => array('Channel', 'Package', 'Stable/(Latest)', 'Local'),
+ 'channel' => $channel
+ );
+ } else {
+ $data = array(
+ 'caption' => 'Matched packages, channel ' . $channel . ':',
+ 'border' => true,
+ 'headline' => array('Package', 'Stable/(Latest)', 'Local'),
+ 'channel' => $channel
+ );
+ }
+
+ if (!$available && $channelinfo) {
+ unset($data['headline']);
+ $data['data'] = 'No packages found that match pattern "' . $package . '".';
+ $available = array();
+ }
+
+ foreach ($available as $name => $info) {
+ $installed = $reg->packageInfo($name, null, $channel);
+ $desc = $info['summary'];
+ if (isset($params[$name]))
+ $desc .= "\n\n".$info['description'];
+
+ if (!isset($info['stable']) || !$info['stable']) {
+ $version_remote = 'none';
+ } else {
+ if ($info['unstable']) {
+ $version_remote = $info['unstable'];
+ } else {
+ $version_remote = $info['stable'];
+ }
+ $version_remote .= ' ('.$info['state'].')';
+ }
+ $version = is_array($installed['version']) ? $installed['version']['release'] :
+ $installed['version'];
+ if ($channelinfo) {
+ $packageinfo = array(
+ $channel,
+ $name,
+ $version_remote,
+ $version,
+ $desc,
+ );
+ } else {
+ $packageinfo = array(
+ $name,
+ $version_remote,
+ $version,
+ $desc,
+ );
+ }
+ $data['data'][$info['category']][] = $packageinfo;
+ }
+
+ $this->ui->outputData($data, $command);
+ $this->config->set('default_channel', $channel);
+ return true;
+ }
+
+ function &getDownloader($options)
+ {
+ if (!class_exists('PEAR_Downloader')) {
+ require_once 'PEAR/Downloader.php';
+ }
+ $a = &new PEAR_Downloader($this->ui, $options, $this->config);
+ return $a;
+ }
+
+ function doDownload($command, $options, $params)
+ {
+ // make certain that dependencies are ignored
+ $options['downloadonly'] = 1;
+
+ // eliminate error messages for preferred_state-related errors
+ /* TODO: Should be an option, but until now download does respect
+ prefered state */
+ /* $options['ignorepreferred_state'] = 1; */
+ // eliminate error messages for preferred_state-related errors
+
+ $downloader = &$this->getDownloader($options);
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $e = $downloader->setDownloadDir(getcwd());
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($e)) {
+ return $this->raiseError('Current directory is not writeable, cannot download');
+ }
+
+ $errors = array();
+ $downloaded = array();
+ $err = $downloader->download($params);
+ if (PEAR::isError($err)) {
+ return $err;
+ }
+
+ $errors = $downloader->getErrorMsgs();
+ if (count($errors)) {
+ foreach ($errors as $error) {
+ if ($error !== null) {
+ $this->ui->outputData($error);
+ }
+ }
+
+ return $this->raiseError("$command failed");
+ }
+
+ $downloaded = $downloader->getDownloadedPackages();
+ foreach ($downloaded as $pkg) {
+ $this->ui->outputData("File $pkg[file] downloaded", $command);
+ }
+
+ return true;
+ }
+
+ function downloadCallback($msg, $params = null)
+ {
+ if ($msg == 'done') {
+ $this->bytes_downloaded = $params;
+ }
+ }
+
+ function doListUpgrades($command, $options, $params)
+ {
+ require_once 'PEAR/Common.php';
+ if (isset($params[0]) && !is_array(PEAR_Common::betterStates($params[0]))) {
+ return $this->raiseError($params[0] . ' is not a valid state (stable/beta/alpha/devel/etc.) try "pear help list-upgrades"');
+ }
+
+ $savechannel = $channel = $this->config->get('default_channel');
+ $reg = &$this->config->getRegistry();
+ foreach ($reg->listChannels() as $channel) {
+ $inst = array_flip($reg->listPackages($channel));
+ if (!count($inst)) {
+ continue;
+ }
+
+ if ($channel == '__uri') {
+ continue;
+ }
+
+ $this->config->set('default_channel', $channel);
+ $state = empty($params[0]) ? $this->config->get('preferred_state') : $params[0];
+
+ $caption = $channel . ' Available Upgrades';
+ $chan = $reg->getChannel($channel);
+ if (PEAR::isError($e = $this->_checkChannelForStatus($channel, $chan))) {
+ return $e;
+ }
+
+ $latest = array();
+ $base2 = false;
+ $preferred_mirror = $this->config->get('preferred_mirror');
+ if ($chan->supportsREST($preferred_mirror) &&
+ (
+ //($base2 = $chan->getBaseURL('REST1.4', $preferred_mirror)) ||
+ ($base = $chan->getBaseURL('REST1.0', $preferred_mirror))
+ )
+
+ ) {
+ if ($base2) {
+ $rest = &$this->config->getREST('1.4', array());
+ $base = $base2;
+ } else {
+ $rest = &$this->config->getREST('1.0', array());
+ }
+
+ if (empty($state) || $state == 'any') {
+ $state = false;
+ } else {
+ $caption .= ' (' . implode(', ', PEAR_Common::betterStates($state, true)) . ')';
+ }
+
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $latest = $rest->listLatestUpgrades($base, $state, $inst, $channel, $reg);
+ PEAR::staticPopErrorHandling();
+ }
+
+ if (PEAR::isError($latest)) {
+ $this->ui->outputData($latest->getMessage());
+ continue;
+ }
+
+ $caption .= ':';
+ if (PEAR::isError($latest)) {
+ $this->config->set('default_channel', $savechannel);
+ return $latest;
+ }
+
+ $data = array(
+ 'caption' => $caption,
+ 'border' => 1,
+ 'headline' => array('Channel', 'Package', 'Local', 'Remote', 'Size'),
+ 'channel' => $channel
+ );
+
+ foreach ((array)$latest as $pkg => $info) {
+ $package = strtolower($pkg);
+ if (!isset($inst[$package])) {
+ // skip packages we don't have installed
+ continue;
+ }
+
+ extract($info);
+ $inst_version = $reg->packageInfo($package, 'version', $channel);
+ $inst_state = $reg->packageInfo($package, 'release_state', $channel);
+ if (version_compare("$version", "$inst_version", "le")) {
+ // installed version is up-to-date
+ continue;
+ }
+
+ if ($filesize >= 20480) {
+ $filesize += 1024 - ($filesize % 1024);
+ $fs = sprintf("%dkB", $filesize / 1024);
+ } elseif ($filesize > 0) {
+ $filesize += 103 - ($filesize % 103);
+ $fs = sprintf("%.1fkB", $filesize / 1024.0);
+ } else {
+ $fs = " -"; // XXX center instead
+ }
+
+ $data['data'][] = array($channel, $pkg, "$inst_version ($inst_state)", "$version ($state)", $fs);
+ }
+
+ if (isset($options['channelinfo'])) {
+ if (empty($data['data'])) {
+ unset($data['headline']);
+ if (count($inst) == 0) {
+ $data['data'] = '(no packages installed)';
+ } else {
+ $data['data'] = '(no upgrades available)';
+ }
+ }
+ $this->ui->outputData($data, $command);
+ } else {
+ if (empty($data['data'])) {
+ $this->ui->outputData('Channel ' . $channel . ': No upgrades available');
+ } else {
+ $this->ui->outputData($data, $command);
+ }
+ }
+ }
+
+ $this->config->set('default_channel', $savechannel);
+ return true;
+ }
+
+ function doClearCache($command, $options, $params)
+ {
+ $cache_dir = $this->config->get('cache_dir');
+ $verbose = $this->config->get('verbose');
+ $output = '';
+ if (!file_exists($cache_dir) || !is_dir($cache_dir)) {
+ return $this->raiseError("$cache_dir does not exist or is not a directory");
+ }
+
+ if (!($dp = @opendir($cache_dir))) {
+ return $this->raiseError("opendir($cache_dir) failed: $php_errormsg");
+ }
+
+ if ($verbose >= 1) {
+ $output .= "reading directory $cache_dir\n";
+ }
+
+ $num = 0;
+ while ($ent = readdir($dp)) {
+ if (preg_match('/rest.cache(file|id)\\z/', $ent)) {
+ $path = $cache_dir . DIRECTORY_SEPARATOR . $ent;
+ if (file_exists($path)) {
+ $ok = @unlink($path);
+ } else {
+ $ok = false;
+ $php_errormsg = '';
+ }
+
+ if ($ok) {
+ if ($verbose >= 2) {
+ $output .= "deleted $path\n";
+ }
+ $num++;
+ } elseif ($verbose >= 1) {
+ $output .= "failed to delete $path $php_errormsg\n";
+ }
+ }
+ }
+
+ closedir($dp);
+ if ($verbose >= 1) {
+ $output .= "$num cache entries cleared\n";
+ }
+
+ $this->ui->outputData(rtrim($output), $command);
+ return $num;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Remote.xml b/includes/pear/PEAR/Command/Remote.xml
new file mode 100644
index 0000000..b4f6100
--- /dev/null
+++ b/includes/pear/PEAR/Command/Remote.xml
@@ -0,0 +1,109 @@
+<commands version="1.0">
+ <remote-info>
+ <summary>Information About Remote Packages</summary>
+ <function>doRemoteInfo</function>
+ <shortcut>ri</shortcut>
+ <options />
+ <doc>&lt;package&gt;
+Get details on a package from the server.</doc>
+ </remote-info>
+ <list-upgrades>
+ <summary>List Available Upgrades</summary>
+ <function>doListUpgrades</function>
+ <shortcut>lu</shortcut>
+ <options>
+ <channelinfo>
+ <shortopt>i</shortopt>
+ <doc>output fully channel-aware data, even on failure</doc>
+ </channelinfo>
+ </options>
+ <doc>[preferred_state]
+List releases on the server of packages you have installed where
+a newer version is available with the same release state (stable etc.)
+or the state passed as the second parameter.</doc>
+ </list-upgrades>
+ <remote-list>
+ <summary>List Remote Packages</summary>
+ <function>doRemoteList</function>
+ <shortcut>rl</shortcut>
+ <options>
+ <channel>
+ <shortopt>c</shortopt>
+ <doc>specify a channel other than the default channel</doc>
+ <arg>CHAN</arg>
+ </channel>
+ </options>
+ <doc>
+Lists the packages available on the configured server along with the
+latest stable release of each package.</doc>
+ </remote-list>
+ <search>
+ <summary>Search remote package database</summary>
+ <function>doSearch</function>
+ <shortcut>sp</shortcut>
+ <options>
+ <channel>
+ <shortopt>c</shortopt>
+ <doc>specify a channel other than the default channel</doc>
+ <arg>CHAN</arg>
+ </channel>
+ <allchannels>
+ <shortopt>a</shortopt>
+ <doc>search packages from all known channels</doc>
+ </allchannels>
+ <channelinfo>
+ <shortopt>i</shortopt>
+ <doc>output fully channel-aware data, even on failure</doc>
+ </channelinfo>
+ </options>
+ <doc>[packagename] [packageinfo]
+Lists all packages which match the search parameters. The first
+parameter is a fragment of a packagename. The default channel
+will be used unless explicitly overridden. The second parameter
+will be used to match any portion of the summary/description</doc>
+ </search>
+ <list-all>
+ <summary>List All Packages</summary>
+ <function>doListAll</function>
+ <shortcut>la</shortcut>
+ <options>
+ <channel>
+ <shortopt>c</shortopt>
+ <doc>specify a channel other than the default channel</doc>
+ <arg>CHAN</arg>
+ </channel>
+ <channelinfo>
+ <shortopt>i</shortopt>
+ <doc>output fully channel-aware data, even on failure</doc>
+ </channelinfo>
+ </options>
+ <doc>
+Lists the packages available on the configured server along with the
+latest stable release of each package.</doc>
+ </list-all>
+ <download>
+ <summary>Download Package</summary>
+ <function>doDownload</function>
+ <shortcut>d</shortcut>
+ <options>
+ <nocompress>
+ <shortopt>Z</shortopt>
+ <doc>download an uncompressed (.tar) file</doc>
+ </nocompress>
+ </options>
+ <doc>&lt;package&gt;...
+Download package tarballs. The files will be named as suggested by the
+server, for example if you download the DB package and the latest stable
+version of DB is 1.6.5, the downloaded file will be DB-1.6.5.tgz.</doc>
+ </download>
+ <clear-cache>
+ <summary>Clear Web Services Cache</summary>
+ <function>doClearCache</function>
+ <shortcut>cc</shortcut>
+ <options />
+ <doc>
+Clear the XML-RPC/REST cache. See also the cache_ttl configuration
+parameter.
+</doc>
+ </clear-cache>
+</commands> \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Test.php b/includes/pear/PEAR/Command/Test.php
new file mode 100644
index 0000000..fcfa6f6
--- /dev/null
+++ b/includes/pear/PEAR/Command/Test.php
@@ -0,0 +1,337 @@
+<?php
+/**
+ * PEAR_Command_Test (run-tests)
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Martin Jansen <mj@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Command/Common.php';
+
+/**
+ * PEAR commands for login/logout
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Martin Jansen <mj@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ */
+
+class PEAR_Command_Test extends PEAR_Command_Common
+{
+ var $commands = array(
+ 'run-tests' => array(
+ 'summary' => 'Run Regression Tests',
+ 'function' => 'doRunTests',
+ 'shortcut' => 'rt',
+ 'options' => array(
+ 'recur' => array(
+ 'shortopt' => 'r',
+ 'doc' => 'Run tests in child directories, recursively. 4 dirs deep maximum',
+ ),
+ 'ini' => array(
+ 'shortopt' => 'i',
+ 'doc' => 'actual string of settings to pass to php in format " -d setting=blah"',
+ 'arg' => 'SETTINGS'
+ ),
+ 'realtimelog' => array(
+ 'shortopt' => 'l',
+ 'doc' => 'Log test runs/results as they are run',
+ ),
+ 'quiet' => array(
+ 'shortopt' => 'q',
+ 'doc' => 'Only display detail for failed tests',
+ ),
+ 'simple' => array(
+ 'shortopt' => 's',
+ 'doc' => 'Display simple output for all tests',
+ ),
+ 'package' => array(
+ 'shortopt' => 'p',
+ 'doc' => 'Treat parameters as installed packages from which to run tests',
+ ),
+ 'phpunit' => array(
+ 'shortopt' => 'u',
+ 'doc' => 'Search parameters for AllTests.php, and use that to run phpunit-based tests
+If none is found, all .phpt tests will be tried instead.',
+ ),
+ 'tapoutput' => array(
+ 'shortopt' => 't',
+ 'doc' => 'Output run-tests.log in TAP-compliant format',
+ ),
+ 'cgi' => array(
+ 'shortopt' => 'c',
+ 'doc' => 'CGI php executable (needed for tests with POST/GET section)',
+ 'arg' => 'PHPCGI',
+ ),
+ 'coverage' => array(
+ 'shortopt' => 'x',
+ 'doc' => 'Generate a code coverage report (requires Xdebug 2.0.0+)',
+ ),
+ ),
+ 'doc' => '[testfile|dir ...]
+Run regression tests with PHP\'s regression testing script (run-tests.php).',
+ ),
+ );
+
+ var $output;
+
+ /**
+ * PEAR_Command_Test constructor.
+ *
+ * @access public
+ */
+ function PEAR_Command_Test(&$ui, &$config)
+ {
+ parent::PEAR_Command_Common($ui, $config);
+ }
+
+ function doRunTests($command, $options, $params)
+ {
+ if (isset($options['phpunit']) && isset($options['tapoutput'])) {
+ return $this->raiseError('ERROR: cannot use both --phpunit and --tapoutput at the same time');
+ }
+
+ require_once 'PEAR/Common.php';
+ require_once 'System.php';
+ $log = new PEAR_Common;
+ $log->ui = &$this->ui; // slightly hacky, but it will work
+ $tests = array();
+ $depth = isset($options['recur']) ? 14 : 1;
+
+ if (!count($params)) {
+ $params[] = '.';
+ }
+
+ if (isset($options['package'])) {
+ $oldparams = $params;
+ $params = array();
+ $reg = &$this->config->getRegistry();
+ foreach ($oldparams as $param) {
+ $pname = $reg->parsePackageName($param, $this->config->get('default_channel'));
+ if (PEAR::isError($pname)) {
+ return $this->raiseError($pname);
+ }
+
+ $package = &$reg->getPackage($pname['package'], $pname['channel']);
+ if (!$package) {
+ return PEAR::raiseError('Unknown package "' .
+ $reg->parsedPackageNameToString($pname) . '"');
+ }
+
+ $filelist = $package->getFilelist();
+ foreach ($filelist as $name => $atts) {
+ if (isset($atts['role']) && $atts['role'] != 'test') {
+ continue;
+ }
+
+ if (isset($options['phpunit']) && preg_match('/AllTests\.php\\z/i', $name)) {
+ $params[] = $atts['installed_as'];
+ continue;
+ } elseif (!preg_match('/\.phpt\\z/', $name)) {
+ continue;
+ }
+ $params[] = $atts['installed_as'];
+ }
+ }
+ }
+
+ foreach ($params as $p) {
+ if (is_dir($p)) {
+ if (isset($options['phpunit'])) {
+ $dir = System::find(array($p, '-type', 'f',
+ '-maxdepth', $depth,
+ '-name', 'AllTests.php'));
+ if (count($dir)) {
+ foreach ($dir as $p) {
+ $p = realpath($p);
+ if (!count($tests) ||
+ (count($tests) && strlen($p) < strlen($tests[0]))) {
+ // this is in a higher-level directory, use this one instead.
+ $tests = array($p);
+ }
+ }
+ }
+ continue;
+ }
+
+ $args = array($p, '-type', 'f', '-name', '*.phpt');
+ } else {
+ if (isset($options['phpunit'])) {
+ if (preg_match('/AllTests\.php\\z/i', $p)) {
+ $p = realpath($p);
+ if (!count($tests) ||
+ (count($tests) && strlen($p) < strlen($tests[0]))) {
+ // this is in a higher-level directory, use this one instead.
+ $tests = array($p);
+ }
+ }
+ continue;
+ }
+
+ if (file_exists($p) && preg_match('/\.phpt$/', $p)) {
+ $tests[] = $p;
+ continue;
+ }
+
+ if (!preg_match('/\.phpt\\z/', $p)) {
+ $p .= '.phpt';
+ }
+
+ $args = array(dirname($p), '-type', 'f', '-name', $p);
+ }
+
+ if (!isset($options['recur'])) {
+ $args[] = '-maxdepth';
+ $args[] = 1;
+ }
+
+ $dir = System::find($args);
+ $tests = array_merge($tests, $dir);
+ }
+
+ $ini_settings = '';
+ if (isset($options['ini'])) {
+ $ini_settings .= $options['ini'];
+ }
+
+ if (isset($_ENV['TEST_PHP_INCLUDE_PATH'])) {
+ $ini_settings .= " -d include_path={$_ENV['TEST_PHP_INCLUDE_PATH']}";
+ }
+
+ if ($ini_settings) {
+ $this->ui->outputData('Using INI settings: "' . $ini_settings . '"');
+ }
+
+ $skipped = $passed = $failed = array();
+ $tests_count = count($tests);
+ $this->ui->outputData('Running ' . $tests_count . ' tests', $command);
+ $start = time();
+ if (isset($options['realtimelog']) && file_exists('run-tests.log')) {
+ unlink('run-tests.log');
+ }
+
+ if (isset($options['tapoutput'])) {
+ $tap = '1..' . $tests_count . "\n";
+ }
+
+ require_once 'PEAR/RunTest.php';
+ $run = new PEAR_RunTest($log, $options);
+ $run->tests_count = $tests_count;
+
+ if (isset($options['coverage']) && extension_loaded('xdebug')){
+ $run->xdebug_loaded = true;
+ } else {
+ $run->xdebug_loaded = false;
+ }
+
+ $j = $i = 1;
+ foreach ($tests as $t) {
+ if (isset($options['realtimelog'])) {
+ $fp = @fopen('run-tests.log', 'a');
+ if ($fp) {
+ fwrite($fp, "Running test [$i / $tests_count] $t...");
+ fclose($fp);
+ }
+ }
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ if (isset($options['phpunit'])) {
+ $result = $run->runPHPUnit($t, $ini_settings);
+ } else {
+ $result = $run->run($t, $ini_settings, $j);
+ }
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($result)) {
+ $this->ui->log($result->getMessage());
+ continue;
+ }
+
+ if (isset($options['tapoutput'])) {
+ $tap .= $result[0] . ' ' . $i . $result[1] . "\n";
+ continue;
+ }
+
+ if (isset($options['realtimelog'])) {
+ $fp = @fopen('run-tests.log', 'a');
+ if ($fp) {
+ fwrite($fp, "$result\n");
+ fclose($fp);
+ }
+ }
+
+ if ($result == 'FAILED') {
+ $failed[] = $t;
+ }
+ if ($result == 'PASSED') {
+ $passed[] = $t;
+ }
+ if ($result == 'SKIPPED') {
+ $skipped[] = $t;
+ }
+
+ $j++;
+ }
+
+ $total = date('i:s', time() - $start);
+ if (isset($options['tapoutput'])) {
+ $fp = @fopen('run-tests.log', 'w');
+ if ($fp) {
+ fwrite($fp, $tap, strlen($tap));
+ fclose($fp);
+ $this->ui->outputData('wrote TAP-format log to "' .realpath('run-tests.log') .
+ '"', $command);
+ }
+ } else {
+ if (count($failed)) {
+ $output = "TOTAL TIME: $total\n";
+ $output .= count($passed) . " PASSED TESTS\n";
+ $output .= count($skipped) . " SKIPPED TESTS\n";
+ $output .= count($failed) . " FAILED TESTS:\n";
+ foreach ($failed as $failure) {
+ $output .= $failure . "\n";
+ }
+
+ $mode = isset($options['realtimelog']) ? 'a' : 'w';
+ $fp = @fopen('run-tests.log', $mode);
+
+ if ($fp) {
+ fwrite($fp, $output, strlen($output));
+ fclose($fp);
+ $this->ui->outputData('wrote log to "' . realpath('run-tests.log') . '"', $command);
+ }
+ } elseif (file_exists('run-tests.log') && !is_dir('run-tests.log')) {
+ @unlink('run-tests.log');
+ }
+ }
+ $this->ui->outputData('TOTAL TIME: ' . $total);
+ $this->ui->outputData(count($passed) . ' PASSED TESTS', $command);
+ $this->ui->outputData(count($skipped) . ' SKIPPED TESTS', $command);
+ if (count($failed)) {
+ $this->ui->outputData(count($failed) . ' FAILED TESTS:', $command);
+ foreach ($failed as $failure) {
+ $this->ui->outputData($failure, $command);
+ }
+ }
+
+ return true;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Command/Test.xml b/includes/pear/PEAR/Command/Test.xml
new file mode 100644
index 0000000..bbe9fcc
--- /dev/null
+++ b/includes/pear/PEAR/Command/Test.xml
@@ -0,0 +1,54 @@
+<commands version="1.0">
+ <run-tests>
+ <summary>Run Regression Tests</summary>
+ <function>doRunTests</function>
+ <shortcut>rt</shortcut>
+ <options>
+ <recur>
+ <shortopt>r</shortopt>
+ <doc>Run tests in child directories, recursively. 4 dirs deep maximum</doc>
+ </recur>
+ <ini>
+ <shortopt>i</shortopt>
+ <doc>actual string of settings to pass to php in format &quot; -d setting=blah&quot;</doc>
+ <arg>SETTINGS</arg>
+ </ini>
+ <realtimelog>
+ <shortopt>l</shortopt>
+ <doc>Log test runs/results as they are run</doc>
+ </realtimelog>
+ <quiet>
+ <shortopt>q</shortopt>
+ <doc>Only display detail for failed tests</doc>
+ </quiet>
+ <simple>
+ <shortopt>s</shortopt>
+ <doc>Display simple output for all tests</doc>
+ </simple>
+ <package>
+ <shortopt>p</shortopt>
+ <doc>Treat parameters as installed packages from which to run tests</doc>
+ </package>
+ <phpunit>
+ <shortopt>u</shortopt>
+ <doc>Search parameters for AllTests.php, and use that to run phpunit-based tests
+If none is found, all .phpt tests will be tried instead.</doc>
+ </phpunit>
+ <tapoutput>
+ <shortopt>t</shortopt>
+ <doc>Output run-tests.log in TAP-compliant format</doc>
+ </tapoutput>
+ <cgi>
+ <shortopt>c</shortopt>
+ <doc>CGI php executable (needed for tests with POST/GET section)</doc>
+ <arg>PHPCGI</arg>
+ </cgi>
+ <coverage>
+ <shortopt>x</shortopt>
+ <doc>Generate a code coverage report (requires Xdebug 2.0.0+)</doc>
+ </coverage>
+ </options>
+ <doc>[testfile|dir ...]
+Run regression tests with PHP&#039;s regression testing script (run-tests.php).</doc>
+ </run-tests>
+</commands> \ No newline at end of file
diff --git a/includes/pear/PEAR/Common.php b/includes/pear/PEAR/Common.php
new file mode 100644
index 0000000..f98bf2c
--- /dev/null
+++ b/includes/pear/PEAR/Common.php
@@ -0,0 +1,837 @@
+<?php
+/**
+ * PEAR_Common, the base class for the PEAR Installer
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V. V. Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1.0
+ * @deprecated File deprecated since Release 1.4.0a1
+ */
+
+/**
+ * Include error handling
+ */
+require_once 'PEAR.php';
+
+/**
+ * PEAR_Common error when an invalid PHP file is passed to PEAR_Common::analyzeSourceCode()
+ */
+define('PEAR_COMMON_ERROR_INVALIDPHP', 1);
+define('_PEAR_COMMON_PACKAGE_NAME_PREG', '[A-Za-z][a-zA-Z0-9_]+');
+define('PEAR_COMMON_PACKAGE_NAME_PREG', '/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '\\z/');
+
+// this should allow: 1, 1.0, 1.0RC1, 1.0dev, 1.0dev123234234234, 1.0a1, 1.0b1, 1.0pl1
+define('_PEAR_COMMON_PACKAGE_VERSION_PREG', '\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?');
+define('PEAR_COMMON_PACKAGE_VERSION_PREG', '/^' . _PEAR_COMMON_PACKAGE_VERSION_PREG . '\\z/i');
+
+// XXX far from perfect :-)
+define('_PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '(' . _PEAR_COMMON_PACKAGE_NAME_PREG .
+ ')(-([.0-9a-zA-Z]+))?');
+define('PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '/^' . _PEAR_COMMON_PACKAGE_DOWNLOAD_PREG .
+ '\\z/');
+
+define('_PEAR_CHANNELS_NAME_PREG', '[A-Za-z][a-zA-Z0-9\.]+');
+define('PEAR_CHANNELS_NAME_PREG', '/^' . _PEAR_CHANNELS_NAME_PREG . '\\z/');
+
+// this should allow any dns or IP address, plus a path - NO UNDERSCORES ALLOWED
+define('_PEAR_CHANNELS_SERVER_PREG', '[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(\/[a-zA-Z0-9\-]+)*');
+define('PEAR_CHANNELS_SERVER_PREG', '/^' . _PEAR_CHANNELS_SERVER_PREG . '\\z/i');
+
+define('_PEAR_CHANNELS_PACKAGE_PREG', '(' ._PEAR_CHANNELS_SERVER_PREG . ')\/('
+ . _PEAR_COMMON_PACKAGE_NAME_PREG . ')');
+define('PEAR_CHANNELS_PACKAGE_PREG', '/^' . _PEAR_CHANNELS_PACKAGE_PREG . '\\z/i');
+
+define('_PEAR_COMMON_CHANNEL_DOWNLOAD_PREG', '(' . _PEAR_CHANNELS_NAME_PREG . ')::('
+ . _PEAR_COMMON_PACKAGE_NAME_PREG . ')(-([.0-9a-zA-Z]+))?');
+define('PEAR_COMMON_CHANNEL_DOWNLOAD_PREG', '/^' . _PEAR_COMMON_CHANNEL_DOWNLOAD_PREG . '\\z/');
+
+/**
+ * List of temporary files and directories registered by
+ * PEAR_Common::addTempFile().
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_tempfiles'] = array();
+
+/**
+ * Valid maintainer roles
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_maintainer_roles'] = array('lead','developer','contributor','helper');
+
+/**
+ * Valid release states
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_release_states'] = array('alpha','beta','stable','snapshot','devel');
+
+/**
+ * Valid dependency types
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_dependency_types'] = array('pkg','ext','php','prog','ldlib','rtlib','os','websrv','sapi');
+
+/**
+ * Valid dependency relations
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_dependency_relations'] = array('has','eq','lt','le','gt','ge','not', 'ne');
+
+/**
+ * Valid file roles
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_file_roles'] = array('php','ext','test','doc','data','src','script');
+
+/**
+ * Valid replacement types
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_replacement_types'] = array('php-const', 'pear-config', 'package-info');
+
+/**
+ * Valid "provide" types
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_provide_types'] = array('ext', 'prog', 'class', 'function', 'feature', 'api');
+
+/**
+ * Valid "provide" types
+ * @var array
+ */
+$GLOBALS['_PEAR_Common_script_phases'] = array('pre-install', 'post-install', 'pre-uninstall', 'post-uninstall', 'pre-build', 'post-build', 'pre-configure', 'post-configure', 'pre-setup', 'post-setup');
+
+/**
+ * Class providing common functionality for PEAR administration classes.
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V. V. Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ * @deprecated This class will disappear, and its components will be spread
+ * into smaller classes, like the AT&T breakup, as of Release 1.4.0a1
+ */
+class PEAR_Common extends PEAR
+{
+ /**
+ * User Interface object (PEAR_Frontend_* class). If null,
+ * the log() method uses print.
+ * @var object
+ */
+ var $ui = null;
+
+ /**
+ * Configuration object (PEAR_Config).
+ * @var PEAR_Config
+ */
+ var $config = null;
+
+ /** stack of elements, gives some sort of XML context */
+ var $element_stack = array();
+
+ /** name of currently parsed XML element */
+ var $current_element;
+
+ /** array of attributes of the currently parsed XML element */
+ var $current_attributes = array();
+
+ /** assoc with information about a package */
+ var $pkginfo = array();
+
+ var $current_path = null;
+
+ /**
+ * Flag variable used to mark a valid package file
+ * @var boolean
+ * @access private
+ */
+ var $_validPackageFile;
+
+ /**
+ * PEAR_Common constructor
+ *
+ * @access public
+ */
+ function PEAR_Common()
+ {
+ parent::PEAR();
+ $this->config = &PEAR_Config::singleton();
+ $this->debug = $this->config->get('verbose');
+ }
+
+ /**
+ * PEAR_Common destructor
+ *
+ * @access private
+ */
+ function _PEAR_Common()
+ {
+ // doesn't work due to bug #14744
+ //$tempfiles = $this->_tempfiles;
+ $tempfiles =& $GLOBALS['_PEAR_Common_tempfiles'];
+ while ($file = array_shift($tempfiles)) {
+ if (@is_dir($file)) {
+ if (!class_exists('System')) {
+ require_once 'System.php';
+ }
+
+ System::rm(array('-rf', $file));
+ } elseif (file_exists($file)) {
+ unlink($file);
+ }
+ }
+ }
+
+ /**
+ * Register a temporary file or directory. When the destructor is
+ * executed, all registered temporary files and directories are
+ * removed.
+ *
+ * @param string $file name of file or directory
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function addTempFile($file)
+ {
+ if (!class_exists('PEAR_Frontend')) {
+ require_once 'PEAR/Frontend.php';
+ }
+ PEAR_Frontend::addTempFile($file);
+ }
+
+ /**
+ * Wrapper to System::mkDir(), creates a directory as well as
+ * any necessary parent directories.
+ *
+ * @param string $dir directory name
+ *
+ * @return bool TRUE on success, or a PEAR error
+ *
+ * @access public
+ */
+ function mkDirHier($dir)
+ {
+ // Only used in Installer - move it there ?
+ $this->log(2, "+ create dir $dir");
+ if (!class_exists('System')) {
+ require_once 'System.php';
+ }
+ return System::mkDir(array('-p', $dir));
+ }
+
+ /**
+ * Logging method.
+ *
+ * @param int $level log level (0 is quiet, higher is noisier)
+ * @param string $msg message to write to the log
+ *
+ * @return void
+ *
+ * @access public
+ * @static
+ */
+ function log($level, $msg, $append_crlf = true)
+ {
+ if ($this->debug >= $level) {
+ if (!class_exists('PEAR_Frontend')) {
+ require_once 'PEAR/Frontend.php';
+ }
+
+ $ui = &PEAR_Frontend::singleton();
+ if (is_a($ui, 'PEAR_Frontend')) {
+ $ui->log($msg, $append_crlf);
+ } else {
+ print "$msg\n";
+ }
+ }
+ }
+
+ /**
+ * Create and register a temporary directory.
+ *
+ * @param string $tmpdir (optional) Directory to use as tmpdir.
+ * Will use system defaults (for example
+ * /tmp or c:\windows\temp) if not specified
+ *
+ * @return string name of created directory
+ *
+ * @access public
+ */
+ function mkTempDir($tmpdir = '')
+ {
+ $topt = $tmpdir ? array('-t', $tmpdir) : array();
+ $topt = array_merge($topt, array('-d', 'pear'));
+ if (!class_exists('System')) {
+ require_once 'System.php';
+ }
+
+ if (!$tmpdir = System::mktemp($topt)) {
+ return false;
+ }
+
+ $this->addTempFile($tmpdir);
+ return $tmpdir;
+ }
+
+ /**
+ * Set object that represents the frontend to be used.
+ *
+ * @param object Reference of the frontend object
+ * @return void
+ * @access public
+ */
+ function setFrontendObject(&$ui)
+ {
+ $this->ui = &$ui;
+ }
+
+ /**
+ * Return an array containing all of the states that are more stable than
+ * or equal to the passed in state
+ *
+ * @param string Release state
+ * @param boolean Determines whether to include $state in the list
+ * @return false|array False if $state is not a valid release state
+ */
+ function betterStates($state, $include = false)
+ {
+ static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable');
+ $i = array_search($state, $states);
+ if ($i === false) {
+ return false;
+ }
+ if ($include) {
+ $i--;
+ }
+ return array_slice($states, $i + 1);
+ }
+
+ /**
+ * Get the valid roles for a PEAR package maintainer
+ *
+ * @return array
+ * @static
+ */
+ function getUserRoles()
+ {
+ return $GLOBALS['_PEAR_Common_maintainer_roles'];
+ }
+
+ /**
+ * Get the valid package release states of packages
+ *
+ * @return array
+ * @static
+ */
+ function getReleaseStates()
+ {
+ return $GLOBALS['_PEAR_Common_release_states'];
+ }
+
+ /**
+ * Get the implemented dependency types (php, ext, pkg etc.)
+ *
+ * @return array
+ * @static
+ */
+ function getDependencyTypes()
+ {
+ return $GLOBALS['_PEAR_Common_dependency_types'];
+ }
+
+ /**
+ * Get the implemented dependency relations (has, lt, ge etc.)
+ *
+ * @return array
+ * @static
+ */
+ function getDependencyRelations()
+ {
+ return $GLOBALS['_PEAR_Common_dependency_relations'];
+ }
+
+ /**
+ * Get the implemented file roles
+ *
+ * @return array
+ * @static
+ */
+ function getFileRoles()
+ {
+ return $GLOBALS['_PEAR_Common_file_roles'];
+ }
+
+ /**
+ * Get the implemented file replacement types in
+ *
+ * @return array
+ * @static
+ */
+ function getReplacementTypes()
+ {
+ return $GLOBALS['_PEAR_Common_replacement_types'];
+ }
+
+ /**
+ * Get the implemented file replacement types in
+ *
+ * @return array
+ * @static
+ */
+ function getProvideTypes()
+ {
+ return $GLOBALS['_PEAR_Common_provide_types'];
+ }
+
+ /**
+ * Get the implemented file replacement types in
+ *
+ * @return array
+ * @static
+ */
+ function getScriptPhases()
+ {
+ return $GLOBALS['_PEAR_Common_script_phases'];
+ }
+
+ /**
+ * Test whether a string contains a valid package name.
+ *
+ * @param string $name the package name to test
+ *
+ * @return bool
+ *
+ * @access public
+ */
+ function validPackageName($name)
+ {
+ return (bool)preg_match(PEAR_COMMON_PACKAGE_NAME_PREG, $name);
+ }
+
+ /**
+ * Test whether a string contains a valid package version.
+ *
+ * @param string $ver the package version to test
+ *
+ * @return bool
+ *
+ * @access public
+ */
+ function validPackageVersion($ver)
+ {
+ return (bool)preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver);
+ }
+
+ /**
+ * @param string $path relative or absolute include path
+ * @return boolean
+ * @static
+ */
+ function isIncludeable($path)
+ {
+ if (file_exists($path) && is_readable($path)) {
+ return true;
+ }
+
+ $ipath = explode(PATH_SEPARATOR, ini_get('include_path'));
+ foreach ($ipath as $include) {
+ $test = realpath($include . DIRECTORY_SEPARATOR . $path);
+ if (file_exists($test) && is_readable($test)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ function _postProcessChecks($pf)
+ {
+ if (!PEAR::isError($pf)) {
+ return $this->_postProcessValidPackagexml($pf);
+ }
+
+ $errs = $pf->getUserinfo();
+ if (is_array($errs)) {
+ foreach ($errs as $error) {
+ $e = $this->raiseError($error['message'], $error['code'], null, null, $error);
+ }
+ }
+
+ return $pf;
+ }
+
+ /**
+ * Returns information about a package file. Expects the name of
+ * a gzipped tar file as input.
+ *
+ * @param string $file name of .tgz file
+ *
+ * @return array array with package information
+ *
+ * @access public
+ * @deprecated use PEAR_PackageFile->fromTgzFile() instead
+ *
+ */
+ function infoFromTgzFile($file)
+ {
+ $packagefile = &new PEAR_PackageFile($this->config);
+ $pf = &$packagefile->fromTgzFile($file, PEAR_VALIDATE_NORMAL);
+ return $this->_postProcessChecks($pf);
+ }
+
+ /**
+ * Returns information about a package file. Expects the name of
+ * a package xml file as input.
+ *
+ * @param string $descfile name of package xml file
+ *
+ * @return array array with package information
+ *
+ * @access public
+ * @deprecated use PEAR_PackageFile->fromPackageFile() instead
+ *
+ */
+ function infoFromDescriptionFile($descfile)
+ {
+ $packagefile = &new PEAR_PackageFile($this->config);
+ $pf = &$packagefile->fromPackageFile($descfile, PEAR_VALIDATE_NORMAL);
+ return $this->_postProcessChecks($pf);
+ }
+
+ /**
+ * Returns information about a package file. Expects the contents
+ * of a package xml file as input.
+ *
+ * @param string $data contents of package.xml file
+ *
+ * @return array array with package information
+ *
+ * @access public
+ * @deprecated use PEAR_PackageFile->fromXmlstring() instead
+ *
+ */
+ function infoFromString($data)
+ {
+ $packagefile = &new PEAR_PackageFile($this->config);
+ $pf = &$packagefile->fromXmlString($data, PEAR_VALIDATE_NORMAL, false);
+ return $this->_postProcessChecks($pf);
+ }
+
+ /**
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * @return array
+ */
+ function _postProcessValidPackagexml(&$pf)
+ {
+ if (!is_a($pf, 'PEAR_PackageFile_v2')) {
+ $this->pkginfo = $pf->toArray();
+ return $this->pkginfo;
+ }
+
+ // sort of make this into a package.xml 1.0-style array
+ // changelog is not converted to old format.
+ $arr = $pf->toArray(true);
+ $arr = array_merge($arr, $arr['old']);
+ unset($arr['old'], $arr['xsdversion'], $arr['contents'], $arr['compatible'],
+ $arr['channel'], $arr['uri'], $arr['dependencies'], $arr['phprelease'],
+ $arr['extsrcrelease'], $arr['zendextsrcrelease'], $arr['extbinrelease'],
+ $arr['zendextbinrelease'], $arr['bundle'], $arr['lead'], $arr['developer'],
+ $arr['helper'], $arr['contributor']);
+ $arr['filelist'] = $pf->getFilelist();
+ $this->pkginfo = $arr;
+ return $arr;
+ }
+
+ /**
+ * Returns package information from different sources
+ *
+ * This method is able to extract information about a package
+ * from a .tgz archive or from a XML package definition file.
+ *
+ * @access public
+ * @param string Filename of the source ('package.xml', '<package>.tgz')
+ * @return string
+ * @deprecated use PEAR_PackageFile->fromAnyFile() instead
+ */
+ function infoFromAny($info)
+ {
+ if (is_string($info) && file_exists($info)) {
+ $packagefile = &new PEAR_PackageFile($this->config);
+ $pf = &$packagefile->fromAnyFile($info, PEAR_VALIDATE_NORMAL);
+ if (PEAR::isError($pf)) {
+ $errs = $pf->getUserinfo();
+ if (is_array($errs)) {
+ foreach ($errs as $error) {
+ $e = $this->raiseError($error['message'], $error['code'], null, null, $error);
+ }
+ }
+
+ return $pf;
+ }
+
+ return $this->_postProcessValidPackagexml($pf);
+ }
+
+ return $info;
+ }
+
+ /**
+ * Return an XML document based on the package info (as returned
+ * by the PEAR_Common::infoFrom* methods).
+ *
+ * @param array $pkginfo package info
+ *
+ * @return string XML data
+ *
+ * @access public
+ * @deprecated use a PEAR_PackageFile_v* object's generator instead
+ */
+ function xmlFromInfo($pkginfo)
+ {
+ $config = &PEAR_Config::singleton();
+ $packagefile = &new PEAR_PackageFile($config);
+ $pf = &$packagefile->fromArray($pkginfo);
+ $gen = &$pf->getDefaultGenerator();
+ return $gen->toXml(PEAR_VALIDATE_PACKAGING);
+ }
+
+ /**
+ * Validate XML package definition file.
+ *
+ * @param string $info Filename of the package archive or of the
+ * package definition file
+ * @param array $errors Array that will contain the errors
+ * @param array $warnings Array that will contain the warnings
+ * @param string $dir_prefix (optional) directory where source files
+ * may be found, or empty if they are not available
+ * @access public
+ * @return boolean
+ * @deprecated use the validation of PEAR_PackageFile objects
+ */
+ function validatePackageInfo($info, &$errors, &$warnings, $dir_prefix = '')
+ {
+ $config = &PEAR_Config::singleton();
+ $packagefile = &new PEAR_PackageFile($config);
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ if (strpos($info, '<?xml') !== false) {
+ $pf = &$packagefile->fromXmlString($info, PEAR_VALIDATE_NORMAL, '');
+ } else {
+ $pf = &$packagefile->fromAnyFile($info, PEAR_VALIDATE_NORMAL);
+ }
+
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($pf)) {
+ $errs = $pf->getUserinfo();
+ if (is_array($errs)) {
+ foreach ($errs as $error) {
+ if ($error['level'] == 'error') {
+ $errors[] = $error['message'];
+ } else {
+ $warnings[] = $error['message'];
+ }
+ }
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Build a "provides" array from data returned by
+ * analyzeSourceCode(). The format of the built array is like
+ * this:
+ *
+ * array(
+ * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'),
+ * ...
+ * )
+ *
+ *
+ * @param array $srcinfo array with information about a source file
+ * as returned by the analyzeSourceCode() method.
+ *
+ * @return void
+ *
+ * @access public
+ *
+ */
+ function buildProvidesArray($srcinfo)
+ {
+ $file = basename($srcinfo['source_file']);
+ $pn = '';
+ if (isset($this->_packageName)) {
+ $pn = $this->_packageName;
+ }
+
+ $pnl = strlen($pn);
+ foreach ($srcinfo['declared_classes'] as $class) {
+ $key = "class;$class";
+ if (isset($this->pkginfo['provides'][$key])) {
+ continue;
+ }
+
+ $this->pkginfo['provides'][$key] =
+ array('file'=> $file, 'type' => 'class', 'name' => $class);
+ if (isset($srcinfo['inheritance'][$class])) {
+ $this->pkginfo['provides'][$key]['extends'] =
+ $srcinfo['inheritance'][$class];
+ }
+ }
+
+ foreach ($srcinfo['declared_methods'] as $class => $methods) {
+ foreach ($methods as $method) {
+ $function = "$class::$method";
+ $key = "function;$function";
+ if ($method{0} == '_' || !strcasecmp($method, $class) ||
+ isset($this->pkginfo['provides'][$key])) {
+ continue;
+ }
+
+ $this->pkginfo['provides'][$key] =
+ array('file'=> $file, 'type' => 'function', 'name' => $function);
+ }
+ }
+
+ foreach ($srcinfo['declared_functions'] as $function) {
+ $key = "function;$function";
+ if ($function{0} == '_' || isset($this->pkginfo['provides'][$key])) {
+ continue;
+ }
+
+ if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) {
+ $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\"";
+ }
+
+ $this->pkginfo['provides'][$key] =
+ array('file'=> $file, 'type' => 'function', 'name' => $function);
+ }
+ }
+
+ /**
+ * Analyze the source code of the given PHP file
+ *
+ * @param string Filename of the PHP file
+ * @return mixed
+ * @access public
+ */
+ function analyzeSourceCode($file)
+ {
+ if (!class_exists('PEAR_PackageFile_v2_Validator')) {
+ require_once 'PEAR/PackageFile/v2/Validator.php';
+ }
+
+ $a = new PEAR_PackageFile_v2_Validator;
+ return $a->analyzeSourceCode($file);
+ }
+
+ function detectDependencies($any, $status_callback = null)
+ {
+ if (!function_exists("token_get_all")) {
+ return false;
+ }
+
+ if (PEAR::isError($info = $this->infoFromAny($any))) {
+ return $this->raiseError($info);
+ }
+
+ if (!is_array($info)) {
+ return false;
+ }
+
+ $deps = array();
+ $used_c = $decl_c = $decl_f = $decl_m = array();
+ foreach ($info['filelist'] as $file => $fa) {
+ $tmp = $this->analyzeSourceCode($file);
+ $used_c = @array_merge($used_c, $tmp['used_classes']);
+ $decl_c = @array_merge($decl_c, $tmp['declared_classes']);
+ $decl_f = @array_merge($decl_f, $tmp['declared_functions']);
+ $decl_m = @array_merge($decl_m, $tmp['declared_methods']);
+ $inheri = @array_merge($inheri, $tmp['inheritance']);
+ }
+
+ $used_c = array_unique($used_c);
+ $decl_c = array_unique($decl_c);
+ $undecl_c = array_diff($used_c, $decl_c);
+
+ return array('used_classes' => $used_c,
+ 'declared_classes' => $decl_c,
+ 'declared_methods' => $decl_m,
+ 'declared_functions' => $decl_f,
+ 'undeclared_classes' => $undecl_c,
+ 'inheritance' => $inheri,
+ );
+ }
+
+ /**
+ * Download a file through HTTP. Considers suggested file name in
+ * Content-disposition: header and can run a callback function for
+ * different events. The callback will be called with two
+ * parameters: the callback type, and parameters. The implemented
+ * callback types are:
+ *
+ * 'setup' called at the very beginning, parameter is a UI object
+ * that should be used for all output
+ * 'message' the parameter is a string with an informational message
+ * 'saveas' may be used to save with a different file name, the
+ * parameter is the filename that is about to be used.
+ * If a 'saveas' callback returns a non-empty string,
+ * that file name will be used as the filename instead.
+ * Note that $save_dir will not be affected by this, only
+ * the basename of the file.
+ * 'start' download is starting, parameter is number of bytes
+ * that are expected, or -1 if unknown
+ * 'bytesread' parameter is the number of bytes read so far
+ * 'done' download is complete, parameter is the total number
+ * of bytes read
+ * 'connfailed' if the TCP connection fails, this callback is called
+ * with array(host,port,errno,errmsg)
+ * 'writefailed' if writing to disk fails, this callback is called
+ * with array(destfile,errmsg)
+ *
+ * If an HTTP proxy has been configured (http_proxy PEAR_Config
+ * setting), the proxy will be used.
+ *
+ * @param string $url the URL to download
+ * @param object $ui PEAR_Frontend_* instance
+ * @param object $config PEAR_Config instance
+ * @param string $save_dir (optional) directory to save file in
+ * @param mixed $callback (optional) function/method to call for status
+ * updates
+ *
+ * @return string Returns the full path of the downloaded file or a PEAR
+ * error on failure. If the error is caused by
+ * socket-related errors, the error object will
+ * have the fsockopen error code available through
+ * getCode().
+ *
+ * @access public
+ * @deprecated in favor of PEAR_Downloader::downloadHttp()
+ */
+ function downloadHttp($url, &$ui, $save_dir = '.', $callback = null)
+ {
+ if (!class_exists('PEAR_Downloader')) {
+ require_once 'PEAR/Downloader.php';
+ }
+ return PEAR_Downloader::downloadHttp($url, $ui, $save_dir, $callback);
+ }
+}
+
+require_once 'PEAR/Config.php';
+require_once 'PEAR/PackageFile.php'; \ No newline at end of file
diff --git a/includes/pear/PEAR/Config.php b/includes/pear/PEAR/Config.php
new file mode 100644
index 0000000..f6fb9ee
--- /dev/null
+++ b/includes/pear/PEAR/Config.php
@@ -0,0 +1,2092 @@
+<?php
+/**
+ * PEAR_Config, customized configuration handling for the PEAR Installer
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**
+ * Required for error handling
+ */
+require_once 'PEAR.php';
+require_once 'PEAR/Registry.php';
+require_once 'PEAR/Installer/Role.php';
+require_once 'System.php';
+
+/**
+ * Last created PEAR_Config instance.
+ * @var object
+ */
+$GLOBALS['_PEAR_Config_instance'] = null;
+if (!defined('PEAR_INSTALL_DIR') || !PEAR_INSTALL_DIR) {
+ $PEAR_INSTALL_DIR = PHP_LIBDIR . DIRECTORY_SEPARATOR . 'pear';
+} else {
+ $PEAR_INSTALL_DIR = PEAR_INSTALL_DIR;
+}
+
+// Below we define constants with default values for all configuration
+// parameters except username/password. All of them can have their
+// defaults set through environment variables. The reason we use the
+// PHP_ prefix is for some security, PHP protects environment
+// variables starting with PHP_*.
+
+// default channel and preferred mirror is based on whether we are invoked through
+// the "pear" or the "pecl" command
+if (!defined('PEAR_RUNTYPE')) {
+ define('PEAR_RUNTYPE', 'pear');
+}
+
+if (PEAR_RUNTYPE == 'pear') {
+ define('PEAR_CONFIG_DEFAULT_CHANNEL', 'pear.php.net');
+} else {
+ define('PEAR_CONFIG_DEFAULT_CHANNEL', 'pecl.php.net');
+}
+
+if (getenv('PHP_PEAR_SYSCONF_DIR')) {
+ define('PEAR_CONFIG_SYSCONFDIR', getenv('PHP_PEAR_SYSCONF_DIR'));
+} elseif (getenv('SystemRoot')) {
+ define('PEAR_CONFIG_SYSCONFDIR', getenv('SystemRoot'));
+} else {
+ define('PEAR_CONFIG_SYSCONFDIR', PHP_SYSCONFDIR);
+}
+
+// Default for master_server
+if (getenv('PHP_PEAR_MASTER_SERVER')) {
+ define('PEAR_CONFIG_DEFAULT_MASTER_SERVER', getenv('PHP_PEAR_MASTER_SERVER'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_MASTER_SERVER', 'pear.php.net');
+}
+
+// Default for http_proxy
+if (getenv('PHP_PEAR_HTTP_PROXY')) {
+ define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', getenv('PHP_PEAR_HTTP_PROXY'));
+} elseif (getenv('http_proxy')) {
+ define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', getenv('http_proxy'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_HTTP_PROXY', '');
+}
+
+// Default for php_dir
+if (getenv('PHP_PEAR_INSTALL_DIR')) {
+ define('PEAR_CONFIG_DEFAULT_PHP_DIR', getenv('PHP_PEAR_INSTALL_DIR'));
+} else {
+ if (@file_exists($PEAR_INSTALL_DIR) && is_dir($PEAR_INSTALL_DIR)) {
+ define('PEAR_CONFIG_DEFAULT_PHP_DIR', $PEAR_INSTALL_DIR);
+ } else {
+ define('PEAR_CONFIG_DEFAULT_PHP_DIR', $PEAR_INSTALL_DIR);
+ }
+}
+
+// Default for ext_dir
+if (getenv('PHP_PEAR_EXTENSION_DIR')) {
+ define('PEAR_CONFIG_DEFAULT_EXT_DIR', getenv('PHP_PEAR_EXTENSION_DIR'));
+} else {
+ if (ini_get('extension_dir')) {
+ define('PEAR_CONFIG_DEFAULT_EXT_DIR', ini_get('extension_dir'));
+ } elseif (defined('PEAR_EXTENSION_DIR') &&
+ file_exists(PEAR_EXTENSION_DIR) && is_dir(PEAR_EXTENSION_DIR)) {
+ define('PEAR_CONFIG_DEFAULT_EXT_DIR', PEAR_EXTENSION_DIR);
+ } elseif (defined('PHP_EXTENSION_DIR')) {
+ define('PEAR_CONFIG_DEFAULT_EXT_DIR', PHP_EXTENSION_DIR);
+ } else {
+ define('PEAR_CONFIG_DEFAULT_EXT_DIR', '.');
+ }
+}
+
+// Default for doc_dir
+if (getenv('PHP_PEAR_DOC_DIR')) {
+ define('PEAR_CONFIG_DEFAULT_DOC_DIR', getenv('PHP_PEAR_DOC_DIR'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_DOC_DIR',
+ $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'docs');
+}
+
+// Default for bin_dir
+if (getenv('PHP_PEAR_BIN_DIR')) {
+ define('PEAR_CONFIG_DEFAULT_BIN_DIR', getenv('PHP_PEAR_BIN_DIR'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_BIN_DIR', PHP_BINDIR);
+}
+
+// Default for data_dir
+if (getenv('PHP_PEAR_DATA_DIR')) {
+ define('PEAR_CONFIG_DEFAULT_DATA_DIR', getenv('PHP_PEAR_DATA_DIR'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_DATA_DIR',
+ $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'data');
+}
+
+// Default for cfg_dir
+if (getenv('PHP_PEAR_CFG_DIR')) {
+ define('PEAR_CONFIG_DEFAULT_CFG_DIR', getenv('PHP_PEAR_CFG_DIR'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_CFG_DIR',
+ $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'cfg');
+}
+
+// Default for www_dir
+if (getenv('PHP_PEAR_WWW_DIR')) {
+ define('PEAR_CONFIG_DEFAULT_WWW_DIR', getenv('PHP_PEAR_WWW_DIR'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_WWW_DIR',
+ $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'www');
+}
+
+// Default for test_dir
+if (getenv('PHP_PEAR_TEST_DIR')) {
+ define('PEAR_CONFIG_DEFAULT_TEST_DIR', getenv('PHP_PEAR_TEST_DIR'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_TEST_DIR',
+ $PEAR_INSTALL_DIR.DIRECTORY_SEPARATOR.'tests');
+}
+
+// Default for temp_dir
+if (getenv('PHP_PEAR_TEMP_DIR')) {
+ define('PEAR_CONFIG_DEFAULT_TEMP_DIR', getenv('PHP_PEAR_TEMP_DIR'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_TEMP_DIR',
+ System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' .
+ DIRECTORY_SEPARATOR . 'temp');
+}
+
+// Default for cache_dir
+if (getenv('PHP_PEAR_CACHE_DIR')) {
+ define('PEAR_CONFIG_DEFAULT_CACHE_DIR', getenv('PHP_PEAR_CACHE_DIR'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_CACHE_DIR',
+ System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' .
+ DIRECTORY_SEPARATOR . 'cache');
+}
+
+// Default for download_dir
+if (getenv('PHP_PEAR_DOWNLOAD_DIR')) {
+ define('PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR', getenv('PHP_PEAR_DOWNLOAD_DIR'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR',
+ System::tmpdir() . DIRECTORY_SEPARATOR . 'pear' .
+ DIRECTORY_SEPARATOR . 'download');
+}
+
+// Default for php_bin
+if (getenv('PHP_PEAR_PHP_BIN')) {
+ define('PEAR_CONFIG_DEFAULT_PHP_BIN', getenv('PHP_PEAR_PHP_BIN'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_PHP_BIN', PEAR_CONFIG_DEFAULT_BIN_DIR.
+ DIRECTORY_SEPARATOR.'php'.(OS_WINDOWS ? '.exe' : ''));
+}
+
+// Default for verbose
+if (getenv('PHP_PEAR_VERBOSE')) {
+ define('PEAR_CONFIG_DEFAULT_VERBOSE', getenv('PHP_PEAR_VERBOSE'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_VERBOSE', 1);
+}
+
+// Default for preferred_state
+if (getenv('PHP_PEAR_PREFERRED_STATE')) {
+ define('PEAR_CONFIG_DEFAULT_PREFERRED_STATE', getenv('PHP_PEAR_PREFERRED_STATE'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_PREFERRED_STATE', 'stable');
+}
+
+// Default for umask
+if (getenv('PHP_PEAR_UMASK')) {
+ define('PEAR_CONFIG_DEFAULT_UMASK', getenv('PHP_PEAR_UMASK'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_UMASK', decoct(umask()));
+}
+
+// Default for cache_ttl
+if (getenv('PHP_PEAR_CACHE_TTL')) {
+ define('PEAR_CONFIG_DEFAULT_CACHE_TTL', getenv('PHP_PEAR_CACHE_TTL'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_CACHE_TTL', 3600);
+}
+
+// Default for sig_type
+if (getenv('PHP_PEAR_SIG_TYPE')) {
+ define('PEAR_CONFIG_DEFAULT_SIG_TYPE', getenv('PHP_PEAR_SIG_TYPE'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_SIG_TYPE', 'gpg');
+}
+
+// Default for sig_bin
+if (getenv('PHP_PEAR_SIG_BIN')) {
+ define('PEAR_CONFIG_DEFAULT_SIG_BIN', getenv('PHP_PEAR_SIG_BIN'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_SIG_BIN',
+ System::which(
+ 'gpg', OS_WINDOWS ? 'c:\gnupg\gpg.exe' : '/usr/local/bin/gpg'));
+}
+
+// Default for sig_keydir
+if (getenv('PHP_PEAR_SIG_KEYDIR')) {
+ define('PEAR_CONFIG_DEFAULT_SIG_KEYDIR', getenv('PHP_PEAR_SIG_KEYDIR'));
+} else {
+ define('PEAR_CONFIG_DEFAULT_SIG_KEYDIR',
+ PEAR_CONFIG_SYSCONFDIR . DIRECTORY_SEPARATOR . 'pearkeys');
+}
+
+/**
+ * This is a class for storing configuration data, keeping track of
+ * which are system-defined, user-defined or defaulted.
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ */
+class PEAR_Config extends PEAR
+{
+ /**
+ * Array of config files used.
+ *
+ * @var array layer => config file
+ */
+ var $files = array(
+ 'system' => '',
+ 'user' => '',
+ );
+
+ var $layers = array();
+
+ /**
+ * Configuration data, two-dimensional array where the first
+ * dimension is the config layer ('user', 'system' and 'default'),
+ * and the second dimension is keyname => value.
+ *
+ * The order in the first dimension is important! Earlier
+ * layers will shadow later ones when a config value is
+ * requested (if a 'user' value exists, it will be returned first,
+ * then 'system' and finally 'default').
+ *
+ * @var array layer => array(keyname => value, ...)
+ */
+ var $configuration = array(
+ 'user' => array(),
+ 'system' => array(),
+ 'default' => array(),
+ );
+
+ /**
+ * Configuration values that can be set for a channel
+ *
+ * All other configuration values can only have a global value
+ * @var array
+ * @access private
+ */
+ var $_channelConfigInfo = array(
+ 'php_dir', 'ext_dir', 'doc_dir', 'bin_dir', 'data_dir', 'cfg_dir',
+ 'test_dir', 'www_dir', 'php_bin', 'php_prefix', 'php_suffix', 'username',
+ 'password', 'verbose', 'preferred_state', 'umask', 'preferred_mirror', 'php_ini'
+ );
+
+ /**
+ * Channels that can be accessed
+ * @see setChannels()
+ * @var array
+ * @access private
+ */
+ var $_channels = array('pear.php.net', 'pecl.php.net', '__uri');
+
+ /**
+ * This variable is used to control the directory values returned
+ * @see setInstallRoot();
+ * @var string|false
+ * @access private
+ */
+ var $_installRoot = false;
+
+ /**
+ * If requested, this will always refer to the registry
+ * contained in php_dir
+ * @var PEAR_Registry
+ */
+ var $_registry = array();
+
+ /**
+ * @var array
+ * @access private
+ */
+ var $_regInitialized = array();
+
+ /**
+ * @var bool
+ * @access private
+ */
+ var $_noRegistry = false;
+
+ /**
+ * amount of errors found while parsing config
+ * @var integer
+ * @access private
+ */
+ var $_errorsFound = 0;
+ var $_lastError = null;
+
+ /**
+ * Information about the configuration data. Stores the type,
+ * default value and a documentation string for each configuration
+ * value.
+ *
+ * @var array layer => array(infotype => value, ...)
+ */
+ var $configuration_info = array(
+ // Channels/Internet Access
+ 'default_channel' => array(
+ 'type' => 'string',
+ 'default' => PEAR_CONFIG_DEFAULT_CHANNEL,
+ 'doc' => 'the default channel to use for all non explicit commands',
+ 'prompt' => 'Default Channel',
+ 'group' => 'Internet Access',
+ ),
+ 'preferred_mirror' => array(
+ 'type' => 'string',
+ 'default' => PEAR_CONFIG_DEFAULT_CHANNEL,
+ 'doc' => 'the default server or mirror to use for channel actions',
+ 'prompt' => 'Default Channel Mirror',
+ 'group' => 'Internet Access',
+ ),
+ 'remote_config' => array(
+ 'type' => 'password',
+ 'default' => '',
+ 'doc' => 'ftp url of remote configuration file to use for synchronized install',
+ 'prompt' => 'Remote Configuration File',
+ 'group' => 'Internet Access',
+ ),
+ 'auto_discover' => array(
+ 'type' => 'integer',
+ 'default' => 1,
+ 'doc' => 'whether to automatically discover new channels',
+ 'prompt' => 'Auto-discover new Channels',
+ 'group' => 'Internet Access',
+ ),
+ // Internet Access
+ 'master_server' => array(
+ 'type' => 'string',
+ 'default' => 'pear.php.net',
+ 'doc' => 'name of the main PEAR server [NOT USED IN THIS VERSION]',
+ 'prompt' => 'PEAR server [DEPRECATED]',
+ 'group' => 'Internet Access',
+ ),
+ 'http_proxy' => array(
+ 'type' => 'string',
+ 'default' => PEAR_CONFIG_DEFAULT_HTTP_PROXY,
+ 'doc' => 'HTTP proxy (host:port) to use when downloading packages',
+ 'prompt' => 'HTTP Proxy Server Address',
+ 'group' => 'Internet Access',
+ ),
+ // File Locations
+ 'php_dir' => array(
+ 'type' => 'directory',
+ 'default' => PEAR_CONFIG_DEFAULT_PHP_DIR,
+ 'doc' => 'directory where .php files are installed',
+ 'prompt' => 'PEAR directory',
+ 'group' => 'File Locations',
+ ),
+ 'ext_dir' => array(
+ 'type' => 'directory',
+ 'default' => PEAR_CONFIG_DEFAULT_EXT_DIR,
+ 'doc' => 'directory where loadable extensions are installed',
+ 'prompt' => 'PHP extension directory',
+ 'group' => 'File Locations',
+ ),
+ 'doc_dir' => array(
+ 'type' => 'directory',
+ 'default' => PEAR_CONFIG_DEFAULT_DOC_DIR,
+ 'doc' => 'directory where documentation is installed',
+ 'prompt' => 'PEAR documentation directory',
+ 'group' => 'File Locations',
+ ),
+ 'bin_dir' => array(
+ 'type' => 'directory',
+ 'default' => PEAR_CONFIG_DEFAULT_BIN_DIR,
+ 'doc' => 'directory where executables are installed',
+ 'prompt' => 'PEAR executables directory',
+ 'group' => 'File Locations',
+ ),
+ 'data_dir' => array(
+ 'type' => 'directory',
+ 'default' => PEAR_CONFIG_DEFAULT_DATA_DIR,
+ 'doc' => 'directory where data files are installed',
+ 'prompt' => 'PEAR data directory',
+ 'group' => 'File Locations (Advanced)',
+ ),
+ 'cfg_dir' => array(
+ 'type' => 'directory',
+ 'default' => PEAR_CONFIG_DEFAULT_CFG_DIR,
+ 'doc' => 'directory where modifiable configuration files are installed',
+ 'prompt' => 'PEAR configuration file directory',
+ 'group' => 'File Locations (Advanced)',
+ ),
+ 'www_dir' => array(
+ 'type' => 'directory',
+ 'default' => PEAR_CONFIG_DEFAULT_WWW_DIR,
+ 'doc' => 'directory where www frontend files (html/js) are installed',
+ 'prompt' => 'PEAR www files directory',
+ 'group' => 'File Locations (Advanced)',
+ ),
+ 'test_dir' => array(
+ 'type' => 'directory',
+ 'default' => PEAR_CONFIG_DEFAULT_TEST_DIR,
+ 'doc' => 'directory where regression tests are installed',
+ 'prompt' => 'PEAR test directory',
+ 'group' => 'File Locations (Advanced)',
+ ),
+ 'cache_dir' => array(
+ 'type' => 'directory',
+ 'default' => PEAR_CONFIG_DEFAULT_CACHE_DIR,
+ 'doc' => 'directory which is used for web service cache',
+ 'prompt' => 'PEAR Installer cache directory',
+ 'group' => 'File Locations (Advanced)',
+ ),
+ 'temp_dir' => array(
+ 'type' => 'directory',
+ 'default' => PEAR_CONFIG_DEFAULT_TEMP_DIR,
+ 'doc' => 'directory which is used for all temp files',
+ 'prompt' => 'PEAR Installer temp directory',
+ 'group' => 'File Locations (Advanced)',
+ ),
+ 'download_dir' => array(
+ 'type' => 'directory',
+ 'default' => PEAR_CONFIG_DEFAULT_DOWNLOAD_DIR,
+ 'doc' => 'directory which is used for all downloaded files',
+ 'prompt' => 'PEAR Installer download directory',
+ 'group' => 'File Locations (Advanced)',
+ ),
+ 'php_bin' => array(
+ 'type' => 'file',
+ 'default' => PEAR_CONFIG_DEFAULT_PHP_BIN,
+ 'doc' => 'PHP CLI/CGI binary for executing scripts',
+ 'prompt' => 'PHP CLI/CGI binary',
+ 'group' => 'File Locations (Advanced)',
+ ),
+ 'php_prefix' => array(
+ 'type' => 'string',
+ 'default' => '',
+ 'doc' => '--program-prefix for php_bin\'s ./configure, used for pecl installs',
+ 'prompt' => '--program-prefix passed to PHP\'s ./configure',
+ 'group' => 'File Locations (Advanced)',
+ ),
+ 'php_suffix' => array(
+ 'type' => 'string',
+ 'default' => '',
+ 'doc' => '--program-suffix for php_bin\'s ./configure, used for pecl installs',
+ 'prompt' => '--program-suffix passed to PHP\'s ./configure',
+ 'group' => 'File Locations (Advanced)',
+ ),
+ 'php_ini' => array(
+ 'type' => 'file',
+ 'default' => '',
+ 'doc' => 'location of php.ini in which to enable PECL extensions on install',
+ 'prompt' => 'php.ini location',
+ 'group' => 'File Locations (Advanced)',
+ ),
+ // Maintainers
+ 'username' => array(
+ 'type' => 'string',
+ 'default' => '',
+ 'doc' => '(maintainers) your PEAR account name',
+ 'prompt' => 'PEAR username (for maintainers)',
+ 'group' => 'Maintainers',
+ ),
+ 'password' => array(
+ 'type' => 'password',
+ 'default' => '',
+ 'doc' => '(maintainers) your PEAR account password',
+ 'prompt' => 'PEAR password (for maintainers)',
+ 'group' => 'Maintainers',
+ ),
+ // Advanced
+ 'verbose' => array(
+ 'type' => 'integer',
+ 'default' => PEAR_CONFIG_DEFAULT_VERBOSE,
+ 'doc' => 'verbosity level
+0: really quiet
+1: somewhat quiet
+2: verbose
+3: debug',
+ 'prompt' => 'Debug Log Level',
+ 'group' => 'Advanced',
+ ),
+ 'preferred_state' => array(
+ 'type' => 'set',
+ 'default' => PEAR_CONFIG_DEFAULT_PREFERRED_STATE,
+ 'doc' => 'the installer will prefer releases with this state when installing packages without a version or state specified',
+ 'valid_set' => array(
+ 'stable', 'beta', 'alpha', 'devel', 'snapshot'),
+ 'prompt' => 'Preferred Package State',
+ 'group' => 'Advanced',
+ ),
+ 'umask' => array(
+ 'type' => 'mask',
+ 'default' => PEAR_CONFIG_DEFAULT_UMASK,
+ 'doc' => 'umask used when creating files (Unix-like systems only)',
+ 'prompt' => 'Unix file mask',
+ 'group' => 'Advanced',
+ ),
+ 'cache_ttl' => array(
+ 'type' => 'integer',
+ 'default' => PEAR_CONFIG_DEFAULT_CACHE_TTL,
+ 'doc' => 'amount of secs where the local cache is used and not updated',
+ 'prompt' => 'Cache TimeToLive',
+ 'group' => 'Advanced',
+ ),
+ 'sig_type' => array(
+ 'type' => 'set',
+ 'default' => PEAR_CONFIG_DEFAULT_SIG_TYPE,
+ 'doc' => 'which package signature mechanism to use',
+ 'valid_set' => array('gpg'),
+ 'prompt' => 'Package Signature Type',
+ 'group' => 'Maintainers',
+ ),
+ 'sig_bin' => array(
+ 'type' => 'string',
+ 'default' => PEAR_CONFIG_DEFAULT_SIG_BIN,
+ 'doc' => 'which package signature mechanism to use',
+ 'prompt' => 'Signature Handling Program',
+ 'group' => 'Maintainers',
+ ),
+ 'sig_keyid' => array(
+ 'type' => 'string',
+ 'default' => '',
+ 'doc' => 'which key to use for signing with',
+ 'prompt' => 'Signature Key Id',
+ 'group' => 'Maintainers',
+ ),
+ 'sig_keydir' => array(
+ 'type' => 'directory',
+ 'default' => PEAR_CONFIG_DEFAULT_SIG_KEYDIR,
+ 'doc' => 'directory where signature keys are located',
+ 'prompt' => 'Signature Key Directory',
+ 'group' => 'Maintainers',
+ ),
+ // __channels is reserved - used for channel-specific configuration
+ );
+
+ /**
+ * Constructor.
+ *
+ * @param string file to read user-defined options from
+ * @param string file to read system-wide defaults from
+ * @param bool determines whether a registry object "follows"
+ * the value of php_dir (is automatically created
+ * and moved when php_dir is changed)
+ * @param bool if true, fails if configuration files cannot be loaded
+ *
+ * @access public
+ *
+ * @see PEAR_Config::singleton
+ */
+ function PEAR_Config($user_file = '', $system_file = '', $ftp_file = false,
+ $strict = true)
+ {
+ $this->PEAR();
+ PEAR_Installer_Role::initializeConfig($this);
+ $sl = DIRECTORY_SEPARATOR;
+ if (empty($user_file)) {
+ if (OS_WINDOWS) {
+ $user_file = PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.ini';
+ } else {
+ $user_file = getenv('HOME') . $sl . '.pearrc';
+ }
+ }
+
+ if (empty($system_file)) {
+ $system_file = PEAR_CONFIG_SYSCONFDIR . $sl;
+ if (OS_WINDOWS) {
+ $system_file .= 'pearsys.ini';
+ } else {
+ $system_file .= 'pear.conf';
+ }
+ }
+
+ $this->layers = array_keys($this->configuration);
+ $this->files['user'] = $user_file;
+ $this->files['system'] = $system_file;
+ if ($user_file && file_exists($user_file)) {
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $this->readConfigFile($user_file, 'user', $strict);
+ $this->popErrorHandling();
+ if ($this->_errorsFound > 0) {
+ return;
+ }
+ }
+
+ if ($system_file && @file_exists($system_file)) {
+ $this->mergeConfigFile($system_file, false, 'system', $strict);
+ if ($this->_errorsFound > 0) {
+ return;
+ }
+
+ }
+
+ if (!$ftp_file) {
+ $ftp_file = $this->get('remote_config');
+ }
+
+ if ($ftp_file && defined('PEAR_REMOTEINSTALL_OK')) {
+ $this->readFTPConfigFile($ftp_file);
+ }
+
+ foreach ($this->configuration_info as $key => $info) {
+ $this->configuration['default'][$key] = $info['default'];
+ }
+
+ $this->_registry['default'] = &new PEAR_Registry($this->configuration['default']['php_dir']);
+ $this->_registry['default']->setConfig($this, false);
+ $this->_regInitialized['default'] = false;
+ //$GLOBALS['_PEAR_Config_instance'] = &$this;
+ }
+
+ /**
+ * Return the default locations of user and system configuration files
+ * @static
+ */
+ function getDefaultConfigFiles()
+ {
+ $sl = DIRECTORY_SEPARATOR;
+ if (OS_WINDOWS) {
+ return array(
+ 'user' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.ini',
+ 'system' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pearsys.ini'
+ );
+ }
+
+ return array(
+ 'user' => getenv('HOME') . $sl . '.pearrc',
+ 'system' => PEAR_CONFIG_SYSCONFDIR . $sl . 'pear.conf'
+ );
+ }
+
+ /**
+ * Static singleton method. If you want to keep only one instance
+ * of this class in use, this method will give you a reference to
+ * the last created PEAR_Config object if one exists, or create a
+ * new object.
+ *
+ * @param string (optional) file to read user-defined options from
+ * @param string (optional) file to read system-wide defaults from
+ *
+ * @return object an existing or new PEAR_Config instance
+ *
+ * @access public
+ *
+ * @see PEAR_Config::PEAR_Config
+ */
+ function &singleton($user_file = '', $system_file = '', $strict = true)
+ {
+ if (is_object($GLOBALS['_PEAR_Config_instance'])) {
+ return $GLOBALS['_PEAR_Config_instance'];
+ }
+
+ $t_conf = &new PEAR_Config($user_file, $system_file, false, $strict);
+ if ($t_conf->_errorsFound > 0) {
+ return $t_conf->lastError;
+ }
+
+ $GLOBALS['_PEAR_Config_instance'] = &$t_conf;
+ return $GLOBALS['_PEAR_Config_instance'];
+ }
+
+ /**
+ * Determine whether any configuration files have been detected, and whether a
+ * registry object can be retrieved from this configuration.
+ * @return bool
+ * @since PEAR 1.4.0a1
+ */
+ function validConfiguration()
+ {
+ if ($this->isDefinedLayer('user') || $this->isDefinedLayer('system')) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Reads configuration data from a file. All existing values in
+ * the config layer are discarded and replaced with data from the
+ * file.
+ * @param string file to read from, if NULL or not specified, the
+ * last-used file for the same layer (second param) is used
+ * @param string config layer to insert data into ('user' or 'system')
+ * @return bool TRUE on success or a PEAR error on failure
+ */
+ function readConfigFile($file = null, $layer = 'user', $strict = true)
+ {
+ if (empty($this->files[$layer])) {
+ return $this->raiseError("unknown config layer `$layer'");
+ }
+
+ if ($file === null) {
+ $file = $this->files[$layer];
+ }
+
+ $data = $this->_readConfigDataFrom($file);
+ if (PEAR::isError($data)) {
+ if (!$strict) {
+ return true;
+ }
+
+ $this->_errorsFound++;
+ $this->lastError = $data;
+
+ return $data;
+ }
+
+ $this->files[$layer] = $file;
+ $this->_decodeInput($data);
+ $this->configuration[$layer] = $data;
+ $this->_setupChannels();
+ if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) {
+ $this->_registry[$layer] = &new PEAR_Registry($phpdir);
+ $this->_registry[$layer]->setConfig($this, false);
+ $this->_regInitialized[$layer] = false;
+ } else {
+ unset($this->_registry[$layer]);
+ }
+ return true;
+ }
+
+ /**
+ * @param string url to the remote config file, like ftp://www.example.com/pear/config.ini
+ * @return true|PEAR_Error
+ */
+ function readFTPConfigFile($path)
+ {
+ do { // poor man's try
+ if (!class_exists('PEAR_FTP')) {
+ if (!class_exists('PEAR_Common')) {
+ require_once 'PEAR/Common.php';
+ }
+ if (PEAR_Common::isIncludeable('PEAR/FTP.php')) {
+ require_once 'PEAR/FTP.php';
+ }
+ }
+
+ if (!class_exists('PEAR_FTP')) {
+ return PEAR::raiseError('PEAR_RemoteInstaller must be installed to use remote config');
+ }
+
+ $this->_ftp = &new PEAR_FTP;
+ $this->_ftp->pushErrorHandling(PEAR_ERROR_RETURN);
+ $e = $this->_ftp->init($path);
+ if (PEAR::isError($e)) {
+ $this->_ftp->popErrorHandling();
+ return $e;
+ }
+
+ $tmp = System::mktemp('-d');
+ PEAR_Common::addTempFile($tmp);
+ $e = $this->_ftp->get(basename($path), $tmp . DIRECTORY_SEPARATOR .
+ 'pear.ini', false, FTP_BINARY);
+ if (PEAR::isError($e)) {
+ $this->_ftp->popErrorHandling();
+ return $e;
+ }
+
+ PEAR_Common::addTempFile($tmp . DIRECTORY_SEPARATOR . 'pear.ini');
+ $this->_ftp->disconnect();
+ $this->_ftp->popErrorHandling();
+ $this->files['ftp'] = $tmp . DIRECTORY_SEPARATOR . 'pear.ini';
+ $e = $this->readConfigFile(null, 'ftp');
+ if (PEAR::isError($e)) {
+ return $e;
+ }
+
+ $fail = array();
+ foreach ($this->configuration_info as $key => $val) {
+ if (in_array($this->getGroup($key),
+ array('File Locations', 'File Locations (Advanced)')) &&
+ $this->getType($key) == 'directory') {
+ // any directory configs must be set for this to work
+ if (!isset($this->configuration['ftp'][$key])) {
+ $fail[] = $key;
+ }
+ }
+ }
+
+ if (!count($fail)) {
+ return true;
+ }
+
+ $fail = '"' . implode('", "', $fail) . '"';
+ unset($this->files['ftp']);
+ unset($this->configuration['ftp']);
+ return PEAR::raiseError('ERROR: Ftp configuration file must set all ' .
+ 'directory configuration variables. These variables were not set: ' .
+ $fail);
+ } while (false); // poor man's catch
+ unset($this->files['ftp']);
+ return PEAR::raiseError('no remote host specified');
+ }
+
+ /**
+ * Reads the existing configurations and creates the _channels array from it
+ */
+ function _setupChannels()
+ {
+ $set = array_flip(array_values($this->_channels));
+ foreach ($this->configuration as $layer => $data) {
+ $i = 1000;
+ if (isset($data['__channels']) && is_array($data['__channels'])) {
+ foreach ($data['__channels'] as $channel => $info) {
+ $set[$channel] = $i++;
+ }
+ }
+ }
+ $this->_channels = array_values(array_flip($set));
+ $this->setChannels($this->_channels);
+ }
+
+ function deleteChannel($channel)
+ {
+ $ch = strtolower($channel);
+ foreach ($this->configuration as $layer => $data) {
+ if (isset($data['__channels']) && isset($data['__channels'][$ch])) {
+ unset($this->configuration[$layer]['__channels'][$ch]);
+ }
+ }
+
+ $this->_channels = array_flip($this->_channels);
+ unset($this->_channels[$ch]);
+ $this->_channels = array_flip($this->_channels);
+ }
+
+ /**
+ * Merges data into a config layer from a file. Does the same
+ * thing as readConfigFile, except it does not replace all
+ * existing values in the config layer.
+ * @param string file to read from
+ * @param bool whether to overwrite existing data (default TRUE)
+ * @param string config layer to insert data into ('user' or 'system')
+ * @param string if true, errors are returned if file opening fails
+ * @return bool TRUE on success or a PEAR error on failure
+ */
+ function mergeConfigFile($file, $override = true, $layer = 'user', $strict = true)
+ {
+ if (empty($this->files[$layer])) {
+ return $this->raiseError("unknown config layer `$layer'");
+ }
+
+ if ($file === null) {
+ $file = $this->files[$layer];
+ }
+
+ $data = $this->_readConfigDataFrom($file);
+ if (PEAR::isError($data)) {
+ if (!$strict) {
+ return true;
+ }
+
+ $this->_errorsFound++;
+ $this->lastError = $data;
+
+ return $data;
+ }
+
+ $this->_decodeInput($data);
+ if ($override) {
+ $this->configuration[$layer] =
+ PEAR_Config::arrayMergeRecursive($this->configuration[$layer], $data);
+ } else {
+ $this->configuration[$layer] =
+ PEAR_Config::arrayMergeRecursive($data, $this->configuration[$layer]);
+ }
+
+ $this->_setupChannels();
+ if (!$this->_noRegistry && ($phpdir = $this->get('php_dir', $layer, 'pear.php.net'))) {
+ $this->_registry[$layer] = &new PEAR_Registry($phpdir);
+ $this->_registry[$layer]->setConfig($this, false);
+ $this->_regInitialized[$layer] = false;
+ } else {
+ unset($this->_registry[$layer]);
+ }
+ return true;
+ }
+
+ /**
+ * @param array
+ * @param array
+ * @return array
+ * @static
+ */
+ function arrayMergeRecursive($arr2, $arr1)
+ {
+ $ret = array();
+ foreach ($arr2 as $key => $data) {
+ if (!isset($arr1[$key])) {
+ $ret[$key] = $data;
+ unset($arr1[$key]);
+ continue;
+ }
+ if (is_array($data)) {
+ if (!is_array($arr1[$key])) {
+ $ret[$key] = $arr1[$key];
+ unset($arr1[$key]);
+ continue;
+ }
+ $ret[$key] = PEAR_Config::arrayMergeRecursive($arr1[$key], $arr2[$key]);
+ unset($arr1[$key]);
+ }
+ }
+
+ return array_merge($ret, $arr1);
+ }
+
+ /**
+ * Writes data into a config layer from a file.
+ *
+ * @param string|null file to read from, or null for default
+ * @param string config layer to insert data into ('user' or
+ * 'system')
+ * @param string|null data to write to config file or null for internal data [DEPRECATED]
+ * @return bool TRUE on success or a PEAR error on failure
+ */
+ function writeConfigFile($file = null, $layer = 'user', $data = null)
+ {
+ $this->_lazyChannelSetup($layer);
+ if ($layer == 'both' || $layer == 'all') {
+ foreach ($this->files as $type => $file) {
+ $err = $this->writeConfigFile($file, $type, $data);
+ if (PEAR::isError($err)) {
+ return $err;
+ }
+ }
+ return true;
+ }
+
+ if (empty($this->files[$layer])) {
+ return $this->raiseError("unknown config file type `$layer'");
+ }
+
+ if ($file === null) {
+ $file = $this->files[$layer];
+ }
+
+ $data = ($data === null) ? $this->configuration[$layer] : $data;
+ $this->_encodeOutput($data);
+ $opt = array('-p', dirname($file));
+ if (!@System::mkDir($opt)) {
+ return $this->raiseError("could not create directory: " . dirname($file));
+ }
+
+ if (file_exists($file) && is_file($file) && !is_writeable($file)) {
+ return $this->raiseError("no write access to $file!");
+ }
+
+ $fp = @fopen($file, "w");
+ if (!$fp) {
+ return $this->raiseError("PEAR_Config::writeConfigFile fopen('$file','w') failed ($php_errormsg)");
+ }
+
+ $contents = "#PEAR_Config 0.9\n" . serialize($data);
+ if (!@fwrite($fp, $contents)) {
+ return $this->raiseError("PEAR_Config::writeConfigFile: fwrite failed ($php_errormsg)");
+ }
+ return true;
+ }
+
+ /**
+ * Reads configuration data from a file and returns the parsed data
+ * in an array.
+ *
+ * @param string file to read from
+ * @return array configuration data or a PEAR error on failure
+ */
+ private function _readConfigDataFrom($file)
+ {
+ $fp = false;
+ if (file_exists($file)) {
+ $fp = @fopen($file, "r");
+ }
+
+ if (!$fp) {
+ return $this->raiseError("PEAR_Config::readConfigFile fopen('$file','r') failed");
+ }
+
+ $size = filesize($file);
+ fclose($fp);
+ $contents = file_get_contents($file);
+ if (empty($contents)) {
+ return $this->raiseError('Configuration file "' . $file . '" is empty');
+ }
+
+ $version = false;
+ if (preg_match('/^#PEAR_Config\s+(\S+)\s+/si', $contents, $matches)) {
+ $version = $matches[1];
+ $contents = substr($contents, strlen($matches[0]));
+ } else {
+ // Museum config file
+ if (substr($contents,0,2) == 'a:') {
+ $version = '0.1';
+ }
+ }
+
+ if ($version && version_compare("$version", '1', '<')) {
+ // no '@', it is possible that unserialize
+ // raises a notice but it seems to block IO to
+ // STDOUT if a '@' is used and a notice is raise
+ $data = unserialize($contents);
+
+ if (!is_array($data) && !$data) {
+ if ($contents == serialize(false)) {
+ $data = array();
+ } else {
+ $err = $this->raiseError("PEAR_Config: bad data in $file");
+ return $err;
+ }
+ }
+ if (!is_array($data)) {
+ if (strlen(trim($contents)) > 0) {
+ $error = "PEAR_Config: bad data in $file";
+ $err = $this->raiseError($error);
+ return $err;
+ }
+
+ $data = array();
+ }
+ // add parsing of newer formats here...
+ } else {
+ $err = $this->raiseError("$file: unknown version `$version'");
+ return $err;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Gets the file used for storing the config for a layer
+ *
+ * @param string $layer 'user' or 'system'
+ */
+ function getConfFile($layer)
+ {
+ return $this->files[$layer];
+ }
+
+ /**
+ * @param string Configuration class name, used for detecting duplicate calls
+ * @param array information on a role as parsed from its xml file
+ * @return true|PEAR_Error
+ * @access private
+ */
+ function _addConfigVars($class, $vars)
+ {
+ static $called = array();
+ if (isset($called[$class])) {
+ return;
+ }
+
+ $called[$class] = 1;
+ if (count($vars) > 3) {
+ return $this->raiseError('Roles can only define 3 new config variables or less');
+ }
+
+ foreach ($vars as $name => $var) {
+ if (!is_array($var)) {
+ return $this->raiseError('Configuration information must be an array');
+ }
+
+ if (!isset($var['type'])) {
+ return $this->raiseError('Configuration information must contain a type');
+ } elseif (!in_array($var['type'],
+ array('string', 'mask', 'password', 'directory', 'file', 'set'))) {
+ return $this->raiseError(
+ 'Configuration type must be one of directory, file, string, ' .
+ 'mask, set, or password');
+ }
+ if (!isset($var['default'])) {
+ return $this->raiseError(
+ 'Configuration information must contain a default value ("default" index)');
+ }
+
+ if (is_array($var['default'])) {
+ $real_default = '';
+ foreach ($var['default'] as $config_var => $val) {
+ if (strpos($config_var, 'text') === 0) {
+ $real_default .= $val;
+ } elseif (strpos($config_var, 'constant') === 0) {
+ if (!defined($val)) {
+ return $this->raiseError(
+ 'Unknown constant "' . $val . '" requested in ' .
+ 'default value for configuration variable "' .
+ $name . '"');
+ }
+
+ $real_default .= constant($val);
+ } elseif (isset($this->configuration_info[$config_var])) {
+ $real_default .=
+ $this->configuration_info[$config_var]['default'];
+ } else {
+ return $this->raiseError(
+ 'Unknown request for "' . $config_var . '" value in ' .
+ 'default value for configuration variable "' .
+ $name . '"');
+ }
+ }
+ $var['default'] = $real_default;
+ }
+
+ if ($var['type'] == 'integer') {
+ $var['default'] = (integer) $var['default'];
+ }
+
+ if (!isset($var['doc'])) {
+ return $this->raiseError(
+ 'Configuration information must contain a summary ("doc" index)');
+ }
+
+ if (!isset($var['prompt'])) {
+ return $this->raiseError(
+ 'Configuration information must contain a simple prompt ("prompt" index)');
+ }
+
+ if (!isset($var['group'])) {
+ return $this->raiseError(
+ 'Configuration information must contain a simple group ("group" index)');
+ }
+
+ if (isset($this->configuration_info[$name])) {
+ return $this->raiseError('Configuration variable "' . $name .
+ '" already exists');
+ }
+
+ $this->configuration_info[$name] = $var;
+ // fix bug #7351: setting custom config variable in a channel fails
+ $this->_channelConfigInfo[] = $name;
+ }
+
+ return true;
+ }
+
+ /**
+ * Encodes/scrambles configuration data before writing to files.
+ * Currently, 'password' values will be base64-encoded as to avoid
+ * that people spot cleartext passwords by accident.
+ *
+ * @param array (reference) array to encode values in
+ * @return bool TRUE on success
+ * @access private
+ */
+ function _encodeOutput(&$data)
+ {
+ foreach ($data as $key => $value) {
+ if ($key == '__channels') {
+ foreach ($data['__channels'] as $channel => $blah) {
+ $this->_encodeOutput($data['__channels'][$channel]);
+ }
+ }
+
+ if (!isset($this->configuration_info[$key])) {
+ continue;
+ }
+
+ $type = $this->configuration_info[$key]['type'];
+ switch ($type) {
+ // we base64-encode passwords so they are at least
+ // not shown in plain by accident
+ case 'password': {
+ $data[$key] = base64_encode($data[$key]);
+ break;
+ }
+ case 'mask': {
+ $data[$key] = octdec($data[$key]);
+ break;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Decodes/unscrambles configuration data after reading from files.
+ *
+ * @param array (reference) array to encode values in
+ * @return bool TRUE on success
+ * @access private
+ *
+ * @see PEAR_Config::_encodeOutput
+ */
+ function _decodeInput(&$data)
+ {
+ if (!is_array($data)) {
+ return true;
+ }
+
+ foreach ($data as $key => $value) {
+ if ($key == '__channels') {
+ foreach ($data['__channels'] as $channel => $blah) {
+ $this->_decodeInput($data['__channels'][$channel]);
+ }
+ }
+
+ if (!isset($this->configuration_info[$key])) {
+ continue;
+ }
+
+ $type = $this->configuration_info[$key]['type'];
+ switch ($type) {
+ case 'password': {
+ $data[$key] = base64_decode($data[$key]);
+ break;
+ }
+ case 'mask': {
+ $data[$key] = decoct($data[$key]);
+ break;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Retrieve the default channel.
+ *
+ * On startup, channels are not initialized, so if the default channel is not
+ * pear.php.net, then initialize the config.
+ * @param string registry layer
+ * @return string|false
+ */
+ function getDefaultChannel($layer = null)
+ {
+ $ret = false;
+ if ($layer === null) {
+ foreach ($this->layers as $layer) {
+ if (isset($this->configuration[$layer]['default_channel'])) {
+ $ret = $this->configuration[$layer]['default_channel'];
+ break;
+ }
+ }
+ } elseif (isset($this->configuration[$layer]['default_channel'])) {
+ $ret = $this->configuration[$layer]['default_channel'];
+ }
+
+ if ($ret == 'pear.php.net' && defined('PEAR_RUNTYPE') && PEAR_RUNTYPE == 'pecl') {
+ $ret = 'pecl.php.net';
+ }
+
+ if ($ret) {
+ if ($ret != 'pear.php.net') {
+ $this->_lazyChannelSetup();
+ }
+
+ return $ret;
+ }
+
+ return PEAR_CONFIG_DEFAULT_CHANNEL;
+ }
+
+ /**
+ * Returns a configuration value, prioritizing layers as per the
+ * layers property.
+ *
+ * @param string config key
+ * @return mixed the config value, or NULL if not found
+ * @access public
+ */
+ function get($key, $layer = null, $channel = false)
+ {
+ if (!isset($this->configuration_info[$key])) {
+ return null;
+ }
+
+ if ($key == '__channels') {
+ return null;
+ }
+
+ if ($key == 'default_channel') {
+ return $this->getDefaultChannel($layer);
+ }
+
+ if (!$channel) {
+ $channel = $this->getDefaultChannel();
+ } elseif ($channel != 'pear.php.net') {
+ $this->_lazyChannelSetup();
+ }
+ $channel = strtolower($channel);
+
+ $test = (in_array($key, $this->_channelConfigInfo)) ?
+ $this->_getChannelValue($key, $layer, $channel) :
+ null;
+ if ($test !== null) {
+ if ($this->_installRoot) {
+ if (in_array($this->getGroup($key),
+ array('File Locations', 'File Locations (Advanced)')) &&
+ $this->getType($key) == 'directory') {
+ return $this->_prependPath($test, $this->_installRoot);
+ }
+ }
+ return $test;
+ }
+
+ if ($layer === null) {
+ foreach ($this->layers as $layer) {
+ if (isset($this->configuration[$layer][$key])) {
+ $test = $this->configuration[$layer][$key];
+ if ($this->_installRoot) {
+ if (in_array($this->getGroup($key),
+ array('File Locations', 'File Locations (Advanced)')) &&
+ $this->getType($key) == 'directory') {
+ return $this->_prependPath($test, $this->_installRoot);
+ }
+ }
+
+ if ($key == 'preferred_mirror') {
+ $reg = &$this->getRegistry();
+ if (is_object($reg)) {
+ $chan = &$reg->getChannel($channel);
+ if (PEAR::isError($chan)) {
+ return $channel;
+ }
+
+ if (!$chan->getMirror($test) && $chan->getName() != $test) {
+ return $channel; // mirror does not exist
+ }
+ }
+ }
+ return $test;
+ }
+ }
+ } elseif (isset($this->configuration[$layer][$key])) {
+ $test = $this->configuration[$layer][$key];
+ if ($this->_installRoot) {
+ if (in_array($this->getGroup($key),
+ array('File Locations', 'File Locations (Advanced)')) &&
+ $this->getType($key) == 'directory') {
+ return $this->_prependPath($test, $this->_installRoot);
+ }
+ }
+
+ if ($key == 'preferred_mirror') {
+ $reg = &$this->getRegistry();
+ if (is_object($reg)) {
+ $chan = &$reg->getChannel($channel);
+ if (PEAR::isError($chan)) {
+ return $channel;
+ }
+
+ if (!$chan->getMirror($test) && $chan->getName() != $test) {
+ return $channel; // mirror does not exist
+ }
+ }
+ }
+
+ return $test;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a channel-specific configuration value, prioritizing layers as per the
+ * layers property.
+ *
+ * @param string config key
+ * @return mixed the config value, or NULL if not found
+ * @access private
+ */
+ function _getChannelValue($key, $layer, $channel)
+ {
+ if ($key == '__channels' || $channel == 'pear.php.net') {
+ return null;
+ }
+
+ $ret = null;
+ if ($layer === null) {
+ foreach ($this->layers as $ilayer) {
+ if (isset($this->configuration[$ilayer]['__channels'][$channel][$key])) {
+ $ret = $this->configuration[$ilayer]['__channels'][$channel][$key];
+ break;
+ }
+ }
+ } elseif (isset($this->configuration[$layer]['__channels'][$channel][$key])) {
+ $ret = $this->configuration[$layer]['__channels'][$channel][$key];
+ }
+
+ if ($key != 'preferred_mirror') {
+ return $ret;
+ }
+
+
+ if ($ret !== null) {
+ $reg = &$this->getRegistry($layer);
+ if (is_object($reg)) {
+ $chan = &$reg->getChannel($channel);
+ if (PEAR::isError($chan)) {
+ return $channel;
+ }
+
+ if (!$chan->getMirror($ret) && $chan->getName() != $ret) {
+ return $channel; // mirror does not exist
+ }
+ }
+
+ return $ret;
+ }
+
+ if ($channel != $this->getDefaultChannel($layer)) {
+ return $channel; // we must use the channel name as the preferred mirror
+ // if the user has not chosen an alternate
+ }
+
+ return $this->getDefaultChannel($layer);
+ }
+
+ /**
+ * Set a config value in a specific layer (defaults to 'user').
+ * Enforces the types defined in the configuration_info array. An
+ * integer config variable will be cast to int, and a set config
+ * variable will be validated against its legal values.
+ *
+ * @param string config key
+ * @param string config value
+ * @param string (optional) config layer
+ * @param string channel to set this value for, or null for global value
+ * @return bool TRUE on success, FALSE on failure
+ */
+ function set($key, $value, $layer = 'user', $channel = false)
+ {
+ if ($key == '__channels') {
+ return false;
+ }
+
+ if (!isset($this->configuration[$layer])) {
+ return false;
+ }
+
+ if ($key == 'default_channel') {
+ // can only set this value globally
+ $channel = 'pear.php.net';
+ if ($value != 'pear.php.net') {
+ $this->_lazyChannelSetup($layer);
+ }
+ }
+
+ if ($key == 'preferred_mirror') {
+ if ($channel == '__uri') {
+ return false; // can't set the __uri pseudo-channel's mirror
+ }
+
+ $reg = &$this->getRegistry($layer);
+ if (is_object($reg)) {
+ $chan = &$reg->getChannel($channel ? $channel : 'pear.php.net');
+ if (PEAR::isError($chan)) {
+ return false;
+ }
+
+ if (!$chan->getMirror($value) && $chan->getName() != $value) {
+ return false; // mirror does not exist
+ }
+ }
+ }
+
+ if (!isset($this->configuration_info[$key])) {
+ return false;
+ }
+
+ extract($this->configuration_info[$key]);
+ switch ($type) {
+ case 'integer':
+ $value = (int)$value;
+ break;
+ case 'set': {
+ // If a valid_set is specified, require the value to
+ // be in the set. If there is no valid_set, accept
+ // any value.
+ if ($valid_set) {
+ reset($valid_set);
+ if ((key($valid_set) === 0 && !in_array($value, $valid_set)) ||
+ (key($valid_set) !== 0 && empty($valid_set[$value])))
+ {
+ return false;
+ }
+ }
+ break;
+ }
+ }
+
+ if (!$channel) {
+ $channel = $this->get('default_channel', null, 'pear.php.net');
+ }
+
+ if (!in_array($channel, $this->_channels)) {
+ $this->_lazyChannelSetup($layer);
+ $reg = &$this->getRegistry($layer);
+ if ($reg) {
+ $channel = $reg->channelName($channel);
+ }
+
+ if (!in_array($channel, $this->_channels)) {
+ return false;
+ }
+ }
+
+ if ($channel != 'pear.php.net') {
+ if (in_array($key, $this->_channelConfigInfo)) {
+ $this->configuration[$layer]['__channels'][$channel][$key] = $value;
+ return true;
+ }
+
+ return false;
+ }
+
+ if ($key == 'default_channel') {
+ if (!isset($reg)) {
+ $reg = &$this->getRegistry($layer);
+ if (!$reg) {
+ $reg = &$this->getRegistry();
+ }
+ }
+
+ if ($reg) {
+ $value = $reg->channelName($value);
+ }
+
+ if (!$value) {
+ return false;
+ }
+ }
+
+ $this->configuration[$layer][$key] = $value;
+ if ($key == 'php_dir' && !$this->_noRegistry) {
+ if (!isset($this->_registry[$layer]) ||
+ $value != $this->_registry[$layer]->install_dir) {
+ $this->_registry[$layer] = &new PEAR_Registry($value);
+ $this->_regInitialized[$layer] = false;
+ $this->_registry[$layer]->setConfig($this, false);
+ }
+ }
+
+ return true;
+ }
+
+ function _lazyChannelSetup($uselayer = false)
+ {
+ if ($this->_noRegistry) {
+ return;
+ }
+
+ $merge = false;
+ foreach ($this->_registry as $layer => $p) {
+ if ($uselayer && $uselayer != $layer) {
+ continue;
+ }
+
+ if (!$this->_regInitialized[$layer]) {
+ if ($layer == 'default' && isset($this->_registry['user']) ||
+ isset($this->_registry['system'])) {
+ // only use the default registry if there are no alternatives
+ continue;
+ }
+
+ if (!is_object($this->_registry[$layer])) {
+ if ($phpdir = $this->get('php_dir', $layer, 'pear.php.net')) {
+ $this->_registry[$layer] = &new PEAR_Registry($phpdir);
+ $this->_registry[$layer]->setConfig($this, false);
+ $this->_regInitialized[$layer] = false;
+ } else {
+ unset($this->_registry[$layer]);
+ return;
+ }
+ }
+
+ $this->setChannels($this->_registry[$layer]->listChannels(), $merge);
+ $this->_regInitialized[$layer] = true;
+ $merge = true;
+ }
+ }
+ }
+
+ /**
+ * Set the list of channels.
+ *
+ * This should be set via a call to {@link PEAR_Registry::listChannels()}
+ * @param array
+ * @param bool
+ * @return bool success of operation
+ */
+ function setChannels($channels, $merge = false)
+ {
+ if (!is_array($channels)) {
+ return false;
+ }
+
+ if ($merge) {
+ $this->_channels = array_merge($this->_channels, $channels);
+ } else {
+ $this->_channels = $channels;
+ }
+
+ foreach ($channels as $channel) {
+ $channel = strtolower($channel);
+ if ($channel == 'pear.php.net') {
+ continue;
+ }
+
+ foreach ($this->layers as $layer) {
+ if (!isset($this->configuration[$layer]['__channels'])) {
+ $this->configuration[$layer]['__channels'] = array();
+ }
+ if (!isset($this->configuration[$layer]['__channels'][$channel])
+ || !is_array($this->configuration[$layer]['__channels'][$channel])) {
+ $this->configuration[$layer]['__channels'][$channel] = array();
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Get the type of a config value.
+ *
+ * @param string config key
+ *
+ * @return string type, one of "string", "integer", "file",
+ * "directory", "set" or "password".
+ *
+ * @access public
+ *
+ */
+ function getType($key)
+ {
+ if (isset($this->configuration_info[$key])) {
+ return $this->configuration_info[$key]['type'];
+ }
+ return false;
+ }
+
+ /**
+ * Get the documentation for a config value.
+ *
+ * @param string config key
+ * @return string documentation string
+ *
+ * @access public
+ *
+ */
+ function getDocs($key)
+ {
+ if (isset($this->configuration_info[$key])) {
+ return $this->configuration_info[$key]['doc'];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the short documentation for a config value.
+ *
+ * @param string config key
+ * @return string short documentation string
+ *
+ * @access public
+ *
+ */
+ function getPrompt($key)
+ {
+ if (isset($this->configuration_info[$key])) {
+ return $this->configuration_info[$key]['prompt'];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the parameter group for a config key.
+ *
+ * @param string config key
+ * @return string parameter group
+ *
+ * @access public
+ *
+ */
+ function getGroup($key)
+ {
+ if (isset($this->configuration_info[$key])) {
+ return $this->configuration_info[$key]['group'];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the list of parameter groups.
+ *
+ * @return array list of parameter groups
+ *
+ * @access public
+ *
+ */
+ function getGroups()
+ {
+ $tmp = array();
+ foreach ($this->configuration_info as $key => $info) {
+ $tmp[$info['group']] = 1;
+ }
+
+ return array_keys($tmp);
+ }
+
+ /**
+ * Get the list of the parameters in a group.
+ *
+ * @param string $group parameter group
+ * @return array list of parameters in $group
+ *
+ * @access public
+ *
+ */
+ function getGroupKeys($group)
+ {
+ $keys = array();
+ foreach ($this->configuration_info as $key => $info) {
+ if ($info['group'] == $group) {
+ $keys[] = $key;
+ }
+ }
+
+ return $keys;
+ }
+
+ /**
+ * Get the list of allowed set values for a config value. Returns
+ * NULL for config values that are not sets.
+ *
+ * @param string config key
+ * @return array enumerated array of set values, or NULL if the
+ * config key is unknown or not a set
+ *
+ * @access public
+ *
+ */
+ function getSetValues($key)
+ {
+ if (isset($this->configuration_info[$key]) &&
+ isset($this->configuration_info[$key]['type']) &&
+ $this->configuration_info[$key]['type'] == 'set')
+ {
+ $valid_set = $this->configuration_info[$key]['valid_set'];
+ reset($valid_set);
+ if (key($valid_set) === 0) {
+ return $valid_set;
+ }
+
+ return array_keys($valid_set);
+ }
+
+ return null;
+ }
+
+ /**
+ * Get all the current config keys.
+ *
+ * @return array simple array of config keys
+ *
+ * @access public
+ */
+ function getKeys()
+ {
+ $keys = array();
+ foreach ($this->layers as $layer) {
+ $test = $this->configuration[$layer];
+ if (isset($test['__channels'])) {
+ foreach ($test['__channels'] as $channel => $configs) {
+ $keys = array_merge($keys, $configs);
+ }
+ }
+
+ unset($test['__channels']);
+ $keys = array_merge($keys, $test);
+
+ }
+ return array_keys($keys);
+ }
+
+ /**
+ * Remove the a config key from a specific config layer.
+ *
+ * @param string config key
+ * @param string (optional) config layer
+ * @param string (optional) channel (defaults to default channel)
+ * @return bool TRUE on success, FALSE on failure
+ *
+ * @access public
+ */
+ function remove($key, $layer = 'user', $channel = null)
+ {
+ if ($channel === null) {
+ $channel = $this->getDefaultChannel();
+ }
+
+ if ($channel !== 'pear.php.net') {
+ if (isset($this->configuration[$layer]['__channels'][$channel][$key])) {
+ unset($this->configuration[$layer]['__channels'][$channel][$key]);
+ return true;
+ }
+ }
+
+ if (isset($this->configuration[$layer][$key])) {
+ unset($this->configuration[$layer][$key]);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Temporarily remove an entire config layer. USE WITH CARE!
+ *
+ * @param string config key
+ * @param string (optional) config layer
+ * @return bool TRUE on success, FALSE on failure
+ *
+ * @access public
+ */
+ function removeLayer($layer)
+ {
+ if (isset($this->configuration[$layer])) {
+ $this->configuration[$layer] = array();
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Stores configuration data in a layer.
+ *
+ * @param string config layer to store
+ * @return bool TRUE on success, or PEAR error on failure
+ *
+ * @access public
+ */
+ function store($layer = 'user', $data = null)
+ {
+ return $this->writeConfigFile(null, $layer, $data);
+ }
+
+ /**
+ * Tells what config layer that gets to define a key.
+ *
+ * @param string config key
+ * @param boolean return the defining channel
+ *
+ * @return string|array the config layer, or an empty string if not found.
+ *
+ * if $returnchannel, the return is an array array('layer' => layername,
+ * 'channel' => channelname), or an empty string if not found
+ *
+ * @access public
+ */
+ function definedBy($key, $returnchannel = false)
+ {
+ foreach ($this->layers as $layer) {
+ $channel = $this->getDefaultChannel();
+ if ($channel !== 'pear.php.net') {
+ if (isset($this->configuration[$layer]['__channels'][$channel][$key])) {
+ if ($returnchannel) {
+ return array('layer' => $layer, 'channel' => $channel);
+ }
+ return $layer;
+ }
+ }
+
+ if (isset($this->configuration[$layer][$key])) {
+ if ($returnchannel) {
+ return array('layer' => $layer, 'channel' => 'pear.php.net');
+ }
+ return $layer;
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * Tells whether a given key exists as a config value.
+ *
+ * @param string config key
+ * @return bool whether <config key> exists in this object
+ *
+ * @access public
+ */
+ function isDefined($key)
+ {
+ foreach ($this->layers as $layer) {
+ if (isset($this->configuration[$layer][$key])) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Tells whether a given config layer exists.
+ *
+ * @param string config layer
+ * @return bool whether <config layer> exists in this object
+ *
+ * @access public
+ */
+ function isDefinedLayer($layer)
+ {
+ return isset($this->configuration[$layer]);
+ }
+
+ /**
+ * Returns the layers defined (except the 'default' one)
+ *
+ * @return array of the defined layers
+ */
+ function getLayers()
+ {
+ $cf = $this->configuration;
+ unset($cf['default']);
+ return array_keys($cf);
+ }
+
+ function apiVersion()
+ {
+ return '1.1';
+ }
+
+ /**
+ * @return PEAR_Registry
+ */
+ function &getRegistry($use = null)
+ {
+ $layer = $use === null ? 'user' : $use;
+ if (isset($this->_registry[$layer])) {
+ return $this->_registry[$layer];
+ } elseif ($use === null && isset($this->_registry['system'])) {
+ return $this->_registry['system'];
+ } elseif ($use === null && isset($this->_registry['default'])) {
+ return $this->_registry['default'];
+ } elseif ($use) {
+ $a = false;
+ return $a;
+ }
+
+ // only go here if null was passed in
+ echo "CRITICAL ERROR: Registry could not be initialized from any value";
+ exit(1);
+ }
+
+ /**
+ * This is to allow customization like the use of installroot
+ * @param PEAR_Registry
+ * @return bool
+ */
+ function setRegistry(&$reg, $layer = 'user')
+ {
+ if ($this->_noRegistry) {
+ return false;
+ }
+
+ if (!in_array($layer, array('user', 'system'))) {
+ return false;
+ }
+
+ $this->_registry[$layer] = &$reg;
+ if (is_object($reg)) {
+ $this->_registry[$layer]->setConfig($this, false);
+ }
+
+ return true;
+ }
+
+ function noRegistry()
+ {
+ $this->_noRegistry = true;
+ }
+
+ /**
+ * @return PEAR_REST
+ */
+ function &getREST($version, $options = array())
+ {
+ $version = str_replace('.', '', $version);
+ if (!class_exists($class = 'PEAR_REST_' . $version)) {
+ require_once 'PEAR/REST/' . $version . '.php';
+ }
+
+ $remote = &new $class($this, $options);
+ return $remote;
+ }
+
+ /**
+ * The ftp server is set in {@link readFTPConfigFile()}. It exists only if a
+ * remote configuration file has been specified
+ * @return PEAR_FTP|false
+ */
+ function &getFTP()
+ {
+ if (isset($this->_ftp)) {
+ return $this->_ftp;
+ }
+
+ $a = false;
+ return $a;
+ }
+
+ function _prependPath($path, $prepend)
+ {
+ if (strlen($prepend) > 0) {
+ if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) {
+ if (preg_match('/^[a-z]:/i', $prepend)) {
+ $prepend = substr($prepend, 2);
+ } elseif ($prepend{0} != '\\') {
+ $prepend = "\\$prepend";
+ }
+ $path = substr($path, 0, 2) . $prepend . substr($path, 2);
+ } else {
+ $path = $prepend . $path;
+ }
+ }
+ return $path;
+ }
+
+ /**
+ * @param string|false installation directory to prepend to all _dir variables, or false to
+ * disable
+ */
+ function setInstallRoot($root)
+ {
+ if (substr($root, -1) == DIRECTORY_SEPARATOR) {
+ $root = substr($root, 0, -1);
+ }
+ $old = $this->_installRoot;
+ $this->_installRoot = $root;
+ if (($old != $root) && !$this->_noRegistry) {
+ foreach (array_keys($this->_registry) as $layer) {
+ if ($layer == 'ftp' || !isset($this->_registry[$layer])) {
+ continue;
+ }
+ $this->_registry[$layer] =
+ &new PEAR_Registry($this->get('php_dir', $layer, 'pear.php.net'));
+ $this->_registry[$layer]->setConfig($this, false);
+ $this->_regInitialized[$layer] = false;
+ }
+ }
+ }
+}
diff --git a/includes/pear/PEAR/Dependency2.php b/includes/pear/PEAR/Dependency2.php
new file mode 100644
index 0000000..6d78964
--- /dev/null
+++ b/includes/pear/PEAR/Dependency2.php
@@ -0,0 +1,1362 @@
+<?php
+/**
+ * PEAR_Dependency2, advanced dependency validation
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * Required for the PEAR_VALIDATE_* constants
+ */
+require_once 'PEAR/Validate.php';
+
+/**
+ * Dependency check for PEAR packages
+ *
+ * This class handles both version 1.0 and 2.0 dependencies
+ * WARNING: *any* changes to this class must be duplicated in the
+ * test_PEAR_Dependency2 class found in tests/PEAR_Dependency2/setup.php.inc,
+ * or unit tests will not actually validate the changes
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @PEAR-VER@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Dependency2
+{
+ /**
+ * One of the PEAR_VALIDATE_* states
+ * @see PEAR_VALIDATE_NORMAL
+ * @var integer
+ */
+ var $_state;
+
+ /**
+ * Command-line options to install/upgrade/uninstall commands
+ * @param array
+ */
+ var $_options;
+
+ /**
+ * @var OS_Guess
+ */
+ var $_os;
+
+ /**
+ * @var PEAR_Registry
+ */
+ var $_registry;
+
+ /**
+ * @var PEAR_Config
+ */
+ var $_config;
+
+ /**
+ * @var PEAR_DependencyDB
+ */
+ var $_dependencydb;
+
+ /**
+ * Output of PEAR_Registry::parsedPackageName()
+ * @var array
+ */
+ var $_currentPackage;
+
+ /**
+ * @param PEAR_Config
+ * @param array installation options
+ * @param array format of PEAR_Registry::parsedPackageName()
+ * @param int installation state (one of PEAR_VALIDATE_*)
+ */
+ function PEAR_Dependency2(&$config, $installoptions, $package,
+ $state = PEAR_VALIDATE_INSTALLING)
+ {
+ $this->_config = &$config;
+ if (!class_exists('PEAR_DependencyDB')) {
+ require_once 'PEAR/DependencyDB.php';
+ }
+
+ if (isset($installoptions['packagingroot'])) {
+ // make sure depdb is in the right location
+ $config->setInstallRoot($installoptions['packagingroot']);
+ }
+
+ $this->_registry = &$config->getRegistry();
+ $this->_dependencydb = &PEAR_DependencyDB::singleton($config);
+ if (isset($installoptions['packagingroot'])) {
+ $config->setInstallRoot(false);
+ }
+
+ $this->_options = $installoptions;
+ $this->_state = $state;
+ if (!class_exists('OS_Guess')) {
+ require_once 'OS/Guess.php';
+ }
+
+ $this->_os = new OS_Guess;
+ $this->_currentPackage = $package;
+ }
+
+ function _getExtraString($dep)
+ {
+ $extra = ' (';
+ if (isset($dep['uri'])) {
+ return '';
+ }
+
+ if (isset($dep['recommended'])) {
+ $extra .= 'recommended version ' . $dep['recommended'];
+ } else {
+ if (isset($dep['min'])) {
+ $extra .= 'version >= ' . $dep['min'];
+ }
+
+ if (isset($dep['max'])) {
+ if ($extra != ' (') {
+ $extra .= ', ';
+ }
+ $extra .= 'version <= ' . $dep['max'];
+ }
+
+ if (isset($dep['exclude'])) {
+ if (!is_array($dep['exclude'])) {
+ $dep['exclude'] = array($dep['exclude']);
+ }
+
+ if ($extra != ' (') {
+ $extra .= ', ';
+ }
+
+ $extra .= 'excluded versions: ';
+ foreach ($dep['exclude'] as $i => $exclude) {
+ if ($i) {
+ $extra .= ', ';
+ }
+ $extra .= $exclude;
+ }
+ }
+ }
+
+ $extra .= ')';
+ if ($extra == ' ()') {
+ $extra = '';
+ }
+
+ return $extra;
+ }
+
+ /**
+ * This makes unit-testing a heck of a lot easier
+ */
+ function getPHP_OS()
+ {
+ return PHP_OS;
+ }
+
+ /**
+ * This makes unit-testing a heck of a lot easier
+ */
+ function getsysname()
+ {
+ return $this->_os->getSysname();
+ }
+
+ /**
+ * Specify a dependency on an OS. Use arch for detailed os/processor information
+ *
+ * There are two generic OS dependencies that will be the most common, unix and windows.
+ * Other options are linux, freebsd, darwin (OS X), sunos, irix, hpux, aix
+ */
+ function validateOsDependency($dep)
+ {
+ if ($this->_state != PEAR_VALIDATE_INSTALLING && $this->_state != PEAR_VALIDATE_DOWNLOADING) {
+ return true;
+ }
+
+ if ($dep['name'] == '*') {
+ return true;
+ }
+
+ $not = isset($dep['conflicts']) ? true : false;
+ switch (strtolower($dep['name'])) {
+ case 'windows' :
+ if ($not) {
+ if (strtolower(substr($this->getPHP_OS(), 0, 3)) == 'win') {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError("Cannot install %s on Windows");
+ }
+
+ return $this->warning("warning: Cannot install %s on Windows");
+ }
+ } else {
+ if (strtolower(substr($this->getPHP_OS(), 0, 3)) != 'win') {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError("Can only install %s on Windows");
+ }
+
+ return $this->warning("warning: Can only install %s on Windows");
+ }
+ }
+ break;
+ case 'unix' :
+ $unices = array('linux', 'freebsd', 'darwin', 'sunos', 'irix', 'hpux', 'aix');
+ if ($not) {
+ if (in_array($this->getSysname(), $unices)) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError("Cannot install %s on any Unix system");
+ }
+
+ return $this->warning( "warning: Cannot install %s on any Unix system");
+ }
+ } else {
+ if (!in_array($this->getSysname(), $unices)) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError("Can only install %s on a Unix system");
+ }
+
+ return $this->warning("warning: Can only install %s on a Unix system");
+ }
+ }
+ break;
+ default :
+ if ($not) {
+ if (strtolower($dep['name']) == strtolower($this->getSysname())) {
+ if (!isset($this->_options['nodeps']) &&
+ !isset($this->_options['force'])) {
+ return $this->raiseError('Cannot install %s on ' . $dep['name'] .
+ ' operating system');
+ }
+
+ return $this->warning('warning: Cannot install %s on ' .
+ $dep['name'] . ' operating system');
+ }
+ } else {
+ if (strtolower($dep['name']) != strtolower($this->getSysname())) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('Cannot install %s on ' .
+ $this->getSysname() .
+ ' operating system, can only install on ' . $dep['name']);
+ }
+
+ return $this->warning('warning: Cannot install %s on ' .
+ $this->getSysname() .
+ ' operating system, can only install on ' . $dep['name']);
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * This makes unit-testing a heck of a lot easier
+ */
+ function matchSignature($pattern)
+ {
+ return $this->_os->matchSignature($pattern);
+ }
+
+ /**
+ * Specify a complex dependency on an OS/processor/kernel version,
+ * Use OS for simple operating system dependency.
+ *
+ * This is the only dependency that accepts an eregable pattern. The pattern
+ * will be matched against the php_uname() output parsed by OS_Guess
+ */
+ function validateArchDependency($dep)
+ {
+ if ($this->_state != PEAR_VALIDATE_INSTALLING) {
+ return true;
+ }
+
+ $not = isset($dep['conflicts']) ? true : false;
+ if (!$this->matchSignature($dep['pattern'])) {
+ if (!$not) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s Architecture dependency failed, does not ' .
+ 'match "' . $dep['pattern'] . '"');
+ }
+
+ return $this->warning('warning: %s Architecture dependency failed, does ' .
+ 'not match "' . $dep['pattern'] . '"');
+ }
+
+ return true;
+ }
+
+ if ($not) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s Architecture dependency failed, required "' .
+ $dep['pattern'] . '"');
+ }
+
+ return $this->warning('warning: %s Architecture dependency failed, ' .
+ 'required "' . $dep['pattern'] . '"');
+ }
+
+ return true;
+ }
+
+ /**
+ * This makes unit-testing a heck of a lot easier
+ */
+ function extension_loaded($name)
+ {
+ return extension_loaded($name);
+ }
+
+ /**
+ * This makes unit-testing a heck of a lot easier
+ */
+ function phpversion($name = null)
+ {
+ if ($name !== null) {
+ return phpversion($name);
+ }
+
+ return phpversion();
+ }
+
+ function validateExtensionDependency($dep, $required = true)
+ {
+ if ($this->_state != PEAR_VALIDATE_INSTALLING &&
+ $this->_state != PEAR_VALIDATE_DOWNLOADING) {
+ return true;
+ }
+
+ $loaded = $this->extension_loaded($dep['name']);
+ $extra = $this->_getExtraString($dep);
+ if (isset($dep['exclude'])) {
+ if (!is_array($dep['exclude'])) {
+ $dep['exclude'] = array($dep['exclude']);
+ }
+ }
+
+ if (!isset($dep['min']) && !isset($dep['max']) &&
+ !isset($dep['recommended']) && !isset($dep['exclude'])
+ ) {
+ if ($loaded) {
+ if (isset($dep['conflicts'])) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s conflicts with PHP extension "' .
+ $dep['name'] . '"' . $extra);
+ }
+
+ return $this->warning('warning: %s conflicts with PHP extension "' .
+ $dep['name'] . '"' . $extra);
+ }
+
+ return true;
+ }
+
+ if (isset($dep['conflicts'])) {
+ return true;
+ }
+
+ if ($required) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s requires PHP extension "' .
+ $dep['name'] . '"' . $extra);
+ }
+
+ return $this->warning('warning: %s requires PHP extension "' .
+ $dep['name'] . '"' . $extra);
+ }
+
+ return $this->warning('%s can optionally use PHP extension "' .
+ $dep['name'] . '"' . $extra);
+ }
+
+ if (!$loaded) {
+ if (isset($dep['conflicts'])) {
+ return true;
+ }
+
+ if (!$required) {
+ return $this->warning('%s can optionally use PHP extension "' .
+ $dep['name'] . '"' . $extra);
+ }
+
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s requires PHP extension "' . $dep['name'] .
+ '"' . $extra);
+ }
+
+ return $this->warning('warning: %s requires PHP extension "' . $dep['name'] .
+ '"' . $extra);
+ }
+
+ $version = (string) $this->phpversion($dep['name']);
+ if (empty($version)) {
+ $version = '0';
+ }
+
+ $fail = false;
+ if (isset($dep['min']) && !version_compare($version, $dep['min'], '>=')) {
+ $fail = true;
+ }
+
+ if (isset($dep['max']) && !version_compare($version, $dep['max'], '<=')) {
+ $fail = true;
+ }
+
+ if ($fail && !isset($dep['conflicts'])) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s requires PHP extension "' . $dep['name'] .
+ '"' . $extra . ', installed version is ' . $version);
+ }
+
+ return $this->warning('warning: %s requires PHP extension "' . $dep['name'] .
+ '"' . $extra . ', installed version is ' . $version);
+ } elseif ((isset($dep['min']) || isset($dep['max'])) && !$fail && isset($dep['conflicts'])) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s conflicts with PHP extension "' .
+ $dep['name'] . '"' . $extra . ', installed version is ' . $version);
+ }
+
+ return $this->warning('warning: %s conflicts with PHP extension "' .
+ $dep['name'] . '"' . $extra . ', installed version is ' . $version);
+ }
+
+ if (isset($dep['exclude'])) {
+ foreach ($dep['exclude'] as $exclude) {
+ if (version_compare($version, $exclude, '==')) {
+ if (isset($dep['conflicts'])) {
+ continue;
+ }
+
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s is not compatible with PHP extension "' .
+ $dep['name'] . '" version ' .
+ $exclude);
+ }
+
+ return $this->warning('warning: %s is not compatible with PHP extension "' .
+ $dep['name'] . '" version ' .
+ $exclude);
+ } elseif (version_compare($version, $exclude, '!=') && isset($dep['conflicts'])) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s conflicts with PHP extension "' .
+ $dep['name'] . '"' . $extra . ', installed version is ' . $version);
+ }
+
+ return $this->warning('warning: %s conflicts with PHP extension "' .
+ $dep['name'] . '"' . $extra . ', installed version is ' . $version);
+ }
+ }
+ }
+
+ if (isset($dep['recommended'])) {
+ if (version_compare($version, $dep['recommended'], '==')) {
+ return true;
+ }
+
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s dependency: PHP extension ' . $dep['name'] .
+ ' version "' . $version . '"' .
+ ' is not the recommended version "' . $dep['recommended'] .
+ '", but may be compatible, use --force to install');
+ }
+
+ return $this->warning('warning: %s dependency: PHP extension ' .
+ $dep['name'] . ' version "' . $version . '"' .
+ ' is not the recommended version "' . $dep['recommended'].'"');
+ }
+
+ return true;
+ }
+
+ function validatePhpDependency($dep)
+ {
+ if ($this->_state != PEAR_VALIDATE_INSTALLING &&
+ $this->_state != PEAR_VALIDATE_DOWNLOADING) {
+ return true;
+ }
+
+ $version = $this->phpversion();
+ $extra = $this->_getExtraString($dep);
+ if (isset($dep['exclude'])) {
+ if (!is_array($dep['exclude'])) {
+ $dep['exclude'] = array($dep['exclude']);
+ }
+ }
+
+ if (isset($dep['min'])) {
+ if (!version_compare($version, $dep['min'], '>=')) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s requires PHP' .
+ $extra . ', installed version is ' . $version);
+ }
+
+ return $this->warning('warning: %s requires PHP' .
+ $extra . ', installed version is ' . $version);
+ }
+ }
+
+ if (isset($dep['max'])) {
+ if (!version_compare($version, $dep['max'], '<=')) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s requires PHP' .
+ $extra . ', installed version is ' . $version);
+ }
+
+ return $this->warning('warning: %s requires PHP' .
+ $extra . ', installed version is ' . $version);
+ }
+ }
+
+ if (isset($dep['exclude'])) {
+ foreach ($dep['exclude'] as $exclude) {
+ if (version_compare($version, $exclude, '==')) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s is not compatible with PHP version ' .
+ $exclude);
+ }
+
+ return $this->warning(
+ 'warning: %s is not compatible with PHP version ' .
+ $exclude);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * This makes unit-testing a heck of a lot easier
+ */
+ function getPEARVersion()
+ {
+ return '1.9.4';
+ }
+
+ function validatePearinstallerDependency($dep)
+ {
+ $pearversion = $this->getPEARVersion();
+ if (array_key_exists('PEAR_RUNTESTS_PEAR_VERSION_RETURN', $GLOBALS)) {
+ // A means for testing pear-core in local repositories.
+ return $GLOBALS['PEAR_RUNTESTS_PEAR_VERSION_RETURN'];
+ }
+ $extra = $this->_getExtraString($dep);
+ if (isset($dep['exclude'])) {
+ if (!is_array($dep['exclude'])) {
+ $dep['exclude'] = array($dep['exclude']);
+ }
+ }
+
+ if (version_compare($pearversion, $dep['min'], '<')) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s requires PEAR Installer' . $extra .
+ ', installed version is ' . $pearversion);
+ }
+
+ return $this->warning('warning: %s requires PEAR Installer' . $extra .
+ ', installed version is ' . $pearversion);
+ }
+
+ if (isset($dep['max'])) {
+ if (version_compare($pearversion, $dep['max'], '>')) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s requires PEAR Installer' . $extra .
+ ', installed version is ' . $pearversion);
+ }
+
+ return $this->warning('warning: %s requires PEAR Installer' . $extra .
+ ', installed version is ' . $pearversion);
+ }
+ }
+
+ if (isset($dep['exclude'])) {
+ if (!isset($dep['exclude'][0])) {
+ $dep['exclude'] = array($dep['exclude']);
+ }
+
+ foreach ($dep['exclude'] as $exclude) {
+ if (version_compare($exclude, $pearversion, '==')) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s is not compatible with PEAR Installer ' .
+ 'version ' . $exclude);
+ }
+
+ return $this->warning('warning: %s is not compatible with PEAR ' .
+ 'Installer version ' . $exclude);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ function validateSubpackageDependency($dep, $required, $params)
+ {
+ return $this->validatePackageDependency($dep, $required, $params);
+ }
+
+ /**
+ * @param array dependency information (2.0 format)
+ * @param boolean whether this is a required dependency
+ * @param array a list of downloaded packages to be installed, if any
+ * @param boolean if true, then deps on pear.php.net that fail will also check
+ * against pecl.php.net packages to accomodate extensions that have
+ * moved to pecl.php.net from pear.php.net
+ */
+ function validatePackageDependency($dep, $required, $params, $depv1 = false)
+ {
+ if ($this->_state != PEAR_VALIDATE_INSTALLING &&
+ $this->_state != PEAR_VALIDATE_DOWNLOADING) {
+ return true;
+ }
+
+ if (isset($dep['providesextension'])) {
+ if ($this->extension_loaded($dep['providesextension'])) {
+ $save = $dep;
+ $subdep = $dep;
+ $subdep['name'] = $subdep['providesextension'];
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $ret = $this->validateExtensionDependency($subdep, $required);
+ PEAR::popErrorHandling();
+ if (!PEAR::isError($ret)) {
+ return true;
+ }
+ }
+ }
+
+ if ($this->_state == PEAR_VALIDATE_INSTALLING) {
+ return $this->_validatePackageInstall($dep, $required, $depv1);
+ }
+
+ if ($this->_state == PEAR_VALIDATE_DOWNLOADING) {
+ return $this->_validatePackageDownload($dep, $required, $params, $depv1);
+ }
+ }
+
+ function _validatePackageDownload($dep, $required, $params, $depv1 = false)
+ {
+ $dep['package'] = $dep['name'];
+ if (isset($dep['uri'])) {
+ $dep['channel'] = '__uri';
+ }
+
+ $depname = $this->_registry->parsedPackageNameToString($dep, true);
+ $found = false;
+ foreach ($params as $param) {
+ if ($param->isEqual(
+ array('package' => $dep['name'],
+ 'channel' => $dep['channel']))) {
+ $found = true;
+ break;
+ }
+
+ if ($depv1 && $dep['channel'] == 'pear.php.net') {
+ if ($param->isEqual(
+ array('package' => $dep['name'],
+ 'channel' => 'pecl.php.net'))) {
+ $found = true;
+ break;
+ }
+ }
+ }
+
+ if (!$found && isset($dep['providesextension'])) {
+ foreach ($params as $param) {
+ if ($param->isExtension($dep['providesextension'])) {
+ $found = true;
+ break;
+ }
+ }
+ }
+
+ if ($found) {
+ $version = $param->getVersion();
+ $installed = false;
+ $downloaded = true;
+ } else {
+ if ($this->_registry->packageExists($dep['name'], $dep['channel'])) {
+ $installed = true;
+ $downloaded = false;
+ $version = $this->_registry->packageinfo($dep['name'], 'version',
+ $dep['channel']);
+ } else {
+ if ($dep['channel'] == 'pecl.php.net' && $this->_registry->packageExists($dep['name'],
+ 'pear.php.net')) {
+ $installed = true;
+ $downloaded = false;
+ $version = $this->_registry->packageinfo($dep['name'], 'version',
+ 'pear.php.net');
+ } else {
+ $version = 'not installed or downloaded';
+ $installed = false;
+ $downloaded = false;
+ }
+ }
+ }
+
+ $extra = $this->_getExtraString($dep);
+ if (isset($dep['exclude']) && !is_array($dep['exclude'])) {
+ $dep['exclude'] = array($dep['exclude']);
+ }
+
+ if (!isset($dep['min']) && !isset($dep['max']) &&
+ !isset($dep['recommended']) && !isset($dep['exclude'])
+ ) {
+ if ($installed || $downloaded) {
+ $installed = $installed ? 'installed' : 'downloaded';
+ if (isset($dep['conflicts'])) {
+ $rest = '';
+ if ($version) {
+ $rest = ", $installed version is " . $version;
+ }
+
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s conflicts with package "' . $depname . '"' . $extra . $rest);
+ }
+
+ return $this->warning('warning: %s conflicts with package "' . $depname . '"' . $extra . $rest);
+ }
+
+ return true;
+ }
+
+ if (isset($dep['conflicts'])) {
+ return true;
+ }
+
+ if ($required) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s requires package "' . $depname . '"' . $extra);
+ }
+
+ return $this->warning('warning: %s requires package "' . $depname . '"' . $extra);
+ }
+
+ return $this->warning('%s can optionally use package "' . $depname . '"' . $extra);
+ }
+
+ if (!$installed && !$downloaded) {
+ if (isset($dep['conflicts'])) {
+ return true;
+ }
+
+ if ($required) {
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s requires package "' . $depname . '"' . $extra);
+ }
+
+ return $this->warning('warning: %s requires package "' . $depname . '"' . $extra);
+ }
+
+ return $this->warning('%s can optionally use package "' . $depname . '"' . $extra);
+ }
+
+ $fail = false;
+ if (isset($dep['min']) && version_compare($version, $dep['min'], '<')) {
+ $fail = true;
+ }
+
+ if (isset($dep['max']) && version_compare($version, $dep['max'], '>')) {
+ $fail = true;
+ }
+
+ if ($fail && !isset($dep['conflicts'])) {
+ $installed = $installed ? 'installed' : 'downloaded';
+ $dep['package'] = $dep['name'];
+ $dep = $this->_registry->parsedPackageNameToString($dep, true);
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s requires package "' . $depname . '"' .
+ $extra . ", $installed version is " . $version);
+ }
+
+ return $this->warning('warning: %s requires package "' . $depname . '"' .
+ $extra . ", $installed version is " . $version);
+ } elseif ((isset($dep['min']) || isset($dep['max'])) && !$fail &&
+ isset($dep['conflicts']) && !isset($dep['exclude'])) {
+ $installed = $installed ? 'installed' : 'downloaded';
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s conflicts with package "' . $depname . '"' . $extra .
+ ", $installed version is " . $version);
+ }
+
+ return $this->warning('warning: %s conflicts with package "' . $depname . '"' .
+ $extra . ", $installed version is " . $version);
+ }
+
+ if (isset($dep['exclude'])) {
+ $installed = $installed ? 'installed' : 'downloaded';
+ foreach ($dep['exclude'] as $exclude) {
+ if (version_compare($version, $exclude, '==') && !isset($dep['conflicts'])) {
+ if (!isset($this->_options['nodeps']) &&
+ !isset($this->_options['force'])
+ ) {
+ return $this->raiseError('%s is not compatible with ' .
+ $installed . ' package "' .
+ $depname . '" version ' .
+ $exclude);
+ }
+
+ return $this->warning('warning: %s is not compatible with ' .
+ $installed . ' package "' .
+ $depname . '" version ' .
+ $exclude);
+ } elseif (version_compare($version, $exclude, '!=') && isset($dep['conflicts'])) {
+ $installed = $installed ? 'installed' : 'downloaded';
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('%s conflicts with package "' . $depname . '"' .
+ $extra . ", $installed version is " . $version);
+ }
+
+ return $this->warning('warning: %s conflicts with package "' . $depname . '"' .
+ $extra . ", $installed version is " . $version);
+ }
+ }
+ }
+
+ if (isset($dep['recommended'])) {
+ $installed = $installed ? 'installed' : 'downloaded';
+ if (version_compare($version, $dep['recommended'], '==')) {
+ return true;
+ }
+
+ if (!$found && $installed) {
+ $param = $this->_registry->getPackage($dep['name'], $dep['channel']);
+ }
+
+ if ($param) {
+ $found = false;
+ foreach ($params as $parent) {
+ if ($parent->isEqual($this->_currentPackage)) {
+ $found = true;
+ break;
+ }
+ }
+
+ if ($found) {
+ if ($param->isCompatible($parent)) {
+ return true;
+ }
+ } else { // this is for validPackage() calls
+ $parent = $this->_registry->getPackage($this->_currentPackage['package'],
+ $this->_currentPackage['channel']);
+ if ($parent !== null && $param->isCompatible($parent)) {
+ return true;
+ }
+ }
+ }
+
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force']) &&
+ !isset($this->_options['loose'])
+ ) {
+ return $this->raiseError('%s dependency package "' . $depname .
+ '" ' . $installed . ' version ' . $version .
+ ' is not the recommended version ' . $dep['recommended'] .
+ ', but may be compatible, use --force to install');
+ }
+
+ return $this->warning('warning: %s dependency package "' . $depname .
+ '" ' . $installed . ' version ' . $version .
+ ' is not the recommended version ' . $dep['recommended']);
+ }
+
+ return true;
+ }
+
+ function _validatePackageInstall($dep, $required, $depv1 = false)
+ {
+ return $this->_validatePackageDownload($dep, $required, array(), $depv1);
+ }
+
+ /**
+ * Verify that uninstalling packages passed in to command line is OK.
+ *
+ * @param PEAR_Installer $dl
+ * @return PEAR_Error|true
+ */
+ function validatePackageUninstall(&$dl)
+ {
+ if (PEAR::isError($this->_dependencydb)) {
+ return $this->_dependencydb;
+ }
+
+ $params = array();
+ // construct an array of "downloaded" packages to fool the package dependency checker
+ // into using these to validate uninstalls of circular dependencies
+ $downloaded = &$dl->getUninstallPackages();
+ foreach ($downloaded as $i => $pf) {
+ if (!class_exists('PEAR_Downloader_Package')) {
+ require_once 'PEAR/Downloader/Package.php';
+ }
+ $dp = &new PEAR_Downloader_Package($dl);
+ $dp->setPackageFile($downloaded[$i]);
+ $params[$i] = &$dp;
+ }
+
+ // check cache
+ $memyselfandI = strtolower($this->_currentPackage['channel']) . '/' .
+ strtolower($this->_currentPackage['package']);
+ if (isset($dl->___uninstall_package_cache)) {
+ $badpackages = $dl->___uninstall_package_cache;
+ if (isset($badpackages[$memyselfandI]['warnings'])) {
+ foreach ($badpackages[$memyselfandI]['warnings'] as $warning) {
+ $dl->log(0, $warning[0]);
+ }
+ }
+
+ if (isset($badpackages[$memyselfandI]['errors'])) {
+ foreach ($badpackages[$memyselfandI]['errors'] as $error) {
+ if (is_array($error)) {
+ $dl->log(0, $error[0]);
+ } else {
+ $dl->log(0, $error->getMessage());
+ }
+ }
+
+ if (isset($this->_options['nodeps']) || isset($this->_options['force'])) {
+ return $this->warning(
+ 'warning: %s should not be uninstalled, other installed packages depend ' .
+ 'on this package');
+ }
+
+ return $this->raiseError(
+ '%s cannot be uninstalled, other installed packages depend on this package');
+ }
+
+ return true;
+ }
+
+ // first, list the immediate parents of each package to be uninstalled
+ $perpackagelist = array();
+ $allparents = array();
+ foreach ($params as $i => $param) {
+ $a = array(
+ 'channel' => strtolower($param->getChannel()),
+ 'package' => strtolower($param->getPackage())
+ );
+
+ $deps = $this->_dependencydb->getDependentPackages($a);
+ if ($deps) {
+ foreach ($deps as $d) {
+ $pardeps = $this->_dependencydb->getDependencies($d);
+ foreach ($pardeps as $dep) {
+ if (strtolower($dep['dep']['channel']) == $a['channel'] &&
+ strtolower($dep['dep']['name']) == $a['package']) {
+ if (!isset($perpackagelist[$a['channel'] . '/' . $a['package']])) {
+ $perpackagelist[$a['channel'] . '/' . $a['package']] = array();
+ }
+ $perpackagelist[$a['channel'] . '/' . $a['package']][]
+ = array($d['channel'] . '/' . $d['package'], $dep);
+ if (!isset($allparents[$d['channel'] . '/' . $d['package']])) {
+ $allparents[$d['channel'] . '/' . $d['package']] = array();
+ }
+ if (!isset($allparents[$d['channel'] . '/' . $d['package']][$a['channel'] . '/' . $a['package']])) {
+ $allparents[$d['channel'] . '/' . $d['package']][$a['channel'] . '/' . $a['package']] = array();
+ }
+ $allparents[$d['channel'] . '/' . $d['package']]
+ [$a['channel'] . '/' . $a['package']][]
+ = array($d, $dep);
+ }
+ }
+ }
+ }
+ }
+
+ // next, remove any packages from the parents list that are not installed
+ $remove = array();
+ foreach ($allparents as $parent => $d1) {
+ foreach ($d1 as $d) {
+ if ($this->_registry->packageExists($d[0][0]['package'], $d[0][0]['channel'])) {
+ continue;
+ }
+ $remove[$parent] = true;
+ }
+ }
+
+ // next remove any packages from the parents list that are not passed in for
+ // uninstallation
+ foreach ($allparents as $parent => $d1) {
+ foreach ($d1 as $d) {
+ foreach ($params as $param) {
+ if (strtolower($param->getChannel()) == $d[0][0]['channel'] &&
+ strtolower($param->getPackage()) == $d[0][0]['package']) {
+ // found it
+ continue 3;
+ }
+ }
+ $remove[$parent] = true;
+ }
+ }
+
+ // remove all packages whose dependencies fail
+ // save which ones failed for error reporting
+ $badchildren = array();
+ do {
+ $fail = false;
+ foreach ($remove as $package => $unused) {
+ if (!isset($allparents[$package])) {
+ continue;
+ }
+
+ foreach ($allparents[$package] as $kid => $d1) {
+ foreach ($d1 as $depinfo) {
+ if ($depinfo[1]['type'] != 'optional') {
+ if (isset($badchildren[$kid])) {
+ continue;
+ }
+ $badchildren[$kid] = true;
+ $remove[$kid] = true;
+ $fail = true;
+ continue 2;
+ }
+ }
+ }
+ if ($fail) {
+ // start over, we removed some children
+ continue 2;
+ }
+ }
+ } while ($fail);
+
+ // next, construct the list of packages that can't be uninstalled
+ $badpackages = array();
+ $save = $this->_currentPackage;
+ foreach ($perpackagelist as $package => $packagedeps) {
+ foreach ($packagedeps as $parent) {
+ if (!isset($remove[$parent[0]])) {
+ continue;
+ }
+
+ $packagename = $this->_registry->parsePackageName($parent[0]);
+ $packagename['channel'] = $this->_registry->channelAlias($packagename['channel']);
+ $pa = $this->_registry->getPackage($packagename['package'], $packagename['channel']);
+ $packagename['package'] = $pa->getPackage();
+ $this->_currentPackage = $packagename;
+ // parent is not present in uninstall list, make sure we can actually
+ // uninstall it (parent dep is optional)
+ $parentname['channel'] = $this->_registry->channelAlias($parent[1]['dep']['channel']);
+ $pa = $this->_registry->getPackage($parent[1]['dep']['name'], $parent[1]['dep']['channel']);
+ $parentname['package'] = $pa->getPackage();
+ $parent[1]['dep']['package'] = $parentname['package'];
+ $parent[1]['dep']['channel'] = $parentname['channel'];
+ if ($parent[1]['type'] == 'optional') {
+ $test = $this->_validatePackageUninstall($parent[1]['dep'], false, $dl);
+ if ($test !== true) {
+ $badpackages[$package]['warnings'][] = $test;
+ }
+ } else {
+ $test = $this->_validatePackageUninstall($parent[1]['dep'], true, $dl);
+ if ($test !== true) {
+ $badpackages[$package]['errors'][] = $test;
+ }
+ }
+ }
+ }
+
+ $this->_currentPackage = $save;
+ $dl->___uninstall_package_cache = $badpackages;
+ if (isset($badpackages[$memyselfandI])) {
+ if (isset($badpackages[$memyselfandI]['warnings'])) {
+ foreach ($badpackages[$memyselfandI]['warnings'] as $warning) {
+ $dl->log(0, $warning[0]);
+ }
+ }
+
+ if (isset($badpackages[$memyselfandI]['errors'])) {
+ foreach ($badpackages[$memyselfandI]['errors'] as $error) {
+ if (is_array($error)) {
+ $dl->log(0, $error[0]);
+ } else {
+ $dl->log(0, $error->getMessage());
+ }
+ }
+
+ if (isset($this->_options['nodeps']) || isset($this->_options['force'])) {
+ return $this->warning(
+ 'warning: %s should not be uninstalled, other installed packages depend ' .
+ 'on this package');
+ }
+
+ return $this->raiseError(
+ '%s cannot be uninstalled, other installed packages depend on this package');
+ }
+ }
+
+ return true;
+ }
+
+ function _validatePackageUninstall($dep, $required, $dl)
+ {
+ $depname = $this->_registry->parsedPackageNameToString($dep, true);
+ $version = $this->_registry->packageinfo($dep['package'], 'version', $dep['channel']);
+ if (!$version) {
+ return true;
+ }
+
+ $extra = $this->_getExtraString($dep);
+ if (isset($dep['exclude']) && !is_array($dep['exclude'])) {
+ $dep['exclude'] = array($dep['exclude']);
+ }
+
+ if (isset($dep['conflicts'])) {
+ return true; // uninstall OK - these packages conflict (probably installed with --force)
+ }
+
+ if (!isset($dep['min']) && !isset($dep['max'])) {
+ if (!$required) {
+ return $this->warning('"' . $depname . '" can be optionally used by ' .
+ 'installed package %s' . $extra);
+ }
+
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError('"' . $depname . '" is required by ' .
+ 'installed package %s' . $extra);
+ }
+
+ return $this->warning('warning: "' . $depname . '" is required by ' .
+ 'installed package %s' . $extra);
+ }
+
+ $fail = false;
+ if (isset($dep['min']) && version_compare($version, $dep['min'], '>=')) {
+ $fail = true;
+ }
+
+ if (isset($dep['max']) && version_compare($version, $dep['max'], '<=')) {
+ $fail = true;
+ }
+
+ // we re-use this variable, preserve the original value
+ $saverequired = $required;
+ if (!$required) {
+ return $this->warning($depname . $extra . ' can be optionally used by installed package' .
+ ' "%s"');
+ }
+
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
+ return $this->raiseError($depname . $extra . ' is required by installed package' .
+ ' "%s"');
+ }
+
+ return $this->raiseError('warning: ' . $depname . $extra .
+ ' is required by installed package "%s"');
+ }
+
+ /**
+ * validate a downloaded package against installed packages
+ *
+ * As of PEAR 1.4.3, this will only validate
+ *
+ * @param array|PEAR_Downloader_Package|PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * $pkg package identifier (either
+ * array('package' => blah, 'channel' => blah) or an array with
+ * index 'info' referencing an object)
+ * @param PEAR_Downloader $dl
+ * @param array $params full list of packages to install
+ * @return true|PEAR_Error
+ */
+ function validatePackage($pkg, &$dl, $params = array())
+ {
+ if (is_array($pkg) && isset($pkg['info'])) {
+ $deps = $this->_dependencydb->getDependentPackageDependencies($pkg['info']);
+ } else {
+ $deps = $this->_dependencydb->getDependentPackageDependencies($pkg);
+ }
+
+ $fail = false;
+ if ($deps) {
+ if (!class_exists('PEAR_Downloader_Package')) {
+ require_once 'PEAR/Downloader/Package.php';
+ }
+
+ $dp = &new PEAR_Downloader_Package($dl);
+ if (is_object($pkg)) {
+ $dp->setPackageFile($pkg);
+ } else {
+ $dp->setDownloadURL($pkg);
+ }
+
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ foreach ($deps as $channel => $info) {
+ foreach ($info as $package => $ds) {
+ foreach ($params as $packd) {
+ if (strtolower($packd->getPackage()) == strtolower($package) &&
+ $packd->getChannel() == $channel) {
+ $dl->log(3, 'skipping installed package check of "' .
+ $this->_registry->parsedPackageNameToString(
+ array('channel' => $channel, 'package' => $package),
+ true) .
+ '", version "' . $packd->getVersion() . '" will be ' .
+ 'downloaded and installed');
+ continue 2; // jump to next package
+ }
+ }
+
+ foreach ($ds as $d) {
+ $checker = &new PEAR_Dependency2($this->_config, $this->_options,
+ array('channel' => $channel, 'package' => $package), $this->_state);
+ $dep = $d['dep'];
+ $required = $d['type'] == 'required';
+ $ret = $checker->_validatePackageDownload($dep, $required, array(&$dp));
+ if (is_array($ret)) {
+ $dl->log(0, $ret[0]);
+ } elseif (PEAR::isError($ret)) {
+ $dl->log(0, $ret->getMessage());
+ $fail = true;
+ }
+ }
+ }
+ }
+ PEAR::popErrorHandling();
+ }
+
+ if ($fail) {
+ return $this->raiseError(
+ '%s cannot be installed, conflicts with installed packages');
+ }
+
+ return true;
+ }
+
+ /**
+ * validate a package.xml 1.0 dependency
+ */
+ function validateDependency1($dep, $params = array())
+ {
+ if (!isset($dep['optional'])) {
+ $dep['optional'] = 'no';
+ }
+
+ list($newdep, $type) = $this->normalizeDep($dep);
+ if (!$newdep) {
+ return $this->raiseError("Invalid Dependency");
+ }
+
+ if (method_exists($this, "validate{$type}Dependency")) {
+ return $this->{"validate{$type}Dependency"}($newdep, $dep['optional'] == 'no',
+ $params, true);
+ }
+ }
+
+ /**
+ * Convert a 1.0 dep into a 2.0 dep
+ */
+ function normalizeDep($dep)
+ {
+ $types = array(
+ 'pkg' => 'Package',
+ 'ext' => 'Extension',
+ 'os' => 'Os',
+ 'php' => 'Php'
+ );
+
+ if (!isset($types[$dep['type']])) {
+ return array(false, false);
+ }
+
+ $type = $types[$dep['type']];
+
+ $newdep = array();
+ switch ($type) {
+ case 'Package' :
+ $newdep['channel'] = 'pear.php.net';
+ case 'Extension' :
+ case 'Os' :
+ $newdep['name'] = $dep['name'];
+ break;
+ }
+
+ $dep['rel'] = PEAR_Dependency2::signOperator($dep['rel']);
+ switch ($dep['rel']) {
+ case 'has' :
+ return array($newdep, $type);
+ break;
+ case 'not' :
+ $newdep['conflicts'] = true;
+ break;
+ case '>=' :
+ case '>' :
+ $newdep['min'] = $dep['version'];
+ if ($dep['rel'] == '>') {
+ $newdep['exclude'] = $dep['version'];
+ }
+ break;
+ case '<=' :
+ case '<' :
+ $newdep['max'] = $dep['version'];
+ if ($dep['rel'] == '<') {
+ $newdep['exclude'] = $dep['version'];
+ }
+ break;
+ case 'ne' :
+ case '!=' :
+ $newdep['min'] = '0';
+ $newdep['max'] = '100000';
+ $newdep['exclude'] = $dep['version'];
+ break;
+ case '==' :
+ $newdep['min'] = $dep['version'];
+ $newdep['max'] = $dep['version'];
+ break;
+ }
+ if ($type == 'Php') {
+ if (!isset($newdep['min'])) {
+ $newdep['min'] = '4.4.0';
+ }
+
+ if (!isset($newdep['max'])) {
+ $newdep['max'] = '6.0.0';
+ }
+ }
+ return array($newdep, $type);
+ }
+
+ /**
+ * Converts text comparing operators to them sign equivalents
+ *
+ * Example: 'ge' to '>='
+ *
+ * @access public
+ * @param string Operator
+ * @return string Sign equivalent
+ */
+ function signOperator($operator)
+ {
+ switch($operator) {
+ case 'lt': return '<';
+ case 'le': return '<=';
+ case 'gt': return '>';
+ case 'ge': return '>=';
+ case 'eq': return '==';
+ case 'ne': return '!=';
+ default:
+ return $operator;
+ }
+ }
+
+ function raiseError($msg)
+ {
+ if (isset($this->_options['ignore-errors'])) {
+ return $this->warning($msg);
+ }
+
+ return PEAR::raiseError(sprintf($msg, $this->_registry->parsedPackageNameToString(
+ $this->_currentPackage, true)));
+ }
+
+ function warning($msg)
+ {
+ return array(sprintf($msg, $this->_registry->parsedPackageNameToString(
+ $this->_currentPackage, true)));
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/DependencyDB.php b/includes/pear/PEAR/DependencyDB.php
new file mode 100644
index 0000000..e3ad0ec
--- /dev/null
+++ b/includes/pear/PEAR/DependencyDB.php
@@ -0,0 +1,757 @@
+<?php
+/**
+ * PEAR_DependencyDB, advanced installed packages dependency database
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Tomas V. V. Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * Needed for error handling
+ */
+require_once 'PEAR.php';
+require_once 'PEAR/Config.php';
+
+$GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] = array();
+/**
+ * Track dependency relationships between installed packages
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @author Tomas V.V.Cox <cox@idec.net.com>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_DependencyDB
+{
+ // {{{ properties
+
+ /**
+ * This is initialized by {@link setConfig()}
+ * @var PEAR_Config
+ */
+ private $_config;
+ /**
+ * This is initialized by {@link setConfig()}
+ * @var PEAR_Registry
+ */
+ private $_registry;
+ /**
+ * Filename of the dependency DB (usually .depdb)
+ * @var string
+ */
+ private $_depdb = false;
+ /**
+ * File name of the lockfile (usually .depdblock)
+ * @var string
+ */
+ private $_lockfile = false;
+ /**
+ * Open file resource for locking the lockfile
+ * @var resource|false
+ */
+ private $_lockFp = false;
+ /**
+ * API version of this class, used to validate a file on-disk
+ * @var string
+ */
+ private $_version = '1.0';
+ /**
+ * Cached dependency database file
+ * @var array|null
+ */
+ private $_cache;
+
+ // }}}
+ // {{{ & singleton()
+
+ /**
+ * Get a raw dependency database. Calls setConfig() and assertDepsDB()
+ * @param PEAR_Config
+ * @param string|false full path to the dependency database, or false to use default
+ * @return PEAR_DependencyDB|PEAR_Error
+ */
+ public static function &singleton(&$config, $depdb = false)
+ {
+ $phpdir = $config->get('php_dir', null, 'pear.php.net');
+ if (!isset($GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir])) {
+ $a = new PEAR_DependencyDB;
+ $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir] = &$a;
+ $a->setConfig($config, $depdb);
+ $e = $a->assertDepsDB();
+ if (PEAR::isError($e)) {
+ return $e;
+ }
+ }
+
+ return $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'][$phpdir];
+ }
+
+ /**
+ * Set up the registry/location of dependency DB
+ * @param PEAR_Config|false
+ * @param string|false full path to the dependency database, or false to use default
+ */
+ public function setConfig(&$config, $depdb = false)
+ {
+ if (!$config) {
+ $this->_config = &PEAR_Config::singleton();
+ } else {
+ $this->_config = &$config;
+ }
+
+ $this->_registry = &$this->_config->getRegistry();
+ if (!$depdb) {
+ $this->_depdb = $this->_config->get('php_dir', null, 'pear.php.net') .
+ DIRECTORY_SEPARATOR . '.depdb';
+ } else {
+ $this->_depdb = $depdb;
+ }
+
+ $this->_lockfile = dirname($this->_depdb) . DIRECTORY_SEPARATOR . '.depdblock';
+ }
+ // }}}
+
+ public function hasWriteAccess()
+ {
+ if (!file_exists($this->_depdb)) {
+ $dir = $this->_depdb;
+ while ($dir && $dir != '.') {
+ $dir = dirname($dir); // cd ..
+ if ($dir != '.' && file_exists($dir)) {
+ if (is_writeable($dir)) {
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ return is_writeable($this->_depdb);
+ }
+
+ // {{{ assertDepsDB()
+
+ /**
+ * Create the dependency database, if it doesn't exist. Error if the database is
+ * newer than the code reading it.
+ * @return void|PEAR_Error
+ */
+ public function assertDepsDB()
+ {
+ if (!is_file($this->_depdb)) {
+ $this->rebuildDB();
+ return;
+ }
+
+ $depdb = $this->_getDepDB();
+ // Datatype format has been changed, rebuild the Deps DB
+ if ($depdb['_version'] < $this->_version) {
+ $this->rebuildDB();
+ }
+
+ if ($depdb['_version']{0} > $this->_version{0}) {
+ return PEAR::raiseError('Dependency database is version ' .
+ $depdb['_version'] . ', and we are version ' .
+ $this->_version . ', cannot continue');
+ }
+ }
+
+ /**
+ * Get a list of installed packages that depend on this package
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array
+ * @return array|false
+ */
+ public function getDependentPackages(&$pkg)
+ {
+ $data = $this->_getDepDB();
+ if (is_object($pkg)) {
+ $channel = strtolower($pkg->getChannel());
+ $package = strtolower($pkg->getPackage());
+ } else {
+ $channel = strtolower($pkg['channel']);
+ $package = strtolower($pkg['package']);
+ }
+
+ if (isset($data['packages'][$channel][$package])) {
+ return $data['packages'][$channel][$package];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get a list of the actual dependencies of installed packages that depend on
+ * a package.
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array
+ * @return array|false
+ */
+ public function getDependentPackageDependencies(&$pkg)
+ {
+ $data = $this->_getDepDB();
+ if (is_object($pkg)) {
+ $channel = strtolower($pkg->getChannel());
+ $package = strtolower($pkg->getPackage());
+ } else {
+ $channel = strtolower($pkg['channel']);
+ $package = strtolower($pkg['package']);
+ }
+
+ $depend = $this->getDependentPackages($pkg);
+ if (!$depend) {
+ return false;
+ }
+
+ $dependencies = array();
+ foreach ($depend as $info) {
+ $temp = $this->getDependencies($info);
+ foreach ($temp as $dep) {
+ if (
+ isset($dep['dep'], $dep['dep']['channel'], $dep['dep']['name']) &&
+ strtolower($dep['dep']['channel']) == $channel &&
+ strtolower($dep['dep']['name']) == $package
+ ) {
+ if (!isset($dependencies[$info['channel']])) {
+ $dependencies[$info['channel']] = array();
+ }
+
+ if (!isset($dependencies[$info['channel']][$info['package']])) {
+ $dependencies[$info['channel']][$info['package']] = array();
+ }
+ $dependencies[$info['channel']][$info['package']][] = $dep;
+ }
+ }
+ }
+
+ return $dependencies;
+ }
+
+ /**
+ * Get a list of dependencies of this installed package
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array
+ * @return array|false
+ */
+ public function getDependencies(&$pkg)
+ {
+ if (is_object($pkg)) {
+ $channel = strtolower($pkg->getChannel());
+ $package = strtolower($pkg->getPackage());
+ } else {
+ $channel = strtolower($pkg['channel']);
+ $package = strtolower($pkg['package']);
+ }
+
+ $data = $this->_getDepDB();
+ if (isset($data['dependencies'][$channel][$package])) {
+ return $data['dependencies'][$channel][$package];
+ }
+
+ return false;
+ }
+
+ /**
+ * Determine whether $parent depends on $child, near or deep
+ * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2
+ * @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2
+ */
+ public function dependsOn($parent, $child)
+ {
+ $c = array();
+ $this->_getDepDB();
+ return $this->_dependsOn($parent, $child, $c);
+ }
+
+ private function _dependsOn($parent, $child, &$checked)
+ {
+ if (is_object($parent)) {
+ $channel = strtolower($parent->getChannel());
+ $package = strtolower($parent->getPackage());
+ } else {
+ $channel = strtolower($parent['channel']);
+ $package = strtolower($parent['package']);
+ }
+
+ if (is_object($child)) {
+ $depchannel = strtolower($child->getChannel());
+ $deppackage = strtolower($child->getPackage());
+ } else {
+ $depchannel = strtolower($child['channel']);
+ $deppackage = strtolower($child['package']);
+ }
+
+ if (isset($checked[$channel][$package][$depchannel][$deppackage])) {
+ return false; // avoid endless recursion
+ }
+
+ $checked[$channel][$package][$depchannel][$deppackage] = true;
+ if (!isset($this->_cache['dependencies'][$channel][$package])) {
+ return false;
+ }
+
+ foreach ($this->_cache['dependencies'][$channel][$package] as $info) {
+ if (isset($info['dep']['uri'])) {
+ if (is_object($child)) {
+ if ($info['dep']['uri'] == $child->getURI()) {
+ return true;
+ }
+ } elseif (isset($child['uri'])) {
+ if ($info['dep']['uri'] == $child['uri']) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ if (strtolower($info['dep']['channel']) == $depchannel &&
+ strtolower($info['dep']['name']) == $deppackage) {
+ return true;
+ }
+ }
+
+ foreach ($this->_cache['dependencies'][$channel][$package] as $info) {
+ if (isset($info['dep']['uri'])) {
+ if ($this->_dependsOn(array(
+ 'uri' => $info['dep']['uri'],
+ 'package' => $info['dep']['name']), $child, $checked)) {
+ return true;
+ }
+ } else {
+ if ($this->_dependsOn(array(
+ 'channel' => $info['dep']['channel'],
+ 'package' => $info['dep']['name']), $child, $checked)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Register dependencies of a package that is being installed or upgraded
+ * @param PEAR_PackageFile_v2|PEAR_PackageFile_v2
+ */
+ public function installPackage(&$package)
+ {
+ $data = $this->_getDepDB();
+ unset($this->_cache);
+ $this->_setPackageDeps($data, $package);
+ $this->_writeDepDB($data);
+ }
+
+ /**
+ * Remove dependencies of a package that is being uninstalled, or upgraded.
+ *
+ * Upgraded packages first uninstall, then install
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array If an array, then it must have
+ * indices 'channel' and 'package'
+ */
+ public function uninstallPackage(&$pkg)
+ {
+ $data = $this->_getDepDB();
+ unset($this->_cache);
+ if (is_object($pkg)) {
+ $channel = strtolower($pkg->getChannel());
+ $package = strtolower($pkg->getPackage());
+ } else {
+ $channel = strtolower($pkg['channel']);
+ $package = strtolower($pkg['package']);
+ }
+
+ if (!isset($data['dependencies'][$channel][$package])) {
+ return true;
+ }
+
+ foreach ($data['dependencies'][$channel][$package] as $dep) {
+ $found = false;
+ $depchannel = isset($dep['dep']['uri']) ? '__uri' : strtolower($dep['dep']['channel']);
+ $depname = strtolower($dep['dep']['name']);
+ if (isset($data['packages'][$depchannel][$depname])) {
+ foreach ($data['packages'][$depchannel][$depname] as $i => $info) {
+ if ($info['channel'] == $channel && $info['package'] == $package) {
+ $found = true;
+ break;
+ }
+ }
+ }
+
+ if ($found) {
+ unset($data['packages'][$depchannel][$depname][$i]);
+ if (!count($data['packages'][$depchannel][$depname])) {
+ unset($data['packages'][$depchannel][$depname]);
+ if (!count($data['packages'][$depchannel])) {
+ unset($data['packages'][$depchannel]);
+ }
+ } else {
+ $data['packages'][$depchannel][$depname] =
+ array_values($data['packages'][$depchannel][$depname]);
+ }
+ }
+ }
+
+ unset($data['dependencies'][$channel][$package]);
+ if (!count($data['dependencies'][$channel])) {
+ unset($data['dependencies'][$channel]);
+ }
+
+ if (!count($data['dependencies'])) {
+ unset($data['dependencies']);
+ }
+
+ if (!count($data['packages'])) {
+ unset($data['packages']);
+ }
+
+ $this->_writeDepDB($data);
+ }
+
+ /**
+ * Rebuild the dependency DB by reading registry entries.
+ * @return true|PEAR_Error
+ */
+ public function rebuildDB()
+ {
+ $depdb = array('_version' => $this->_version);
+ if (!$this->hasWriteAccess()) {
+ // allow startup for read-only with older Registry
+ return $depdb;
+ }
+
+ $packages = $this->_registry->listAllPackages();
+ if (PEAR::isError($packages)) {
+ return $packages;
+ }
+
+ foreach ($packages as $channel => $ps) {
+ foreach ($ps as $package) {
+ $package = $this->_registry->getPackage($package, $channel);
+ if (PEAR::isError($package)) {
+ return $package;
+ }
+ $this->_setPackageDeps($depdb, $package);
+ }
+ }
+
+ $error = $this->_writeDepDB($depdb);
+ if (PEAR::isError($error)) {
+ return $error;
+ }
+
+ $this->_cache = $depdb;
+ return true;
+ }
+
+ /**
+ * Register usage of the dependency DB to prevent race conditions
+ * @param int one of the LOCK_* constants
+ * @return true|PEAR_Error
+ */
+ private function _lock($mode = LOCK_EX)
+ {
+ if (stristr(php_uname(), 'Windows 9')) {
+ return true;
+ }
+
+ if ($mode != LOCK_UN && is_resource($this->_lockFp)) {
+ // XXX does not check type of lock (LOCK_SH/LOCK_EX)
+ return true;
+ }
+
+ $open_mode = 'w';
+ // XXX People reported problems with LOCK_SH and 'w'
+ if ($mode === LOCK_SH) {
+ if (!file_exists($this->_lockfile)) {
+ touch($this->_lockfile);
+ } elseif (!is_file($this->_lockfile)) {
+ return PEAR::raiseError('could not create Dependency lock file, ' .
+ 'it exists and is not a regular file');
+ }
+ $open_mode = 'r';
+ }
+
+ if (!is_resource($this->_lockFp)) {
+ $this->_lockFp = @fopen($this->_lockfile, $open_mode);
+ }
+
+ if (!is_resource($this->_lockFp)) {
+ return PEAR::raiseError("could not create Dependency lock file" .
+ (isset($php_errormsg) ? ": " . $php_errormsg : ""));
+ }
+
+ if (!(int)flock($this->_lockFp, $mode)) {
+ switch ($mode) {
+ case LOCK_SH: $str = 'shared'; break;
+ case LOCK_EX: $str = 'exclusive'; break;
+ case LOCK_UN: $str = 'unlock'; break;
+ default: $str = 'unknown'; break;
+ }
+
+ return PEAR::raiseError("could not acquire $str lock ($this->_lockfile)");
+ }
+
+ return true;
+ }
+
+ /**
+ * Release usage of dependency DB
+ * @return true|PEAR_Error
+ */
+ private function _unlock()
+ {
+ $ret = $this->_lock(LOCK_UN);
+ if (is_resource($this->_lockFp)) {
+ fclose($this->_lockFp);
+ }
+ $this->_lockFp = null;
+ return $ret;
+ }
+
+ /**
+ * Load the dependency database from disk, or return the cache
+ * @return array|PEAR_Error
+ */
+ private function _getDepDB()
+ {
+ if (!$this->hasWriteAccess()) {
+ return array('_version' => $this->_version);
+ }
+
+ if (isset($this->_cache)) {
+ return $this->_cache;
+ }
+
+ if (!$fp = fopen($this->_depdb, 'r')) {
+ $err = PEAR::raiseError("Could not open dependencies file `".$this->_depdb."'");
+ return $err;
+ }
+
+ $rt = get_magic_quotes_runtime();
+ set_magic_quotes_runtime(0);
+ clearstatcache();
+ fclose($fp);
+ $data = unserialize(file_get_contents($this->_depdb));
+ set_magic_quotes_runtime($rt);
+ $this->_cache = $data;
+ return $data;
+ }
+
+ /**
+ * Write out the dependency database to disk
+ * @param array the database
+ * @return true|PEAR_Error
+ */
+ private function _writeDepDB(&$deps)
+ {
+ if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
+ return $e;
+ }
+
+ if (!$fp = fopen($this->_depdb, 'wb')) {
+ $this->_unlock();
+ return PEAR::raiseError("Could not open dependencies file `".$this->_depdb."' for writing");
+ }
+
+ $rt = get_magic_quotes_runtime();
+ set_magic_quotes_runtime(0);
+ fwrite($fp, serialize($deps));
+ set_magic_quotes_runtime($rt);
+ fclose($fp);
+ $this->_unlock();
+ $this->_cache = $deps;
+ return true;
+ }
+
+ /**
+ * Register all dependencies from a package in the dependencies database, in essence
+ * "installing" the package's dependency information
+ * @param array the database
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ */
+ private function _setPackageDeps(&$data, &$pkg)
+ {
+ $pkg->setConfig($this->_config);
+ if ($pkg->getPackagexmlVersion() == '1.0') {
+ $gen = &$pkg->getDefaultGenerator();
+ $deps = $gen->dependenciesToV2();
+ } else {
+ $deps = $pkg->getDeps(true);
+ }
+
+ if (!$deps) {
+ return;
+ }
+
+ if (!is_array($data)) {
+ $data = array();
+ }
+
+ if (!isset($data['dependencies'])) {
+ $data['dependencies'] = array();
+ }
+
+ $channel = strtolower($pkg->getChannel());
+ $package = strtolower($pkg->getPackage());
+
+ if (!isset($data['dependencies'][$channel])) {
+ $data['dependencies'][$channel] = array();
+ }
+
+ $data['dependencies'][$channel][$package] = array();
+ if (isset($deps['required']['package'])) {
+ if (!isset($deps['required']['package'][0])) {
+ $deps['required']['package'] = array($deps['required']['package']);
+ }
+
+ foreach ($deps['required']['package'] as $dep) {
+ $this->_registerDep($data, $pkg, $dep, 'required');
+ }
+ }
+
+ if (isset($deps['optional']['package'])) {
+ if (!isset($deps['optional']['package'][0])) {
+ $deps['optional']['package'] = array($deps['optional']['package']);
+ }
+
+ foreach ($deps['optional']['package'] as $dep) {
+ $this->_registerDep($data, $pkg, $dep, 'optional');
+ }
+ }
+
+ if (isset($deps['required']['subpackage'])) {
+ if (!isset($deps['required']['subpackage'][0])) {
+ $deps['required']['subpackage'] = array($deps['required']['subpackage']);
+ }
+
+ foreach ($deps['required']['subpackage'] as $dep) {
+ $this->_registerDep($data, $pkg, $dep, 'required');
+ }
+ }
+
+ if (isset($deps['optional']['subpackage'])) {
+ if (!isset($deps['optional']['subpackage'][0])) {
+ $deps['optional']['subpackage'] = array($deps['optional']['subpackage']);
+ }
+
+ foreach ($deps['optional']['subpackage'] as $dep) {
+ $this->_registerDep($data, $pkg, $dep, 'optional');
+ }
+ }
+
+ if (isset($deps['group'])) {
+ if (!isset($deps['group'][0])) {
+ $deps['group'] = array($deps['group']);
+ }
+
+ foreach ($deps['group'] as $group) {
+ if (isset($group['package'])) {
+ if (!isset($group['package'][0])) {
+ $group['package'] = array($group['package']);
+ }
+
+ foreach ($group['package'] as $dep) {
+ $this->_registerDep($data, $pkg, $dep, 'optional',
+ $group['attribs']['name']);
+ }
+ }
+
+ if (isset($group['subpackage'])) {
+ if (!isset($group['subpackage'][0])) {
+ $group['subpackage'] = array($group['subpackage']);
+ }
+
+ foreach ($group['subpackage'] as $dep) {
+ $this->_registerDep($data, $pkg, $dep, 'optional',
+ $group['attribs']['name']);
+ }
+ }
+ }
+ }
+
+ if ($data['dependencies'][$channel][$package] == array()) {
+ unset($data['dependencies'][$channel][$package]);
+ if (!count($data['dependencies'][$channel])) {
+ unset($data['dependencies'][$channel]);
+ }
+ }
+ }
+
+ /**
+ * @param array the database
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * @param array the specific dependency
+ * @param required|optional whether this is a required or an optional dep
+ * @param string|false dependency group this dependency is from, or false for ordinary dep
+ */
+ private function _registerDep(&$data, &$pkg, $dep, $type, $group = false)
+ {
+ $info = array(
+ 'dep' => $dep,
+ 'type' => $type,
+ 'group' => $group
+ );
+
+ $dep = array_map('strtolower', $dep);
+ $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri';
+ if (!isset($data['dependencies'])) {
+ $data['dependencies'] = array();
+ }
+
+ $channel = strtolower($pkg->getChannel());
+ $package = strtolower($pkg->getPackage());
+
+ if (!isset($data['dependencies'][$channel])) {
+ $data['dependencies'][$channel] = array();
+ }
+
+ if (!isset($data['dependencies'][$channel][$package])) {
+ $data['dependencies'][$channel][$package] = array();
+ }
+
+ $data['dependencies'][$channel][$package][] = $info;
+ if (isset($data['packages'][$depchannel][$dep['name']])) {
+ $found = false;
+ foreach ($data['packages'][$depchannel][$dep['name']] as $i => $p) {
+ if ($p['channel'] == $channel && $p['package'] == $package) {
+ $found = true;
+ break;
+ }
+ }
+ } else {
+ if (!isset($data['packages'])) {
+ $data['packages'] = array();
+ }
+
+ if (!isset($data['packages'][$depchannel])) {
+ $data['packages'][$depchannel] = array();
+ }
+
+ if (!isset($data['packages'][$depchannel][$dep['name']])) {
+ $data['packages'][$depchannel][$dep['name']] = array();
+ }
+
+ $found = false;
+ }
+
+ if (!$found) {
+ $data['packages'][$depchannel][$dep['name']][] = array(
+ 'channel' => $channel,
+ 'package' => $package
+ );
+ }
+ }
+}
diff --git a/includes/pear/PEAR/Downloader.php b/includes/pear/PEAR/Downloader.php
new file mode 100644
index 0000000..b42a210
--- /dev/null
+++ b/includes/pear/PEAR/Downloader.php
@@ -0,0 +1,1766 @@
+<?php
+/**
+ * PEAR_Downloader, the PEAR Installer's download utility class
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V. V. Cox <cox@idecnet.com>
+ * @author Martin Jansen <mj@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.3.0
+ */
+
+/**
+ * Needed for constants, extending
+ */
+require_once 'PEAR/Common.php';
+
+define('PEAR_INSTALLER_OK', 1);
+define('PEAR_INSTALLER_FAILED', 0);
+define('PEAR_INSTALLER_SKIPPED', -1);
+define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2);
+
+/**
+ * Administration class used to download anything from the internet (PEAR Packages,
+ * static URLs, xml files)
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V. V. Cox <cox@idecnet.com>
+ * @author Martin Jansen <mj@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.3.0
+ */
+class PEAR_Downloader extends PEAR_Common
+{
+ /**
+ * @var PEAR_Registry
+ * @access private
+ */
+ var $_registry;
+
+ /**
+ * Preferred Installation State (snapshot, devel, alpha, beta, stable)
+ * @var string|null
+ * @access private
+ */
+ var $_preferredState;
+
+ /**
+ * Options from command-line passed to Install.
+ *
+ * Recognized options:<br />
+ * - onlyreqdeps : install all required dependencies as well
+ * - alldeps : install all dependencies, including optional
+ * - installroot : base relative path to install files in
+ * - force : force a download even if warnings would prevent it
+ * - nocompress : download uncompressed tarballs
+ * @see PEAR_Command_Install
+ * @access private
+ * @var array
+ */
+ var $_options;
+
+ /**
+ * Downloaded Packages after a call to download().
+ *
+ * Format of each entry:
+ *
+ * <code>
+ * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
+ * 'info' => array() // parsed package.xml
+ * );
+ * </code>
+ * @access private
+ * @var array
+ */
+ var $_downloadedPackages = array();
+
+ /**
+ * Packages slated for download.
+ *
+ * This is used to prevent downloading a package more than once should it be a dependency
+ * for two packages to be installed.
+ * Format of each entry:
+ *
+ * <pre>
+ * array('package_name1' => parsed package.xml, 'package_name2' => parsed package.xml,
+ * );
+ * </pre>
+ * @access private
+ * @var array
+ */
+ var $_toDownload = array();
+
+ /**
+ * Array of every package installed, with names lower-cased.
+ *
+ * Format:
+ * <code>
+ * array('package1' => 0, 'package2' => 1, );
+ * </code>
+ * @var array
+ */
+ var $_installed = array();
+
+ /**
+ * @var array
+ * @access private
+ */
+ var $_errorStack = array();
+
+ /**
+ * @var boolean
+ * @access private
+ */
+ var $_internalDownload = false;
+
+ /**
+ * Temporary variable used in sorting packages by dependency in {@link sortPkgDeps()}
+ * @var array
+ * @access private
+ */
+ var $_packageSortTree;
+
+ /**
+ * Temporary directory, or configuration value where downloads will occur
+ * @var string
+ */
+ var $_downloadDir;
+
+ /**
+ * @param PEAR_Frontend_*
+ * @param array
+ * @param PEAR_Config
+ */
+ function PEAR_Downloader(&$ui, $options, &$config)
+ {
+ parent::PEAR_Common();
+ $this->_options = $options;
+ $this->config = &$config;
+ $this->_preferredState = $this->config->get('preferred_state');
+ $this->ui = &$ui;
+ if (!$this->_preferredState) {
+ // don't inadvertantly use a non-set preferred_state
+ $this->_preferredState = null;
+ }
+
+ if (isset($this->_options['installroot'])) {
+ $this->config->setInstallRoot($this->_options['installroot']);
+ }
+ $this->_registry = &$config->getRegistry();
+
+ if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) {
+ $this->_installed = $this->_registry->listAllPackages();
+ foreach ($this->_installed as $key => $unused) {
+ if (!count($unused)) {
+ continue;
+ }
+ $strtolower = create_function('$a','return strtolower($a);');
+ array_walk($this->_installed[$key], $strtolower);
+ }
+ }
+ }
+
+ /**
+ * Attempt to discover a channel's remote capabilities from
+ * its server name
+ * @param string
+ * @return boolean
+ */
+ function discover($channel)
+ {
+ $this->log(1, 'Attempting to discover channel "' . $channel . '"...');
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $callback = $this->ui ? array(&$this, '_downloadCallback') : null;
+ if (!class_exists('System')) {
+ require_once 'System.php';
+ }
+
+ $tmpdir = $this->config->get('temp_dir');
+ $tmp = System::mktemp('-d -t "' . $tmpdir . '"');
+ $a = $this->downloadHttp('http://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false);
+ PEAR::popErrorHandling();
+ if (PEAR::isError($a)) {
+ // Attempt to fallback to https automatically.
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $this->log(1, 'Attempting fallback to https instead of http on channel "' . $channel . '"...');
+ $a = $this->downloadHttp('https://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false);
+ PEAR::popErrorHandling();
+ if (PEAR::isError($a)) {
+ return false;
+ }
+ }
+
+ list($a, $lastmodified) = $a;
+ if (!class_exists('PEAR_ChannelFile')) {
+ require_once 'PEAR/ChannelFile.php';
+ }
+
+ $b = new PEAR_ChannelFile;
+ if ($b->fromXmlFile($a)) {
+ unlink($a);
+ if ($this->config->get('auto_discover')) {
+ $this->_registry->addChannel($b, $lastmodified);
+ $alias = $b->getName();
+ if ($b->getName() == $this->_registry->channelName($b->getAlias())) {
+ $alias = $b->getAlias();
+ }
+
+ $this->log(1, 'Auto-discovered channel "' . $channel .
+ '", alias "' . $alias . '", adding to registry');
+ }
+
+ return true;
+ }
+
+ unlink($a);
+ return false;
+ }
+
+ /**
+ * For simpler unit-testing
+ * @param PEAR_Downloader
+ * @return PEAR_Downloader_Package
+ */
+ function &newDownloaderPackage(&$t)
+ {
+ if (!class_exists('PEAR_Downloader_Package')) {
+ require_once 'PEAR/Downloader/Package.php';
+ }
+ $a = &new PEAR_Downloader_Package($t);
+ return $a;
+ }
+
+ /**
+ * For simpler unit-testing
+ * @param PEAR_Config
+ * @param array
+ * @param array
+ * @param int
+ */
+ function &getDependency2Object(&$c, $i, $p, $s)
+ {
+ if (!class_exists('PEAR_Dependency2')) {
+ require_once 'PEAR/Dependency2.php';
+ }
+ $z = &new PEAR_Dependency2($c, $i, $p, $s);
+ return $z;
+ }
+
+ function &download($params)
+ {
+ if (!count($params)) {
+ $a = array();
+ return $a;
+ }
+
+ if (!isset($this->_registry)) {
+ $this->_registry = &$this->config->getRegistry();
+ }
+
+ $channelschecked = array();
+ // convert all parameters into PEAR_Downloader_Package objects
+ foreach ($params as $i => $param) {
+ $params[$i] = &$this->newDownloaderPackage($this);
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $err = $params[$i]->initialize($param);
+ PEAR::staticPopErrorHandling();
+ if (!$err) {
+ // skip parameters that were missed by preferred_state
+ continue;
+ }
+
+ if (PEAR::isError($err)) {
+ if (!isset($this->_options['soft']) && $err->getMessage() !== '') {
+ $this->log(0, $err->getMessage());
+ }
+
+ $params[$i] = false;
+ if (is_object($param)) {
+ $param = $param->getChannel() . '/' . $param->getPackage();
+ }
+
+ if (!isset($this->_options['soft'])) {
+ $this->log(2, 'Package "' . $param . '" is not valid');
+ }
+
+ // Message logged above in a specific verbose mode, passing null to not show up on CLI
+ $this->pushError(null, PEAR_INSTALLER_SKIPPED);
+ } else {
+ do {
+ if ($params[$i] && $params[$i]->getType() == 'local') {
+ // bug #7090 skip channel.xml check for local packages
+ break;
+ }
+
+ if ($params[$i] && !isset($channelschecked[$params[$i]->getChannel()]) &&
+ !isset($this->_options['offline'])
+ ) {
+ $channelschecked[$params[$i]->getChannel()] = true;
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ if (!class_exists('System')) {
+ require_once 'System.php';
+ }
+
+ $curchannel = &$this->_registry->getChannel($params[$i]->getChannel());
+ if (PEAR::isError($curchannel)) {
+ PEAR::staticPopErrorHandling();
+ return $this->raiseError($curchannel);
+ }
+
+ if (PEAR::isError($dir = $this->getDownloadDir())) {
+ PEAR::staticPopErrorHandling();
+ break;
+ }
+
+ $mirror = $this->config->get('preferred_mirror', null, $params[$i]->getChannel());
+ $url = 'http://' . $mirror . '/channel.xml';
+ $a = $this->downloadHttp($url, $this->ui, $dir, null, $curchannel->lastModified());
+
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($a) || !$a) {
+ // Attempt fallback to https automatically
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $a = $this->downloadHttp('https://' . $mirror .
+ '/channel.xml', $this->ui, $dir, null, $curchannel->lastModified());
+
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($a) || !$a) {
+ break;
+ }
+ }
+ $this->log(0, 'WARNING: channel "' . $params[$i]->getChannel() . '" has ' .
+ 'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $params[$i]->getChannel() .
+ '" to update');
+ }
+ } while (false);
+
+ if ($params[$i] && !isset($this->_options['downloadonly'])) {
+ if (isset($this->_options['packagingroot'])) {
+ $checkdir = $this->_prependPath(
+ $this->config->get('php_dir', null, $params[$i]->getChannel()),
+ $this->_options['packagingroot']);
+ } else {
+ $checkdir = $this->config->get('php_dir',
+ null, $params[$i]->getChannel());
+ }
+
+ while ($checkdir && $checkdir != '/' && !file_exists($checkdir)) {
+ $checkdir = dirname($checkdir);
+ }
+
+ if ($checkdir == '.') {
+ $checkdir = '/';
+ }
+
+ if (!is_writeable($checkdir)) {
+ return PEAR::raiseError('Cannot install, php_dir for channel "' .
+ $params[$i]->getChannel() . '" is not writeable by the current user');
+ }
+ }
+ }
+ }
+
+ unset($channelschecked);
+ PEAR_Downloader_Package::removeDuplicates($params);
+ if (!count($params)) {
+ $a = array();
+ return $a;
+ }
+
+ if (!isset($this->_options['nodeps']) && !isset($this->_options['offline'])) {
+ $reverify = true;
+ while ($reverify) {
+ $reverify = false;
+ foreach ($params as $i => $param) {
+ //PHP Bug 40768 / PEAR Bug #10944
+ //Nested foreaches fail in PHP 5.2.1
+ key($params);
+ $ret = $params[$i]->detectDependencies($params);
+ if (PEAR::isError($ret)) {
+ $reverify = true;
+ $params[$i] = false;
+ PEAR_Downloader_Package::removeDuplicates($params);
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $ret->getMessage());
+ }
+ continue 2;
+ }
+ }
+ }
+ }
+
+ if (isset($this->_options['offline'])) {
+ $this->log(3, 'Skipping dependency download check, --offline specified');
+ }
+
+ if (!count($params)) {
+ $a = array();
+ return $a;
+ }
+
+ while (PEAR_Downloader_Package::mergeDependencies($params));
+ PEAR_Downloader_Package::removeDuplicates($params, true);
+ $errorparams = array();
+ if (PEAR_Downloader_Package::detectStupidDuplicates($params, $errorparams)) {
+ if (count($errorparams)) {
+ foreach ($errorparams as $param) {
+ $name = $this->_registry->parsedPackageNameToString($param->getParsedPackage());
+ $this->pushError('Duplicate package ' . $name . ' found', PEAR_INSTALLER_FAILED);
+ }
+ $a = array();
+ return $a;
+ }
+ }
+
+ PEAR_Downloader_Package::removeInstalled($params);
+ if (!count($params)) {
+ $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED);
+ $a = array();
+ return $a;
+ }
+
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $err = $this->analyzeDependencies($params);
+ PEAR::popErrorHandling();
+ if (!count($params)) {
+ $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED);
+ $a = array();
+ return $a;
+ }
+
+ $ret = array();
+ $newparams = array();
+ if (isset($this->_options['pretend'])) {
+ return $params;
+ }
+
+ $somefailed = false;
+ foreach ($params as $i => $package) {
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $pf = &$params[$i]->download();
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($pf)) {
+ if (!isset($this->_options['soft'])) {
+ $this->log(1, $pf->getMessage());
+ $this->log(0, 'Error: cannot download "' .
+ $this->_registry->parsedPackageNameToString($package->getParsedPackage(),
+ true) .
+ '"');
+ }
+ $somefailed = true;
+ continue;
+ }
+
+ $newparams[] = &$params[$i];
+ $ret[] = array(
+ 'file' => $pf->getArchiveFile(),
+ 'info' => &$pf,
+ 'pkg' => $pf->getPackage()
+ );
+ }
+
+ if ($somefailed) {
+ // remove params that did not download successfully
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $err = $this->analyzeDependencies($newparams, true);
+ PEAR::popErrorHandling();
+ if (!count($newparams)) {
+ $this->pushError('Download failed', PEAR_INSTALLER_FAILED);
+ $a = array();
+ return $a;
+ }
+ }
+
+ $this->_downloadedPackages = $ret;
+ return $newparams;
+ }
+
+ /**
+ * @param array all packages to be installed
+ */
+ function analyzeDependencies(&$params, $force = false)
+ {
+ if (isset($this->_options['downloadonly'])) {
+ return;
+ }
+
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $redo = true;
+ $reset = $hasfailed = $failed = false;
+ while ($redo) {
+ $redo = false;
+ foreach ($params as $i => $param) {
+ $deps = $param->getDeps();
+ if (!$deps) {
+ $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(),
+ $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING);
+ $send = $param->getPackageFile();
+
+ $installcheck = $depchecker->validatePackage($send, $this, $params);
+ if (PEAR::isError($installcheck)) {
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $installcheck->getMessage());
+ }
+ $hasfailed = true;
+ $params[$i] = false;
+ $reset = true;
+ $redo = true;
+ $failed = false;
+ PEAR_Downloader_Package::removeDuplicates($params);
+ continue 2;
+ }
+ continue;
+ }
+
+ if (!$reset && $param->alreadyValidated() && !$force) {
+ continue;
+ }
+
+ if (count($deps)) {
+ $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(),
+ $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING);
+ $send = $param->getPackageFile();
+ if ($send === null) {
+ $send = $param->getDownloadURL();
+ }
+
+ $installcheck = $depchecker->validatePackage($send, $this, $params);
+ if (PEAR::isError($installcheck)) {
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $installcheck->getMessage());
+ }
+ $hasfailed = true;
+ $params[$i] = false;
+ $reset = true;
+ $redo = true;
+ $failed = false;
+ PEAR_Downloader_Package::removeDuplicates($params);
+ continue 2;
+ }
+
+ $failed = false;
+ if (isset($deps['required']) && is_array($deps['required'])) {
+ foreach ($deps['required'] as $type => $dep) {
+ // note: Dependency2 will never return a PEAR_Error if ignore-errors
+ // is specified, so soft is needed to turn off logging
+ if (!isset($dep[0])) {
+ if (PEAR::isError($e = $depchecker->{"validate{$type}Dependency"}($dep,
+ true, $params))) {
+ $failed = true;
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $e->getMessage());
+ }
+ } elseif (is_array($e) && !$param->alreadyValidated()) {
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $e[0]);
+ }
+ }
+ } else {
+ foreach ($dep as $d) {
+ if (PEAR::isError($e =
+ $depchecker->{"validate{$type}Dependency"}($d,
+ true, $params))) {
+ $failed = true;
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $e->getMessage());
+ }
+ } elseif (is_array($e) && !$param->alreadyValidated()) {
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $e[0]);
+ }
+ }
+ }
+ }
+ }
+
+ if (isset($deps['optional']) && is_array($deps['optional'])) {
+ foreach ($deps['optional'] as $type => $dep) {
+ if (!isset($dep[0])) {
+ if (PEAR::isError($e =
+ $depchecker->{"validate{$type}Dependency"}($dep,
+ false, $params))) {
+ $failed = true;
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $e->getMessage());
+ }
+ } elseif (is_array($e) && !$param->alreadyValidated()) {
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $e[0]);
+ }
+ }
+ } else {
+ foreach ($dep as $d) {
+ if (PEAR::isError($e =
+ $depchecker->{"validate{$type}Dependency"}($d,
+ false, $params))) {
+ $failed = true;
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $e->getMessage());
+ }
+ } elseif (is_array($e) && !$param->alreadyValidated()) {
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $e[0]);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ $groupname = $param->getGroup();
+ if (isset($deps['group']) && $groupname) {
+ if (!isset($deps['group'][0])) {
+ $deps['group'] = array($deps['group']);
+ }
+
+ $found = false;
+ foreach ($deps['group'] as $group) {
+ if ($group['attribs']['name'] == $groupname) {
+ $found = true;
+ break;
+ }
+ }
+
+ if ($found) {
+ unset($group['attribs']);
+ foreach ($group as $type => $dep) {
+ if (!isset($dep[0])) {
+ if (PEAR::isError($e =
+ $depchecker->{"validate{$type}Dependency"}($dep,
+ false, $params))) {
+ $failed = true;
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $e->getMessage());
+ }
+ } elseif (is_array($e) && !$param->alreadyValidated()) {
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $e[0]);
+ }
+ }
+ } else {
+ foreach ($dep as $d) {
+ if (PEAR::isError($e =
+ $depchecker->{"validate{$type}Dependency"}($d,
+ false, $params))) {
+ $failed = true;
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $e->getMessage());
+ }
+ } elseif (is_array($e) && !$param->alreadyValidated()) {
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $e[0]);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } else {
+ foreach ($deps as $dep) {
+ if (PEAR::isError($e = $depchecker->validateDependency1($dep, $params))) {
+ $failed = true;
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $e->getMessage());
+ }
+ } elseif (is_array($e) && !$param->alreadyValidated()) {
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $e[0]);
+ }
+ }
+ }
+ }
+ $params[$i]->setValidated();
+ }
+
+ if ($failed) {
+ $hasfailed = true;
+ $params[$i] = false;
+ $reset = true;
+ $redo = true;
+ $failed = false;
+ PEAR_Downloader_Package::removeDuplicates($params);
+ continue 2;
+ }
+ }
+ }
+
+ PEAR::staticPopErrorHandling();
+ if ($hasfailed && (isset($this->_options['ignore-errors']) ||
+ isset($this->_options['nodeps']))) {
+ // this is probably not needed, but just in case
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, 'WARNING: dependencies failed');
+ }
+ }
+ }
+
+ /**
+ * Retrieve the directory that downloads will happen in
+ * @access private
+ * @return string
+ */
+ function getDownloadDir()
+ {
+ if (isset($this->_downloadDir)) {
+ return $this->_downloadDir;
+ }
+
+ $downloaddir = $this->config->get('download_dir');
+ if (empty($downloaddir) || (is_dir($downloaddir) && !is_writable($downloaddir))) {
+ if (is_dir($downloaddir) && !is_writable($downloaddir)) {
+ $this->log(0, 'WARNING: configuration download directory "' . $downloaddir .
+ '" is not writeable. Change download_dir config variable to ' .
+ 'a writeable dir to avoid this warning');
+ }
+
+ if (!class_exists('System')) {
+ require_once 'System.php';
+ }
+
+ if (PEAR::isError($downloaddir = System::mktemp('-d'))) {
+ return $downloaddir;
+ }
+ $this->log(3, '+ tmp dir created at ' . $downloaddir);
+ }
+
+ if (!is_writable($downloaddir)) {
+ if (PEAR::isError(System::mkdir(array('-p', $downloaddir))) ||
+ !is_writable($downloaddir)) {
+ return PEAR::raiseError('download directory "' . $downloaddir .
+ '" is not writeable. Change download_dir config variable to ' .
+ 'a writeable dir');
+ }
+ }
+
+ return $this->_downloadDir = $downloaddir;
+ }
+
+ function setDownloadDir($dir)
+ {
+ if (!@is_writable($dir)) {
+ if (PEAR::isError(System::mkdir(array('-p', $dir)))) {
+ return PEAR::raiseError('download directory "' . $dir .
+ '" is not writeable. Change download_dir config variable to ' .
+ 'a writeable dir');
+ }
+ }
+ $this->_downloadDir = $dir;
+ }
+
+ function configSet($key, $value, $layer = 'user', $channel = false)
+ {
+ $this->config->set($key, $value, $layer, $channel);
+ $this->_preferredState = $this->config->get('preferred_state', null, $channel);
+ if (!$this->_preferredState) {
+ // don't inadvertantly use a non-set preferred_state
+ $this->_preferredState = null;
+ }
+ }
+
+ function setOptions($options)
+ {
+ $this->_options = $options;
+ }
+
+ function getOptions()
+ {
+ return $this->_options;
+ }
+
+
+ /**
+ * @param array output of {@link parsePackageName()}
+ * @access private
+ */
+ function _getPackageDownloadUrl($parr)
+ {
+ $curchannel = $this->config->get('default_channel');
+ $this->configSet('default_channel', $parr['channel']);
+ // getDownloadURL returns an array. On error, it only contains information
+ // on the latest release as array(version, info). On success it contains
+ // array(version, info, download url string)
+ $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state');
+ if (!$this->_registry->channelExists($parr['channel'])) {
+ do {
+ if ($this->config->get('auto_discover') && $this->discover($parr['channel'])) {
+ break;
+ }
+
+ $this->configSet('default_channel', $curchannel);
+ return PEAR::raiseError('Unknown remote channel: ' . $parr['channel']);
+ } while (false);
+ }
+
+ $chan = &$this->_registry->getChannel($parr['channel']);
+ if (PEAR::isError($chan)) {
+ return $chan;
+ }
+
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $version = $this->_registry->packageInfo($parr['package'], 'version', $parr['channel']);
+ $stability = $this->_registry->packageInfo($parr['package'], 'stability', $parr['channel']);
+ // package is installed - use the installed release stability level
+ if (!isset($parr['state']) && $stability !== null) {
+ $state = $stability['release'];
+ }
+ PEAR::staticPopErrorHandling();
+ $base2 = false;
+
+ $preferred_mirror = $this->config->get('preferred_mirror');
+ if (!$chan->supportsREST($preferred_mirror) ||
+ (
+ !($base2 = $chan->getBaseURL('REST1.3', $preferred_mirror))
+ &&
+ !($base = $chan->getBaseURL('REST1.0', $preferred_mirror))
+ )
+ ) {
+ return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.');
+ }
+
+ if ($base2) {
+ $rest = &$this->config->getREST('1.3', $this->_options);
+ $base = $base2;
+ } else {
+ $rest = &$this->config->getREST('1.0', $this->_options);
+ }
+
+ $downloadVersion = false;
+ if (!isset($parr['version']) && !isset($parr['state']) && $version
+ && !PEAR::isError($version)
+ && !isset($this->_options['downloadonly'])
+ ) {
+ $downloadVersion = $version;
+ }
+
+ $url = $rest->getDownloadURL($base, $parr, $state, $downloadVersion, $chan->getName());
+ if (PEAR::isError($url)) {
+ $this->configSet('default_channel', $curchannel);
+ return $url;
+ }
+
+ if ($parr['channel'] != $curchannel) {
+ $this->configSet('default_channel', $curchannel);
+ }
+
+ if (!is_array($url)) {
+ return $url;
+ }
+
+ $url['raw'] = false; // no checking is necessary for REST
+ if (!is_array($url['info'])) {
+ return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' .
+ 'this should never happen');
+ }
+
+ if (!isset($this->_options['force']) &&
+ !isset($this->_options['downloadonly']) &&
+ $version &&
+ !PEAR::isError($version) &&
+ !isset($parr['group'])
+ ) {
+ if (version_compare($version, $url['version'], '=')) {
+ return PEAR::raiseError($this->_registry->parsedPackageNameToString(
+ $parr, true) . ' is already installed and is the same as the ' .
+ 'released version ' . $url['version'], -976);
+ }
+
+ if (version_compare($version, $url['version'], '>')) {
+ return PEAR::raiseError($this->_registry->parsedPackageNameToString(
+ $parr, true) . ' is already installed and is newer than detected ' .
+ 'released version ' . $url['version'], -976);
+ }
+ }
+
+ if (isset($url['info']['required']) || $url['compatible']) {
+ require_once 'PEAR/PackageFile/v2.php';
+ $pf = new PEAR_PackageFile_v2;
+ $pf->setRawChannel($parr['channel']);
+ if ($url['compatible']) {
+ $pf->setRawCompatible($url['compatible']);
+ }
+ } else {
+ require_once 'PEAR/PackageFile/v1.php';
+ $pf = new PEAR_PackageFile_v1;
+ }
+
+ $pf->setRawPackage($url['package']);
+ $pf->setDeps($url['info']);
+ if ($url['compatible']) {
+ $pf->setCompatible($url['compatible']);
+ }
+
+ $pf->setRawState($url['stability']);
+ $url['info'] = &$pf;
+ if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
+ $ext = '.tar';
+ } else {
+ $ext = '.tgz';
+ }
+
+ if (is_array($url) && isset($url['url'])) {
+ $url['url'] .= $ext;
+ }
+
+ return $url;
+ }
+
+ /**
+ * @param array dependency array
+ * @access private
+ */
+ function _getDepPackageDownloadUrl($dep, $parr)
+ {
+ $xsdversion = isset($dep['rel']) ? '1.0' : '2.0';
+ $curchannel = $this->config->get('default_channel');
+ if (isset($dep['uri'])) {
+ $xsdversion = '2.0';
+ $chan = &$this->_registry->getChannel('__uri');
+ if (PEAR::isError($chan)) {
+ return $chan;
+ }
+
+ $version = $this->_registry->packageInfo($dep['name'], 'version', '__uri');
+ $this->configSet('default_channel', '__uri');
+ } else {
+ if (isset($dep['channel'])) {
+ $remotechannel = $dep['channel'];
+ } else {
+ $remotechannel = 'pear.php.net';
+ }
+
+ if (!$this->_registry->channelExists($remotechannel)) {
+ do {
+ if ($this->config->get('auto_discover')) {
+ if ($this->discover($remotechannel)) {
+ break;
+ }
+ }
+ return PEAR::raiseError('Unknown remote channel: ' . $remotechannel);
+ } while (false);
+ }
+
+ $chan = &$this->_registry->getChannel($remotechannel);
+ if (PEAR::isError($chan)) {
+ return $chan;
+ }
+
+ $version = $this->_registry->packageInfo($dep['name'], 'version', $remotechannel);
+ $this->configSet('default_channel', $remotechannel);
+ }
+
+ $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state');
+ if (isset($parr['state']) && isset($parr['version'])) {
+ unset($parr['state']);
+ }
+
+ if (isset($dep['uri'])) {
+ $info = &$this->newDownloaderPackage($this);
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $err = $info->initialize($dep);
+ PEAR::staticPopErrorHandling();
+ if (!$err) {
+ // skip parameters that were missed by preferred_state
+ return PEAR::raiseError('Cannot initialize dependency');
+ }
+
+ if (PEAR::isError($err)) {
+ if (!isset($this->_options['soft'])) {
+ $this->log(0, $err->getMessage());
+ }
+
+ if (is_object($info)) {
+ $param = $info->getChannel() . '/' . $info->getPackage();
+ }
+ return PEAR::raiseError('Package "' . $param . '" is not valid');
+ }
+ return $info;
+ } elseif ($chan->supportsREST($this->config->get('preferred_mirror'))
+ &&
+ (
+ ($base2 = $chan->getBaseURL('REST1.3', $this->config->get('preferred_mirror')))
+ ||
+ ($base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror')))
+ )
+ ) {
+ if ($base2) {
+ $base = $base2;
+ $rest = &$this->config->getREST('1.3', $this->_options);
+ } else {
+ $rest = &$this->config->getREST('1.0', $this->_options);
+ }
+
+ $url = $rest->getDepDownloadURL($base, $xsdversion, $dep, $parr,
+ $state, $version, $chan->getName());
+ if (PEAR::isError($url)) {
+ return $url;
+ }
+
+ if ($parr['channel'] != $curchannel) {
+ $this->configSet('default_channel', $curchannel);
+ }
+
+ if (!is_array($url)) {
+ return $url;
+ }
+
+ $url['raw'] = false; // no checking is necessary for REST
+ if (!is_array($url['info'])) {
+ return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' .
+ 'this should never happen');
+ }
+
+ if (isset($url['info']['required'])) {
+ if (!class_exists('PEAR_PackageFile_v2')) {
+ require_once 'PEAR/PackageFile/v2.php';
+ }
+ $pf = new PEAR_PackageFile_v2;
+ $pf->setRawChannel($remotechannel);
+ } else {
+ if (!class_exists('PEAR_PackageFile_v1')) {
+ require_once 'PEAR/PackageFile/v1.php';
+ }
+ $pf = new PEAR_PackageFile_v1;
+
+ }
+ $pf->setRawPackage($url['package']);
+ $pf->setDeps($url['info']);
+ if ($url['compatible']) {
+ $pf->setCompatible($url['compatible']);
+ }
+
+ $pf->setRawState($url['stability']);
+ $url['info'] = &$pf;
+ if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
+ $ext = '.tar';
+ } else {
+ $ext = '.tgz';
+ }
+
+ if (is_array($url) && isset($url['url'])) {
+ $url['url'] .= $ext;
+ }
+
+ return $url;
+ }
+
+ return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.');
+ }
+
+ /**
+ * @deprecated in favor of _getPackageDownloadUrl
+ */
+ function getPackageDownloadUrl($package, $version = null, $channel = false)
+ {
+ if ($version) {
+ $package .= "-$version";
+ }
+ if ($this === null || $this->_registry === null) {
+ $package = "http://pear.php.net/get/$package";
+ } else {
+ $chan = $this->_registry->getChannel($channel);
+ if (PEAR::isError($chan)) {
+ return '';
+ }
+ $package = "http://" . $chan->getServer() . "/get/$package";
+ }
+ if (!extension_loaded("zlib")) {
+ $package .= '?uncompress=yes';
+ }
+ return $package;
+ }
+
+ /**
+ * Retrieve a list of downloaded packages after a call to {@link download()}.
+ *
+ * Also resets the list of downloaded packages.
+ * @return array
+ */
+ function getDownloadedPackages()
+ {
+ $ret = $this->_downloadedPackages;
+ $this->_downloadedPackages = array();
+ $this->_toDownload = array();
+ return $ret;
+ }
+
+ function _downloadCallback($msg, $params = null)
+ {
+ switch ($msg) {
+ case 'saveas':
+ $this->log(1, "downloading $params ...");
+ break;
+ case 'done':
+ $this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes');
+ break;
+ case 'bytesread':
+ static $bytes;
+ if (empty($bytes)) {
+ $bytes = 0;
+ }
+ if (!($bytes % 10240)) {
+ $this->log(1, '.', false);
+ }
+ $bytes += $params;
+ break;
+ case 'start':
+ if($params[1] == -1) {
+ $length = "Unknown size";
+ } else {
+ $length = number_format($params[1], 0, '', ',')." bytes";
+ }
+ $this->log(1, "Starting to download {$params[0]} ($length)");
+ break;
+ }
+ if (method_exists($this->ui, '_downloadCallback'))
+ $this->ui->_downloadCallback($msg, $params);
+ }
+
+ function _prependPath($path, $prepend)
+ {
+ if (strlen($prepend) > 0) {
+ if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) {
+ if (preg_match('/^[a-z]:/i', $prepend)) {
+ $prepend = substr($prepend, 2);
+ } elseif ($prepend{0} != '\\') {
+ $prepend = "\\$prepend";
+ }
+ $path = substr($path, 0, 2) . $prepend . substr($path, 2);
+ } else {
+ $path = $prepend . $path;
+ }
+ }
+ return $path;
+ }
+
+ /**
+ * @param string
+ * @param integer
+ */
+ function pushError($errmsg, $code = -1)
+ {
+ array_push($this->_errorStack, array($errmsg, $code));
+ }
+
+ function getErrorMsgs()
+ {
+ $msgs = array();
+ $errs = $this->_errorStack;
+ foreach ($errs as $err) {
+ $msgs[] = $err[0];
+ }
+ $this->_errorStack = array();
+ return $msgs;
+ }
+
+ /**
+ * for BC
+ *
+ * @deprecated
+ */
+ function sortPkgDeps(&$packages, $uninstall = false)
+ {
+ $uninstall ?
+ $this->sortPackagesForUninstall($packages) :
+ $this->sortPackagesForInstall($packages);
+ }
+
+ /**
+ * Sort a list of arrays of array(downloaded packagefilename) by dependency.
+ *
+ * This uses the topological sort method from graph theory, and the
+ * Structures_Graph package to properly sort dependencies for installation.
+ * @param array an array of downloaded PEAR_Downloader_Packages
+ * @return array array of array(packagefilename, package.xml contents)
+ */
+ function sortPackagesForInstall(&$packages)
+ {
+ require_once 'Structures/Graph.php';
+ require_once 'Structures/Graph/Node.php';
+ require_once 'Structures/Graph/Manipulator/TopologicalSorter.php';
+ $depgraph = new Structures_Graph(true);
+ $nodes = array();
+ $reg = &$this->config->getRegistry();
+ foreach ($packages as $i => $package) {
+ $pname = $reg->parsedPackageNameToString(
+ array(
+ 'channel' => $package->getChannel(),
+ 'package' => strtolower($package->getPackage()),
+ ));
+ $nodes[$pname] = new Structures_Graph_Node;
+ $nodes[$pname]->setData($packages[$i]);
+ $depgraph->addNode($nodes[$pname]);
+ }
+
+ $deplinks = array();
+ foreach ($nodes as $package => $node) {
+ $pf = &$node->getData();
+ $pdeps = $pf->getDeps(true);
+ if (!$pdeps) {
+ continue;
+ }
+
+ if ($pf->getPackagexmlVersion() == '1.0') {
+ foreach ($pdeps as $dep) {
+ if ($dep['type'] != 'pkg' ||
+ (isset($dep['optional']) && $dep['optional'] == 'yes')) {
+ continue;
+ }
+
+ $dname = $reg->parsedPackageNameToString(
+ array(
+ 'channel' => 'pear.php.net',
+ 'package' => strtolower($dep['name']),
+ ));
+
+ if (isset($nodes[$dname])) {
+ if (!isset($deplinks[$dname])) {
+ $deplinks[$dname] = array();
+ }
+
+ $deplinks[$dname][$package] = 1;
+ // dependency is in installed packages
+ continue;
+ }
+
+ $dname = $reg->parsedPackageNameToString(
+ array(
+ 'channel' => 'pecl.php.net',
+ 'package' => strtolower($dep['name']),
+ ));
+
+ if (isset($nodes[$dname])) {
+ if (!isset($deplinks[$dname])) {
+ $deplinks[$dname] = array();
+ }
+
+ $deplinks[$dname][$package] = 1;
+ // dependency is in installed packages
+ continue;
+ }
+ }
+ } else {
+ // the only ordering we care about is:
+ // 1) subpackages must be installed before packages that depend on them
+ // 2) required deps must be installed before packages that depend on them
+ if (isset($pdeps['required']['subpackage'])) {
+ $t = $pdeps['required']['subpackage'];
+ if (!isset($t[0])) {
+ $t = array($t);
+ }
+
+ $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
+ }
+
+ if (isset($pdeps['group'])) {
+ if (!isset($pdeps['group'][0])) {
+ $pdeps['group'] = array($pdeps['group']);
+ }
+
+ foreach ($pdeps['group'] as $group) {
+ if (isset($group['subpackage'])) {
+ $t = $group['subpackage'];
+ if (!isset($t[0])) {
+ $t = array($t);
+ }
+
+ $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
+ }
+ }
+ }
+
+ if (isset($pdeps['optional']['subpackage'])) {
+ $t = $pdeps['optional']['subpackage'];
+ if (!isset($t[0])) {
+ $t = array($t);
+ }
+
+ $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
+ }
+
+ if (isset($pdeps['required']['package'])) {
+ $t = $pdeps['required']['package'];
+ if (!isset($t[0])) {
+ $t = array($t);
+ }
+
+ $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
+ }
+
+ if (isset($pdeps['group'])) {
+ if (!isset($pdeps['group'][0])) {
+ $pdeps['group'] = array($pdeps['group']);
+ }
+
+ foreach ($pdeps['group'] as $group) {
+ if (isset($group['package'])) {
+ $t = $group['package'];
+ if (!isset($t[0])) {
+ $t = array($t);
+ }
+
+ $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
+ }
+ }
+ }
+ }
+ }
+
+ $this->_detectDepCycle($deplinks);
+ foreach ($deplinks as $dependent => $parents) {
+ foreach ($parents as $parent => $unused) {
+ $nodes[$dependent]->connectTo($nodes[$parent]);
+ }
+ }
+
+ $installOrder = Structures_Graph_Manipulator_TopologicalSorter::sort($depgraph);
+ $ret = array();
+ for ($i = 0, $count = count($installOrder); $i < $count; $i++) {
+ foreach ($installOrder[$i] as $index => $sortedpackage) {
+ $data = &$installOrder[$i][$index]->getData();
+ $ret[] = &$nodes[$reg->parsedPackageNameToString(
+ array(
+ 'channel' => $data->getChannel(),
+ 'package' => strtolower($data->getPackage()),
+ ))]->getData();
+ }
+ }
+
+ $packages = $ret;
+ return;
+ }
+
+ /**
+ * Detect recursive links between dependencies and break the cycles
+ *
+ * @param array
+ * @access private
+ */
+ function _detectDepCycle(&$deplinks)
+ {
+ do {
+ $keepgoing = false;
+ foreach ($deplinks as $dep => $parents) {
+ foreach ($parents as $parent => $unused) {
+ // reset the parent cycle detector
+ $this->_testCycle(null, null, null);
+ if ($this->_testCycle($dep, $deplinks, $parent)) {
+ $keepgoing = true;
+ unset($deplinks[$dep][$parent]);
+ if (count($deplinks[$dep]) == 0) {
+ unset($deplinks[$dep]);
+ }
+
+ continue 3;
+ }
+ }
+ }
+ } while ($keepgoing);
+ }
+
+ function _testCycle($test, $deplinks, $dep)
+ {
+ static $visited = array();
+ if ($test === null) {
+ $visited = array();
+ return;
+ }
+
+ // this happens when a parent has a dep cycle on another dependency
+ // but the child is not part of the cycle
+ if (isset($visited[$dep])) {
+ return false;
+ }
+
+ $visited[$dep] = 1;
+ if ($test == $dep) {
+ return true;
+ }
+
+ if (isset($deplinks[$dep])) {
+ if (in_array($test, array_keys($deplinks[$dep]), true)) {
+ return true;
+ }
+
+ foreach ($deplinks[$dep] as $parent => $unused) {
+ if ($this->_testCycle($test, $deplinks, $parent)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Set up the dependency for installation parsing
+ *
+ * @param array $t dependency information
+ * @param PEAR_Registry $reg
+ * @param array $deplinks list of dependency links already established
+ * @param array $nodes all existing package nodes
+ * @param string $package parent package name
+ * @access private
+ */
+ function _setupGraph($t, $reg, &$deplinks, &$nodes, $package)
+ {
+ foreach ($t as $dep) {
+ $depchannel = !isset($dep['channel']) ? '__uri': $dep['channel'];
+ $dname = $reg->parsedPackageNameToString(
+ array(
+ 'channel' => $depchannel,
+ 'package' => strtolower($dep['name']),
+ ));
+
+ if (isset($nodes[$dname])) {
+ if (!isset($deplinks[$dname])) {
+ $deplinks[$dname] = array();
+ }
+ $deplinks[$dname][$package] = 1;
+ }
+ }
+ }
+
+ function _dependsOn($a, $b)
+ {
+ return $this->_checkDepTree(strtolower($a->getChannel()), strtolower($a->getPackage()), $b);
+ }
+
+ function _checkDepTree($channel, $package, $b, $checked = array())
+ {
+ $checked[$channel][$package] = true;
+ if (!isset($this->_depTree[$channel][$package])) {
+ return false;
+ }
+
+ if (isset($this->_depTree[$channel][$package][strtolower($b->getChannel())]
+ [strtolower($b->getPackage())])) {
+ return true;
+ }
+
+ foreach ($this->_depTree[$channel][$package] as $ch => $packages) {
+ foreach ($packages as $pa => $true) {
+ if ($this->_checkDepTree($ch, $pa, $b, $checked)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ function _sortInstall($a, $b)
+ {
+ if (!$a->getDeps() && !$b->getDeps()) {
+ return 0; // neither package has dependencies, order is insignificant
+ }
+ if ($a->getDeps() && !$b->getDeps()) {
+ return 1; // $a must be installed after $b because $a has dependencies
+ }
+ if (!$a->getDeps() && $b->getDeps()) {
+ return -1; // $b must be installed after $a because $b has dependencies
+ }
+ // both packages have dependencies
+ if ($this->_dependsOn($a, $b)) {
+ return 1;
+ }
+ if ($this->_dependsOn($b, $a)) {
+ return -1;
+ }
+ return 0;
+ }
+
+ /**
+ * Download a file through HTTP. Considers suggested file name in
+ * Content-disposition: header and can run a callback function for
+ * different events. The callback will be called with two
+ * parameters: the callback type, and parameters. The implemented
+ * callback types are:
+ *
+ * 'setup' called at the very beginning, parameter is a UI object
+ * that should be used for all output
+ * 'message' the parameter is a string with an informational message
+ * 'saveas' may be used to save with a different file name, the
+ * parameter is the filename that is about to be used.
+ * If a 'saveas' callback returns a non-empty string,
+ * that file name will be used as the filename instead.
+ * Note that $save_dir will not be affected by this, only
+ * the basename of the file.
+ * 'start' download is starting, parameter is number of bytes
+ * that are expected, or -1 if unknown
+ * 'bytesread' parameter is the number of bytes read so far
+ * 'done' download is complete, parameter is the total number
+ * of bytes read
+ * 'connfailed' if the TCP/SSL connection fails, this callback is called
+ * with array(host,port,errno,errmsg)
+ * 'writefailed' if writing to disk fails, this callback is called
+ * with array(destfile,errmsg)
+ *
+ * If an HTTP proxy has been configured (http_proxy PEAR_Config
+ * setting), the proxy will be used.
+ *
+ * @param string $url the URL to download
+ * @param object $ui PEAR_Frontend_* instance
+ * @param object $config PEAR_Config instance
+ * @param string $save_dir directory to save file in
+ * @param mixed $callback function/method to call for status
+ * updates
+ * @param false|string|array $lastmodified header values to check against for caching
+ * use false to return the header values from this download
+ * @param false|array $accept Accept headers to send
+ * @param false|string $channel Channel to use for retrieving authentication
+ * @return string|array Returns the full path of the downloaded file or a PEAR
+ * error on failure. If the error is caused by
+ * socket-related errors, the error object will
+ * have the fsockopen error code available through
+ * getCode(). If caching is requested, then return the header
+ * values.
+ *
+ * @access public
+ */
+ function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodified = null,
+ $accept = false, $channel = false)
+ {
+ static $redirect = 0;
+ // always reset , so we are clean case of error
+ $wasredirect = $redirect;
+ $redirect = 0;
+ if ($callback) {
+ call_user_func($callback, 'setup', array(&$ui));
+ }
+
+ $info = parse_url($url);
+ if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) {
+ return PEAR::raiseError('Cannot download non-http URL "' . $url . '"');
+ }
+
+ if (!isset($info['host'])) {
+ return PEAR::raiseError('Cannot download from non-URL "' . $url . '"');
+ }
+
+ $host = isset($info['host']) ? $info['host'] : null;
+ $port = isset($info['port']) ? $info['port'] : null;
+ $path = isset($info['path']) ? $info['path'] : null;
+
+ if (isset($this)) {
+ $config = &$this->config;
+ } else {
+ $config = &PEAR_Config::singleton();
+ }
+
+ $proxy_host = $proxy_port = $proxy_user = $proxy_pass = '';
+ if ($config->get('http_proxy') &&
+ $proxy = parse_url($config->get('http_proxy'))) {
+ $proxy_host = isset($proxy['host']) ? $proxy['host'] : null;
+ if (isset($proxy['scheme']) && $proxy['scheme'] == 'https') {
+ $proxy_host = 'ssl://' . $proxy_host;
+ }
+ $proxy_port = isset($proxy['port']) ? $proxy['port'] : 8080;
+ $proxy_user = isset($proxy['user']) ? urldecode($proxy['user']) : null;
+ $proxy_pass = isset($proxy['pass']) ? urldecode($proxy['pass']) : null;
+
+ if ($callback) {
+ call_user_func($callback, 'message', "Using HTTP proxy $host:$port");
+ }
+ }
+
+ if (empty($port)) {
+ $port = (isset($info['scheme']) && $info['scheme'] == 'https') ? 443 : 80;
+ }
+
+ $scheme = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http';
+
+ if ($proxy_host != '') {
+ $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr);
+ if (!$fp) {
+ if ($callback) {
+ call_user_func($callback, 'connfailed', array($proxy_host, $proxy_port,
+ $errno, $errstr));
+ }
+ return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", $errno);
+ }
+
+ if ($lastmodified === false || $lastmodified) {
+ $request = "GET $url HTTP/1.1\r\n";
+ $request .= "Host: $host\r\n";
+ } else {
+ $request = "GET $url HTTP/1.0\r\n";
+ $request .= "Host: $host\r\n";
+ }
+ } else {
+ $network_host = $host;
+ if (isset($info['scheme']) && $info['scheme'] == 'https') {
+ $network_host = 'ssl://' . $host;
+ }
+
+ $fp = @fsockopen($network_host, $port, $errno, $errstr);
+ if (!$fp) {
+ if ($callback) {
+ call_user_func($callback, 'connfailed', array($host, $port,
+ $errno, $errstr));
+ }
+ return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno);
+ }
+
+ if ($lastmodified === false || $lastmodified) {
+ $request = "GET $path HTTP/1.1\r\n";
+ $request .= "Host: $host\r\n";
+ } else {
+ $request = "GET $path HTTP/1.0\r\n";
+ $request .= "Host: $host\r\n";
+ }
+ }
+
+ $ifmodifiedsince = '';
+ if (is_array($lastmodified)) {
+ if (isset($lastmodified['Last-Modified'])) {
+ $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n";
+ }
+
+ if (isset($lastmodified['ETag'])) {
+ $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n";
+ }
+ } else {
+ $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : '');
+ }
+
+ $request .= $ifmodifiedsince .
+ "User-Agent: PEAR/1.9.4/PHP/" . PHP_VERSION . "\r\n";
+
+ if (isset($this)) { // only pass in authentication for non-static calls
+ $username = $config->get('username', null, $channel);
+ $password = $config->get('password', null, $channel);
+ if ($username && $password) {
+ $tmp = base64_encode("$username:$password");
+ $request .= "Authorization: Basic $tmp\r\n";
+ }
+ }
+
+ if ($proxy_host != '' && $proxy_user != '') {
+ $request .= 'Proxy-Authorization: Basic ' .
+ base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n";
+ }
+
+ if ($accept) {
+ $request .= 'Accept: ' . implode(', ', $accept) . "\r\n";
+ }
+
+ $request .= "Connection: close\r\n";
+ $request .= "\r\n";
+ fwrite($fp, $request);
+ $headers = array();
+ $reply = 0;
+ while (trim($line = fgets($fp, 1024))) {
+ if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) {
+ $headers[strtolower($matches[1])] = trim($matches[2]);
+ } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) {
+ $reply = (int)$matches[1];
+ if ($reply == 304 && ($lastmodified || ($lastmodified === false))) {
+ return false;
+ }
+
+ if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) {
+ return PEAR::raiseError("File $scheme://$host:$port$path not valid (received: $line)");
+ }
+ }
+ }
+
+ if ($reply != 200) {
+ if (!isset($headers['location'])) {
+ return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirected but no location)");
+ }
+
+ if ($wasredirect > 4) {
+ return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirection looped more than 5 times)");
+ }
+
+ $redirect = $wasredirect + 1;
+ return $this->downloadHttp($headers['location'],
+ $ui, $save_dir, $callback, $lastmodified, $accept);
+ }
+
+ if (isset($headers['content-disposition']) &&
+ preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|\\z)/', $headers['content-disposition'], $matches)) {
+ $save_as = basename($matches[1]);
+ } else {
+ $save_as = basename($url);
+ }
+
+ if ($callback) {
+ $tmp = call_user_func($callback, 'saveas', $save_as);
+ if ($tmp) {
+ $save_as = $tmp;
+ }
+ }
+
+ $dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as;
+ if (is_link($dest_file)) {
+ return PEAR::raiseError('SECURITY ERROR: Will not write to ' . $dest_file . ' as it is symlinked to ' . readlink($dest_file) . ' - Possible symlink attack');
+ }
+
+ if (!$wp = @fopen($dest_file, 'wb')) {
+ fclose($fp);
+ if ($callback) {
+ call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
+ }
+ return PEAR::raiseError("could not open $dest_file for writing");
+ }
+
+ $length = isset($headers['content-length']) ? $headers['content-length'] : -1;
+
+ $bytes = 0;
+ if ($callback) {
+ call_user_func($callback, 'start', array(basename($dest_file), $length));
+ }
+
+ while ($data = fread($fp, 1024)) {
+ $bytes += strlen($data);
+ if ($callback) {
+ call_user_func($callback, 'bytesread', $bytes);
+ }
+ if (!@fwrite($wp, $data)) {
+ fclose($fp);
+ if ($callback) {
+ call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
+ }
+ return PEAR::raiseError("$dest_file: write failed ($php_errormsg)");
+ }
+ }
+
+ fclose($fp);
+ fclose($wp);
+ if ($callback) {
+ call_user_func($callback, 'done', $bytes);
+ }
+
+ if ($lastmodified === false || $lastmodified) {
+ if (isset($headers['etag'])) {
+ $lastmodified = array('ETag' => $headers['etag']);
+ }
+
+ if (isset($headers['last-modified'])) {
+ if (is_array($lastmodified)) {
+ $lastmodified['Last-Modified'] = $headers['last-modified'];
+ } else {
+ $lastmodified = $headers['last-modified'];
+ }
+ }
+ return array($dest_file, $lastmodified, $headers);
+ }
+ return $dest_file;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Downloader/Package.php b/includes/pear/PEAR/Downloader/Package.php
new file mode 100644
index 0000000..03a53ee
--- /dev/null
+++ b/includes/pear/PEAR/Downloader/Package.php
@@ -0,0 +1,1988 @@
+<?php
+/**
+ * PEAR_Downloader_Package
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * Error code when parameter initialization fails because no releases
+ * exist within preferred_state, but releases do exist
+ */
+define('PEAR_DOWNLOADER_PACKAGE_STATE', -1003);
+/**
+ * Error code when parameter initialization fails because no releases
+ * exist that will work with the existing PHP version
+ */
+define('PEAR_DOWNLOADER_PACKAGE_PHPVERSION', -1004);
+
+/**
+ * Coordinates download parameters and manages their dependencies
+ * prior to downloading them.
+ *
+ * Input can come from three sources:
+ *
+ * - local files (archives or package.xml)
+ * - remote files (downloadable urls)
+ * - abstract package names
+ *
+ * The first two elements are handled cleanly by PEAR_PackageFile, but the third requires
+ * accessing pearweb's xml-rpc interface to determine necessary dependencies, and the
+ * format returned of dependencies is slightly different from that used in package.xml.
+ *
+ * This class hides the differences between these elements, and makes automatic
+ * dependency resolution a piece of cake. It also manages conflicts when
+ * two classes depend on incompatible dependencies, or differing versions of the same
+ * package dependency. In addition, download will not be attempted if the php version is
+ * not supported, PEAR installer version is not supported, or non-PECL extensions are not
+ * installed.
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @PEAR-VER@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Downloader_Package
+{
+ /**
+ * @var PEAR_Downloader
+ */
+ var $_downloader;
+ /**
+ * @var PEAR_Config
+ */
+ var $_config;
+ /**
+ * @var PEAR_Registry
+ */
+ var $_registry;
+ /**
+ * Used to implement packagingroot properly
+ * @var PEAR_Registry
+ */
+ var $_installRegistry;
+ /**
+ * @var PEAR_PackageFile_v1|PEAR_PackageFile|v2
+ */
+ var $_packagefile;
+ /**
+ * @var array
+ */
+ var $_parsedname;
+ /**
+ * @var array
+ */
+ var $_downloadURL;
+ /**
+ * @var array
+ */
+ var $_downloadDeps = array();
+ /**
+ * @var boolean
+ */
+ var $_valid = false;
+ /**
+ * @var boolean
+ */
+ var $_analyzed = false;
+ /**
+ * if this or a parent package was invoked with Package-state, this is set to the
+ * state variable.
+ *
+ * This allows temporary reassignment of preferred_state for a parent package and all of
+ * its dependencies.
+ * @var string|false
+ */
+ var $_explicitState = false;
+ /**
+ * If this package is invoked with Package#group, this variable will be true
+ */
+ var $_explicitGroup = false;
+ /**
+ * Package type local|url
+ * @var string
+ */
+ var $_type;
+ /**
+ * Contents of package.xml, if downloaded from a remote channel
+ * @var string|false
+ * @access private
+ */
+ var $_rawpackagefile;
+ /**
+ * @var boolean
+ * @access private
+ */
+ var $_validated = false;
+
+ /**
+ * @param PEAR_Downloader
+ */
+ function PEAR_Downloader_Package(&$downloader)
+ {
+ $this->_downloader = &$downloader;
+ $this->_config = &$this->_downloader->config;
+ $this->_registry = &$this->_config->getRegistry();
+ $options = $downloader->getOptions();
+ if (isset($options['packagingroot'])) {
+ $this->_config->setInstallRoot($options['packagingroot']);
+ $this->_installRegistry = &$this->_config->getRegistry();
+ $this->_config->setInstallRoot(false);
+ } else {
+ $this->_installRegistry = &$this->_registry;
+ }
+ $this->_valid = $this->_analyzed = false;
+ }
+
+ /**
+ * Parse the input and determine whether this is a local file, a remote uri, or an
+ * abstract package name.
+ *
+ * This is the heart of the PEAR_Downloader_Package(), and is used in
+ * {@link PEAR_Downloader::download()}
+ * @param string
+ * @return bool|PEAR_Error
+ */
+ function initialize($param)
+ {
+ $origErr = $this->_fromFile($param);
+ if ($this->_valid) {
+ return true;
+ }
+
+ $options = $this->_downloader->getOptions();
+ if (isset($options['offline'])) {
+ if (PEAR::isError($origErr) && !isset($options['soft'])) {
+ foreach ($origErr->getUserInfo() as $userInfo) {
+ if (isset($userInfo['message'])) {
+ $this->_downloader->log(0, $userInfo['message']);
+ }
+ }
+
+ $this->_downloader->log(0, $origErr->getMessage());
+ }
+
+ return PEAR::raiseError('Cannot download non-local package "' . $param . '"');
+ }
+
+ $err = $this->_fromUrl($param);
+ if (PEAR::isError($err) || !$this->_valid) {
+ if ($this->_type == 'url') {
+ if (PEAR::isError($err) && !isset($options['soft'])) {
+ $this->_downloader->log(0, $err->getMessage());
+ }
+
+ return PEAR::raiseError("Invalid or missing remote package file");
+ }
+
+ $err = $this->_fromString($param);
+ if (PEAR::isError($err) || !$this->_valid) {
+ if (PEAR::isError($err) && $err->getCode() == PEAR_DOWNLOADER_PACKAGE_STATE) {
+ return false; // instruct the downloader to silently skip
+ }
+
+ if (isset($this->_type) && $this->_type == 'local' && PEAR::isError($origErr)) {
+ if (is_array($origErr->getUserInfo())) {
+ foreach ($origErr->getUserInfo() as $err) {
+ if (is_array($err)) {
+ $err = $err['message'];
+ }
+
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(0, $err);
+ }
+ }
+ }
+
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(0, $origErr->getMessage());
+ }
+
+ if (is_array($param)) {
+ $param = $this->_registry->parsedPackageNameToString($param, true);
+ }
+
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(2, "Cannot initialize '$param', invalid or missing package file");
+ }
+
+ // Passing no message back - already logged above
+ return PEAR::raiseError();
+ }
+
+ if (PEAR::isError($err) && !isset($options['soft'])) {
+ $this->_downloader->log(0, $err->getMessage());
+ }
+
+ if (is_array($param)) {
+ $param = $this->_registry->parsedPackageNameToString($param, true);
+ }
+
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(2, "Cannot initialize '$param', invalid or missing package file");
+ }
+
+ // Passing no message back - already logged above
+ return PEAR::raiseError();
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Retrieve any non-local packages
+ * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|PEAR_Error
+ */
+ function &download()
+ {
+ if (isset($this->_packagefile)) {
+ return $this->_packagefile;
+ }
+
+ if (isset($this->_downloadURL['url'])) {
+ $this->_isvalid = false;
+ $info = $this->getParsedPackage();
+ foreach ($info as $i => $p) {
+ $info[$i] = strtolower($p);
+ }
+
+ $err = $this->_fromUrl($this->_downloadURL['url'],
+ $this->_registry->parsedPackageNameToString($this->_parsedname, true));
+ $newinfo = $this->getParsedPackage();
+ foreach ($newinfo as $i => $p) {
+ $newinfo[$i] = strtolower($p);
+ }
+
+ if ($info != $newinfo) {
+ do {
+ if ($info['channel'] == 'pecl.php.net' && $newinfo['channel'] == 'pear.php.net') {
+ $info['channel'] = 'pear.php.net';
+ if ($info == $newinfo) {
+ // skip the channel check if a pecl package says it's a PEAR package
+ break;
+ }
+ }
+ if ($info['channel'] == 'pear.php.net' && $newinfo['channel'] == 'pecl.php.net') {
+ $info['channel'] = 'pecl.php.net';
+ if ($info == $newinfo) {
+ // skip the channel check if a pecl package says it's a PEAR package
+ break;
+ }
+ }
+
+ return PEAR::raiseError('CRITICAL ERROR: We are ' .
+ $this->_registry->parsedPackageNameToString($info) . ', but the file ' .
+ 'downloaded claims to be ' .
+ $this->_registry->parsedPackageNameToString($this->getParsedPackage()));
+ } while (false);
+ }
+
+ if (PEAR::isError($err) || !$this->_valid) {
+ return $err;
+ }
+ }
+
+ $this->_type = 'local';
+ return $this->_packagefile;
+ }
+
+ function &getPackageFile()
+ {
+ return $this->_packagefile;
+ }
+
+ function &getDownloader()
+ {
+ return $this->_downloader;
+ }
+
+ function getType()
+ {
+ return $this->_type;
+ }
+
+ /**
+ * Like {@link initialize()}, but operates on a dependency
+ */
+ function fromDepURL($dep)
+ {
+ $this->_downloadURL = $dep;
+ if (isset($dep['uri'])) {
+ $options = $this->_downloader->getOptions();
+ if (!extension_loaded("zlib") || isset($options['nocompress'])) {
+ $ext = '.tar';
+ } else {
+ $ext = '.tgz';
+ }
+
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $err = $this->_fromUrl($dep['uri'] . $ext);
+ PEAR::popErrorHandling();
+ if (PEAR::isError($err)) {
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(0, $err->getMessage());
+ }
+
+ return PEAR::raiseError('Invalid uri dependency "' . $dep['uri'] . $ext . '", ' .
+ 'cannot download');
+ }
+ } else {
+ $this->_parsedname =
+ array(
+ 'package' => $dep['info']->getPackage(),
+ 'channel' => $dep['info']->getChannel(),
+ 'version' => $dep['version']
+ );
+ if (!isset($dep['nodefault'])) {
+ $this->_parsedname['group'] = 'default'; // download the default dependency group
+ $this->_explicitGroup = false;
+ }
+
+ $this->_rawpackagefile = $dep['raw'];
+ }
+ }
+
+ function detectDependencies($params)
+ {
+ $options = $this->_downloader->getOptions();
+ if (isset($options['downloadonly'])) {
+ return;
+ }
+
+ if (isset($options['offline'])) {
+ $this->_downloader->log(3, 'Skipping dependency download check, --offline specified');
+ return;
+ }
+
+ $pname = $this->getParsedPackage();
+ if (!$pname) {
+ return;
+ }
+
+ $deps = $this->getDeps();
+ if (!$deps) {
+ return;
+ }
+
+ if (isset($deps['required'])) { // package.xml 2.0
+ return $this->_detect2($deps, $pname, $options, $params);
+ }
+
+ return $this->_detect1($deps, $pname, $options, $params);
+ }
+
+ function setValidated()
+ {
+ $this->_validated = true;
+ }
+
+ function alreadyValidated()
+ {
+ return $this->_validated;
+ }
+
+ /**
+ * Remove packages to be downloaded that are already installed
+ * @param array of PEAR_Downloader_Package objects
+ * @static
+ */
+ function removeInstalled(&$params)
+ {
+ if (!isset($params[0])) {
+ return;
+ }
+
+ $options = $params[0]->_downloader->getOptions();
+ if (!isset($options['downloadonly'])) {
+ foreach ($params as $i => $param) {
+ $package = $param->getPackage();
+ $channel = $param->getChannel();
+ // remove self if already installed with this version
+ // this does not need any pecl magic - we only remove exact matches
+ if ($param->_installRegistry->packageExists($package, $channel)) {
+ $packageVersion = $param->_installRegistry->packageInfo($package, 'version', $channel);
+ if (version_compare($packageVersion, $param->getVersion(), '==')) {
+ if (!isset($options['force'])) {
+ $info = $param->getParsedPackage();
+ unset($info['version']);
+ unset($info['state']);
+ if (!isset($options['soft'])) {
+ $param->_downloader->log(1, 'Skipping package "' .
+ $param->getShortName() .
+ '", already installed as version ' . $packageVersion);
+ }
+ $params[$i] = false;
+ }
+ } elseif (!isset($options['force']) && !isset($options['upgrade']) &&
+ !isset($options['soft'])) {
+ $info = $param->getParsedPackage();
+ $param->_downloader->log(1, 'Skipping package "' .
+ $param->getShortName() .
+ '", already installed as version ' . $packageVersion);
+ $params[$i] = false;
+ }
+ }
+ }
+ }
+
+ PEAR_Downloader_Package::removeDuplicates($params);
+ }
+
+ function _detect2($deps, $pname, $options, $params)
+ {
+ $this->_downloadDeps = array();
+ $groupnotfound = false;
+ foreach (array('package', 'subpackage') as $packagetype) {
+ // get required dependency group
+ if (isset($deps['required'][$packagetype])) {
+ if (isset($deps['required'][$packagetype][0])) {
+ foreach ($deps['required'][$packagetype] as $dep) {
+ if (isset($dep['conflicts'])) {
+ // skip any package that this package conflicts with
+ continue;
+ }
+ $ret = $this->_detect2Dep($dep, $pname, 'required', $params);
+ if (is_array($ret)) {
+ $this->_downloadDeps[] = $ret;
+ } elseif (PEAR::isError($ret) && !isset($options['soft'])) {
+ $this->_downloader->log(0, $ret->getMessage());
+ }
+ }
+ } else {
+ $dep = $deps['required'][$packagetype];
+ if (!isset($dep['conflicts'])) {
+ // skip any package that this package conflicts with
+ $ret = $this->_detect2Dep($dep, $pname, 'required', $params);
+ if (is_array($ret)) {
+ $this->_downloadDeps[] = $ret;
+ } elseif (PEAR::isError($ret) && !isset($options['soft'])) {
+ $this->_downloader->log(0, $ret->getMessage());
+ }
+ }
+ }
+ }
+
+ // get optional dependency group, if any
+ if (isset($deps['optional'][$packagetype])) {
+ $skipnames = array();
+ if (!isset($deps['optional'][$packagetype][0])) {
+ $deps['optional'][$packagetype] = array($deps['optional'][$packagetype]);
+ }
+
+ foreach ($deps['optional'][$packagetype] as $dep) {
+ $skip = false;
+ if (!isset($options['alldeps'])) {
+ $dep['package'] = $dep['name'];
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(3, 'Notice: package "' .
+ $this->_registry->parsedPackageNameToString($this->getParsedPackage(),
+ true) . '" optional dependency "' .
+ $this->_registry->parsedPackageNameToString(array('package' =>
+ $dep['name'], 'channel' => 'pear.php.net'), true) .
+ '" will not be automatically downloaded');
+ }
+ $skipnames[] = $this->_registry->parsedPackageNameToString($dep, true);
+ $skip = true;
+ unset($dep['package']);
+ }
+
+ $ret = $this->_detect2Dep($dep, $pname, 'optional', $params);
+ if (PEAR::isError($ret) && !isset($options['soft'])) {
+ $this->_downloader->log(0, $ret->getMessage());
+ }
+
+ if (!$ret) {
+ $dep['package'] = $dep['name'];
+ $skip = count($skipnames) ?
+ $skipnames[count($skipnames) - 1] : '';
+ if ($skip ==
+ $this->_registry->parsedPackageNameToString($dep, true)) {
+ array_pop($skipnames);
+ }
+ }
+
+ if (!$skip && is_array($ret)) {
+ $this->_downloadDeps[] = $ret;
+ }
+ }
+
+ if (count($skipnames)) {
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(1, 'Did not download optional dependencies: ' .
+ implode(', ', $skipnames) .
+ ', use --alldeps to download automatically');
+ }
+ }
+ }
+
+ // get requested dependency group, if any
+ $groupname = $this->getGroup();
+ $explicit = $this->_explicitGroup;
+ if (!$groupname) {
+ if (!$this->canDefault()) {
+ continue;
+ }
+
+ $groupname = 'default'; // try the default dependency group
+ }
+
+ if ($groupnotfound) {
+ continue;
+ }
+
+ if (isset($deps['group'])) {
+ if (isset($deps['group']['attribs'])) {
+ if (strtolower($deps['group']['attribs']['name']) == strtolower($groupname)) {
+ $group = $deps['group'];
+ } elseif ($explicit) {
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(0, 'Warning: package "' .
+ $this->_registry->parsedPackageNameToString($pname, true) .
+ '" has no dependency ' . 'group named "' . $groupname . '"');
+ }
+
+ $groupnotfound = true;
+ continue;
+ }
+ } else {
+ $found = false;
+ foreach ($deps['group'] as $group) {
+ if (strtolower($group['attribs']['name']) == strtolower($groupname)) {
+ $found = true;
+ break;
+ }
+ }
+
+ if (!$found) {
+ if ($explicit) {
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(0, 'Warning: package "' .
+ $this->_registry->parsedPackageNameToString($pname, true) .
+ '" has no dependency ' . 'group named "' . $groupname . '"');
+ }
+ }
+
+ $groupnotfound = true;
+ continue;
+ }
+ }
+ }
+
+ if (isset($group) && isset($group[$packagetype])) {
+ if (isset($group[$packagetype][0])) {
+ foreach ($group[$packagetype] as $dep) {
+ $ret = $this->_detect2Dep($dep, $pname, 'dependency group "' .
+ $group['attribs']['name'] . '"', $params);
+ if (is_array($ret)) {
+ $this->_downloadDeps[] = $ret;
+ } elseif (PEAR::isError($ret) && !isset($options['soft'])) {
+ $this->_downloader->log(0, $ret->getMessage());
+ }
+ }
+ } else {
+ $ret = $this->_detect2Dep($group[$packagetype], $pname,
+ 'dependency group "' .
+ $group['attribs']['name'] . '"', $params);
+ if (is_array($ret)) {
+ $this->_downloadDeps[] = $ret;
+ } elseif (PEAR::isError($ret) && !isset($options['soft'])) {
+ $this->_downloader->log(0, $ret->getMessage());
+ }
+ }
+ }
+ }
+ }
+
+ function _detect2Dep($dep, $pname, $group, $params)
+ {
+ if (isset($dep['conflicts'])) {
+ return true;
+ }
+
+ $options = $this->_downloader->getOptions();
+ if (isset($dep['uri'])) {
+ return array('uri' => $dep['uri'], 'dep' => $dep);;
+ }
+
+ $testdep = $dep;
+ $testdep['package'] = $dep['name'];
+ if (PEAR_Downloader_Package::willDownload($testdep, $params)) {
+ $dep['package'] = $dep['name'];
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group .
+ ' dependency "' .
+ $this->_registry->parsedPackageNameToString($dep, true) .
+ '", will be installed');
+ }
+ return false;
+ }
+
+ $options = $this->_downloader->getOptions();
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ if ($this->_explicitState) {
+ $pname['state'] = $this->_explicitState;
+ }
+
+ $url = $this->_downloader->_getDepPackageDownloadUrl($dep, $pname);
+ if (PEAR::isError($url)) {
+ PEAR::popErrorHandling();
+ return $url;
+ }
+
+ $dep['package'] = $dep['name'];
+ $ret = $this->_analyzeDownloadURL($url, 'dependency', $dep, $params, $group == 'optional' &&
+ !isset($options['alldeps']), true);
+ PEAR::popErrorHandling();
+ if (PEAR::isError($ret)) {
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(0, $ret->getMessage());
+ }
+
+ return false;
+ }
+
+ // check to see if a dep is already installed and is the same or newer
+ if (!isset($dep['min']) && !isset($dep['max']) && !isset($dep['recommended'])) {
+ $oper = 'has';
+ } else {
+ $oper = 'gt';
+ }
+
+ // do not try to move this before getDepPackageDownloadURL
+ // we can't determine whether upgrade is necessary until we know what
+ // version would be downloaded
+ if (!isset($options['force']) && $this->isInstalled($ret, $oper)) {
+ $version = $this->_installRegistry->packageInfo($dep['name'], 'version', $dep['channel']);
+ $dep['package'] = $dep['name'];
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(3, $this->getShortName() . ': Skipping ' . $group .
+ ' dependency "' .
+ $this->_registry->parsedPackageNameToString($dep, true) .
+ '" version ' . $url['version'] . ', already installed as version ' .
+ $version);
+ }
+
+ return false;
+ }
+
+ if (isset($dep['nodefault'])) {
+ $ret['nodefault'] = true;
+ }
+
+ return $ret;
+ }
+
+ function _detect1($deps, $pname, $options, $params)
+ {
+ $this->_downloadDeps = array();
+ $skipnames = array();
+ foreach ($deps as $dep) {
+ $nodownload = false;
+ if (isset ($dep['type']) && $dep['type'] === 'pkg') {
+ $dep['channel'] = 'pear.php.net';
+ $dep['package'] = $dep['name'];
+ switch ($dep['rel']) {
+ case 'not' :
+ continue 2;
+ case 'ge' :
+ case 'eq' :
+ case 'gt' :
+ case 'has' :
+ $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ?
+ 'required' :
+ 'optional';
+ if (PEAR_Downloader_Package::willDownload($dep, $params)) {
+ $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group
+ . ' dependency "' .
+ $this->_registry->parsedPackageNameToString($dep, true) .
+ '", will be installed');
+ continue 2;
+ }
+ $fakedp = new PEAR_PackageFile_v1;
+ $fakedp->setPackage($dep['name']);
+ // skip internet check if we are not upgrading (bug #5810)
+ if (!isset($options['upgrade']) && $this->isInstalled(
+ $fakedp, $dep['rel'])) {
+ $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group
+ . ' dependency "' .
+ $this->_registry->parsedPackageNameToString($dep, true) .
+ '", is already installed');
+ continue 2;
+ }
+ }
+
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ if ($this->_explicitState) {
+ $pname['state'] = $this->_explicitState;
+ }
+
+ $url = $this->_downloader->_getDepPackageDownloadUrl($dep, $pname);
+ $chan = 'pear.php.net';
+ if (PEAR::isError($url)) {
+ // check to see if this is a pecl package that has jumped
+ // from pear.php.net to pecl.php.net channel
+ if (!class_exists('PEAR_Dependency2')) {
+ require_once 'PEAR/Dependency2.php';
+ }
+
+ $newdep = PEAR_Dependency2::normalizeDep($dep);
+ $newdep = $newdep[0];
+ $newdep['channel'] = 'pecl.php.net';
+ $chan = 'pecl.php.net';
+ $url = $this->_downloader->_getDepPackageDownloadUrl($newdep, $pname);
+ $obj = &$this->_installRegistry->getPackage($dep['name']);
+ if (PEAR::isError($url)) {
+ PEAR::popErrorHandling();
+ if ($obj !== null && $this->isInstalled($obj, $dep['rel'])) {
+ $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ?
+ 'required' :
+ 'optional';
+ $dep['package'] = $dep['name'];
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(3, $this->getShortName() .
+ ': Skipping ' . $group . ' dependency "' .
+ $this->_registry->parsedPackageNameToString($dep, true) .
+ '", already installed as version ' . $obj->getVersion());
+ }
+ $skip = count($skipnames) ?
+ $skipnames[count($skipnames) - 1] : '';
+ if ($skip ==
+ $this->_registry->parsedPackageNameToString($dep, true)) {
+ array_pop($skipnames);
+ }
+ continue;
+ } else {
+ if (isset($dep['optional']) && $dep['optional'] == 'yes') {
+ $this->_downloader->log(2, $this->getShortName() .
+ ': Skipping optional dependency "' .
+ $this->_registry->parsedPackageNameToString($dep, true) .
+ '", no releases exist');
+ continue;
+ } else {
+ return $url;
+ }
+ }
+ }
+ }
+
+ PEAR::popErrorHandling();
+ if (!isset($options['alldeps'])) {
+ if (isset($dep['optional']) && $dep['optional'] == 'yes') {
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(3, 'Notice: package "' .
+ $this->getShortName() .
+ '" optional dependency "' .
+ $this->_registry->parsedPackageNameToString(
+ array('channel' => $chan, 'package' =>
+ $dep['name']), true) .
+ '" will not be automatically downloaded');
+ }
+ $skipnames[] = $this->_registry->parsedPackageNameToString(
+ array('channel' => $chan, 'package' =>
+ $dep['name']), true);
+ $nodownload = true;
+ }
+ }
+
+ if (!isset($options['alldeps']) && !isset($options['onlyreqdeps'])) {
+ if (!isset($dep['optional']) || $dep['optional'] == 'no') {
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(3, 'Notice: package "' .
+ $this->getShortName() .
+ '" required dependency "' .
+ $this->_registry->parsedPackageNameToString(
+ array('channel' => $chan, 'package' =>
+ $dep['name']), true) .
+ '" will not be automatically downloaded');
+ }
+ $skipnames[] = $this->_registry->parsedPackageNameToString(
+ array('channel' => $chan, 'package' =>
+ $dep['name']), true);
+ $nodownload = true;
+ }
+ }
+
+ // check to see if a dep is already installed
+ // do not try to move this before getDepPackageDownloadURL
+ // we can't determine whether upgrade is necessary until we know what
+ // version would be downloaded
+ if (!isset($options['force']) && $this->isInstalled(
+ $url, $dep['rel'])) {
+ $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ?
+ 'required' :
+ 'optional';
+ $dep['package'] = $dep['name'];
+ if (isset($newdep)) {
+ $version = $this->_installRegistry->packageInfo($newdep['name'], 'version', $newdep['channel']);
+ } else {
+ $version = $this->_installRegistry->packageInfo($dep['name'], 'version');
+ }
+
+ $dep['version'] = $url['version'];
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(3, $this->getShortName() . ': Skipping ' . $group .
+ ' dependency "' .
+ $this->_registry->parsedPackageNameToString($dep, true) .
+ '", already installed as version ' . $version);
+ }
+
+ $skip = count($skipnames) ?
+ $skipnames[count($skipnames) - 1] : '';
+ if ($skip ==
+ $this->_registry->parsedPackageNameToString($dep, true)) {
+ array_pop($skipnames);
+ }
+
+ continue;
+ }
+
+ if ($nodownload) {
+ continue;
+ }
+
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ if (isset($newdep)) {
+ $dep = $newdep;
+ }
+
+ $dep['package'] = $dep['name'];
+ $ret = $this->_analyzeDownloadURL($url, 'dependency', $dep, $params,
+ isset($dep['optional']) && $dep['optional'] == 'yes' &&
+ !isset($options['alldeps']), true);
+ PEAR::popErrorHandling();
+ if (PEAR::isError($ret)) {
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(0, $ret->getMessage());
+ }
+ continue;
+ }
+
+ $this->_downloadDeps[] = $ret;
+ }
+ }
+
+ if (count($skipnames)) {
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(1, 'Did not download dependencies: ' .
+ implode(', ', $skipnames) .
+ ', use --alldeps or --onlyreqdeps to download automatically');
+ }
+ }
+ }
+
+ function setDownloadURL($pkg)
+ {
+ $this->_downloadURL = $pkg;
+ }
+
+ /**
+ * Set the package.xml object for this downloaded package
+ *
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 $pkg
+ */
+ function setPackageFile(&$pkg)
+ {
+ $this->_packagefile = &$pkg;
+ }
+
+ function getShortName()
+ {
+ return $this->_registry->parsedPackageNameToString(array('channel' => $this->getChannel(),
+ 'package' => $this->getPackage()), true);
+ }
+
+ function getParsedPackage()
+ {
+ if (isset($this->_packagefile) || isset($this->_parsedname)) {
+ return array('channel' => $this->getChannel(),
+ 'package' => $this->getPackage(),
+ 'version' => $this->getVersion());
+ }
+
+ return false;
+ }
+
+ function getDownloadURL()
+ {
+ return $this->_downloadURL;
+ }
+
+ function canDefault()
+ {
+ if (isset($this->_downloadURL) && isset($this->_downloadURL['nodefault'])) {
+ return false;
+ }
+
+ return true;
+ }
+
+ function getPackage()
+ {
+ if (isset($this->_packagefile)) {
+ return $this->_packagefile->getPackage();
+ } elseif (isset($this->_downloadURL['info'])) {
+ return $this->_downloadURL['info']->getPackage();
+ }
+
+ return false;
+ }
+
+ /**
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ */
+ function isSubpackage(&$pf)
+ {
+ if (isset($this->_packagefile)) {
+ return $this->_packagefile->isSubpackage($pf);
+ } elseif (isset($this->_downloadURL['info'])) {
+ return $this->_downloadURL['info']->isSubpackage($pf);
+ }
+
+ return false;
+ }
+
+ function getPackageType()
+ {
+ if (isset($this->_packagefile)) {
+ return $this->_packagefile->getPackageType();
+ } elseif (isset($this->_downloadURL['info'])) {
+ return $this->_downloadURL['info']->getPackageType();
+ }
+
+ return false;
+ }
+
+ function isBundle()
+ {
+ if (isset($this->_packagefile)) {
+ return $this->_packagefile->getPackageType() == 'bundle';
+ }
+
+ return false;
+ }
+
+ function getPackageXmlVersion()
+ {
+ if (isset($this->_packagefile)) {
+ return $this->_packagefile->getPackagexmlVersion();
+ } elseif (isset($this->_downloadURL['info'])) {
+ return $this->_downloadURL['info']->getPackagexmlVersion();
+ }
+
+ return '1.0';
+ }
+
+ function getChannel()
+ {
+ if (isset($this->_packagefile)) {
+ return $this->_packagefile->getChannel();
+ } elseif (isset($this->_downloadURL['info'])) {
+ return $this->_downloadURL['info']->getChannel();
+ }
+
+ return false;
+ }
+
+ function getURI()
+ {
+ if (isset($this->_packagefile)) {
+ return $this->_packagefile->getURI();
+ } elseif (isset($this->_downloadURL['info'])) {
+ return $this->_downloadURL['info']->getURI();
+ }
+
+ return false;
+ }
+
+ function getVersion()
+ {
+ if (isset($this->_packagefile)) {
+ return $this->_packagefile->getVersion();
+ } elseif (isset($this->_downloadURL['version'])) {
+ return $this->_downloadURL['version'];
+ }
+
+ return false;
+ }
+
+ function isCompatible($pf)
+ {
+ if (isset($this->_packagefile)) {
+ return $this->_packagefile->isCompatible($pf);
+ } elseif (isset($this->_downloadURL['info'])) {
+ return $this->_downloadURL['info']->isCompatible($pf);
+ }
+
+ return true;
+ }
+
+ function setGroup($group)
+ {
+ $this->_parsedname['group'] = $group;
+ }
+
+ function getGroup()
+ {
+ if (isset($this->_parsedname['group'])) {
+ return $this->_parsedname['group'];
+ }
+
+ return '';
+ }
+
+ function isExtension($name)
+ {
+ if (isset($this->_packagefile)) {
+ return $this->_packagefile->isExtension($name);
+ } elseif (isset($this->_downloadURL['info'])) {
+ if ($this->_downloadURL['info']->getPackagexmlVersion() == '2.0') {
+ return $this->_downloadURL['info']->getProvidesExtension() == $name;
+ }
+
+ return false;
+ }
+
+ return false;
+ }
+
+ function getDeps()
+ {
+ if (isset($this->_packagefile)) {
+ $ver = $this->_packagefile->getPackagexmlVersion();
+ if (version_compare($ver, '2.0', '>=')) {
+ return $this->_packagefile->getDeps(true);
+ }
+
+ return $this->_packagefile->getDeps();
+ } elseif (isset($this->_downloadURL['info'])) {
+ $ver = $this->_downloadURL['info']->getPackagexmlVersion();
+ if (version_compare($ver, '2.0', '>=')) {
+ return $this->_downloadURL['info']->getDeps(true);
+ }
+
+ return $this->_downloadURL['info']->getDeps();
+ }
+
+ return array();
+ }
+
+ /**
+ * @param array Parsed array from {@link PEAR_Registry::parsePackageName()} or a dependency
+ * returned from getDepDownloadURL()
+ */
+ function isEqual($param)
+ {
+ if (is_object($param)) {
+ $channel = $param->getChannel();
+ $package = $param->getPackage();
+ if ($param->getURI()) {
+ $param = array(
+ 'channel' => $param->getChannel(),
+ 'package' => $param->getPackage(),
+ 'version' => $param->getVersion(),
+ 'uri' => $param->getURI(),
+ );
+ } else {
+ $param = array(
+ 'channel' => $param->getChannel(),
+ 'package' => $param->getPackage(),
+ 'version' => $param->getVersion(),
+ );
+ }
+ } else {
+ if (isset($param['uri'])) {
+ if ($this->getChannel() != '__uri') {
+ return false;
+ }
+ return $param['uri'] == $this->getURI();
+ }
+
+ $package = isset($param['package']) ? $param['package'] : $param['info']->getPackage();
+ $channel = isset($param['channel']) ? $param['channel'] : $param['info']->getChannel();
+ if (isset($param['rel'])) {
+ if (!class_exists('PEAR_Dependency2')) {
+ require_once 'PEAR/Dependency2.php';
+ }
+
+ $newdep = PEAR_Dependency2::normalizeDep($param);
+ $newdep = $newdep[0];
+ } elseif (isset($param['min'])) {
+ $newdep = $param;
+ }
+ }
+
+ if (isset($newdep)) {
+ if (!isset($newdep['min'])) {
+ $newdep['min'] = '0';
+ }
+
+ if (!isset($newdep['max'])) {
+ $newdep['max'] = '100000000000000000000';
+ }
+
+ // use magic to support pecl packages suddenly jumping to the pecl channel
+ // we need to support both dependency possibilities
+ if ($channel == 'pear.php.net' && $this->getChannel() == 'pecl.php.net') {
+ if ($package == $this->getPackage()) {
+ $channel = 'pecl.php.net';
+ }
+ }
+ if ($channel == 'pecl.php.net' && $this->getChannel() == 'pear.php.net') {
+ if ($package == $this->getPackage()) {
+ $channel = 'pear.php.net';
+ }
+ }
+
+ return (strtolower($package) == strtolower($this->getPackage()) &&
+ $channel == $this->getChannel() &&
+ version_compare($newdep['min'], $this->getVersion(), '<=') &&
+ version_compare($newdep['max'], $this->getVersion(), '>='));
+ }
+
+ // use magic to support pecl packages suddenly jumping to the pecl channel
+ if ($channel == 'pecl.php.net' && $this->getChannel() == 'pear.php.net') {
+ if (strtolower($package) == strtolower($this->getPackage())) {
+ $channel = 'pear.php.net';
+ }
+ }
+
+ if (isset($param['version'])) {
+ return (strtolower($package) == strtolower($this->getPackage()) &&
+ $channel == $this->getChannel() &&
+ $param['version'] == $this->getVersion());
+ }
+
+ return strtolower($package) == strtolower($this->getPackage()) &&
+ $channel == $this->getChannel();
+ }
+
+ function isInstalled($dep, $oper = '==')
+ {
+ if (!$dep) {
+ return false;
+ }
+
+ if ($oper != 'ge' && $oper != 'gt' && $oper != 'has' && $oper != '==') {
+ return false;
+ }
+
+ if (is_object($dep)) {
+ $package = $dep->getPackage();
+ $channel = $dep->getChannel();
+ if ($dep->getURI()) {
+ $dep = array(
+ 'uri' => $dep->getURI(),
+ 'version' => $dep->getVersion(),
+ );
+ } else {
+ $dep = array(
+ 'version' => $dep->getVersion(),
+ );
+ }
+ } else {
+ if (isset($dep['uri'])) {
+ $channel = '__uri';
+ $package = $dep['dep']['name'];
+ } else {
+ $channel = $dep['info']->getChannel();
+ $package = $dep['info']->getPackage();
+ }
+ }
+
+ $options = $this->_downloader->getOptions();
+ $test = $this->_installRegistry->packageExists($package, $channel);
+ if (!$test && $channel == 'pecl.php.net') {
+ // do magic to allow upgrading from old pecl packages to new ones
+ $test = $this->_installRegistry->packageExists($package, 'pear.php.net');
+ $channel = 'pear.php.net';
+ }
+
+ if ($test) {
+ if (isset($dep['uri'])) {
+ if ($this->_installRegistry->packageInfo($package, 'uri', '__uri') == $dep['uri']) {
+ return true;
+ }
+ }
+
+ if (isset($options['upgrade'])) {
+ $packageVersion = $this->_installRegistry->packageInfo($package, 'version', $channel);
+ if (version_compare($packageVersion, $dep['version'], '>=')) {
+ return true;
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Detect duplicate package names with differing versions
+ *
+ * If a user requests to install Date 1.4.6 and Date 1.4.7,
+ * for instance, this is a logic error. This method
+ * detects this situation.
+ *
+ * @param array $params array of PEAR_Downloader_Package objects
+ * @param array $errorparams empty array
+ * @return array array of stupid duplicated packages in PEAR_Downloader_Package obejcts
+ */
+ function detectStupidDuplicates($params, &$errorparams)
+ {
+ $existing = array();
+ foreach ($params as $i => $param) {
+ $package = $param->getPackage();
+ $channel = $param->getChannel();
+ $group = $param->getGroup();
+ if (!isset($existing[$channel . '/' . $package])) {
+ $existing[$channel . '/' . $package] = array();
+ }
+
+ if (!isset($existing[$channel . '/' . $package][$group])) {
+ $existing[$channel . '/' . $package][$group] = array();
+ }
+
+ $existing[$channel . '/' . $package][$group][] = $i;
+ }
+
+ $indices = array();
+ foreach ($existing as $package => $groups) {
+ foreach ($groups as $group => $dupes) {
+ if (count($dupes) > 1) {
+ $indices = $indices + $dupes;
+ }
+ }
+ }
+
+ $indices = array_unique($indices);
+ foreach ($indices as $index) {
+ $errorparams[] = $params[$index];
+ }
+
+ return count($errorparams);
+ }
+
+ /**
+ * @param array
+ * @param bool ignore install groups - for final removal of dupe packages
+ * @static
+ */
+ function removeDuplicates(&$params, $ignoreGroups = false)
+ {
+ $pnames = array();
+ foreach ($params as $i => $param) {
+ if (!$param) {
+ continue;
+ }
+
+ if ($param->getPackage()) {
+ $group = $ignoreGroups ? '' : $param->getGroup();
+ $pnames[$i] = $param->getChannel() . '/' .
+ $param->getPackage() . '-' . $param->getVersion() . '#' . $group;
+ }
+ }
+
+ $pnames = array_unique($pnames);
+ $unset = array_diff(array_keys($params), array_keys($pnames));
+ $testp = array_flip($pnames);
+ foreach ($params as $i => $param) {
+ if (!$param) {
+ $unset[] = $i;
+ continue;
+ }
+
+ if (!is_a($param, 'PEAR_Downloader_Package')) {
+ $unset[] = $i;
+ continue;
+ }
+
+ $group = $ignoreGroups ? '' : $param->getGroup();
+ if (!isset($testp[$param->getChannel() . '/' . $param->getPackage() . '-' .
+ $param->getVersion() . '#' . $group])) {
+ $unset[] = $i;
+ }
+ }
+
+ foreach ($unset as $i) {
+ unset($params[$i]);
+ }
+
+ $ret = array();
+ foreach ($params as $i => $param) {
+ $ret[] = &$params[$i];
+ }
+
+ $params = array();
+ foreach ($ret as $i => $param) {
+ $params[] = &$ret[$i];
+ }
+ }
+
+ function explicitState()
+ {
+ return $this->_explicitState;
+ }
+
+ function setExplicitState($s)
+ {
+ $this->_explicitState = $s;
+ }
+
+ /**
+ * @static
+ */
+ function mergeDependencies(&$params)
+ {
+ $bundles = $newparams = array();
+ foreach ($params as $i => $param) {
+ if (!$param->isBundle()) {
+ continue;
+ }
+
+ $bundles[] = $i;
+ $pf = &$param->getPackageFile();
+ $newdeps = array();
+ $contents = $pf->getBundledPackages();
+ if (!is_array($contents)) {
+ $contents = array($contents);
+ }
+
+ foreach ($contents as $file) {
+ $filecontents = $pf->getFileContents($file);
+ $dl = &$param->getDownloader();
+ $options = $dl->getOptions();
+ if (PEAR::isError($dir = $dl->getDownloadDir())) {
+ return $dir;
+ }
+
+ $fp = @fopen($dir . DIRECTORY_SEPARATOR . $file, 'wb');
+ if (!$fp) {
+ continue;
+ }
+
+ // FIXME do symlink check
+
+ fwrite($fp, $filecontents, strlen($filecontents));
+ fclose($fp);
+ if ($s = $params[$i]->explicitState()) {
+ $obj->setExplicitState($s);
+ }
+
+ $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader());
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ if (PEAR::isError($dir = $dl->getDownloadDir())) {
+ PEAR::popErrorHandling();
+ return $dir;
+ }
+
+ $e = $obj->_fromFile($a = $dir . DIRECTORY_SEPARATOR . $file);
+ PEAR::popErrorHandling();
+ if (PEAR::isError($e)) {
+ if (!isset($options['soft'])) {
+ $dl->log(0, $e->getMessage());
+ }
+ continue;
+ }
+
+ $j = &$obj;
+ if (!PEAR_Downloader_Package::willDownload($j,
+ array_merge($params, $newparams)) && !$param->isInstalled($j)) {
+ $newparams[] = &$j;
+ }
+ }
+ }
+
+ foreach ($bundles as $i) {
+ unset($params[$i]); // remove bundles - only their contents matter for installation
+ }
+
+ PEAR_Downloader_Package::removeDuplicates($params); // strip any unset indices
+ if (count($newparams)) { // add in bundled packages for install
+ foreach ($newparams as $i => $unused) {
+ $params[] = &$newparams[$i];
+ }
+ $newparams = array();
+ }
+
+ foreach ($params as $i => $param) {
+ $newdeps = array();
+ foreach ($param->_downloadDeps as $dep) {
+ $merge = array_merge($params, $newparams);
+ if (!PEAR_Downloader_Package::willDownload($dep, $merge)
+ && !$param->isInstalled($dep)
+ ) {
+ $newdeps[] = $dep;
+ } else {
+ //var_dump($dep);
+ // detect versioning conflicts here
+ }
+ }
+
+ // convert the dependencies into PEAR_Downloader_Package objects for the next time around
+ $params[$i]->_downloadDeps = array();
+ foreach ($newdeps as $dep) {
+ $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader());
+ if ($s = $params[$i]->explicitState()) {
+ $obj->setExplicitState($s);
+ }
+
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $e = $obj->fromDepURL($dep);
+ PEAR::popErrorHandling();
+ if (PEAR::isError($e)) {
+ if (!isset($options['soft'])) {
+ $obj->_downloader->log(0, $e->getMessage());
+ }
+ continue;
+ }
+
+ $e = $obj->detectDependencies($params);
+ if (PEAR::isError($e)) {
+ if (!isset($options['soft'])) {
+ $obj->_downloader->log(0, $e->getMessage());
+ }
+ }
+
+ $j = &$obj;
+ $newparams[] = &$j;
+ }
+ }
+
+ if (count($newparams)) {
+ foreach ($newparams as $i => $unused) {
+ $params[] = &$newparams[$i];
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+
+ /**
+ * @static
+ */
+ function willDownload($param, $params)
+ {
+ if (!is_array($params)) {
+ return false;
+ }
+
+ foreach ($params as $obj) {
+ if ($obj->isEqual($param)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * For simpler unit-testing
+ * @param PEAR_Config
+ * @param int
+ * @param string
+ */
+ function &getPackagefileObject(&$c, $d)
+ {
+ $a = &new PEAR_PackageFile($c, $d);
+ return $a;
+ }
+
+ /**
+ * This will retrieve from a local file if possible, and parse out
+ * a group name as well. The original parameter will be modified to reflect this.
+ * @param string|array can be a parsed package name as well
+ * @access private
+ */
+ function _fromFile(&$param)
+ {
+ $saveparam = $param;
+ if (is_string($param)) {
+ if (!@file_exists($param)) {
+ $test = explode('#', $param);
+ $group = array_pop($test);
+ if (@file_exists(implode('#', $test))) {
+ $this->setGroup($group);
+ $param = implode('#', $test);
+ $this->_explicitGroup = true;
+ }
+ }
+
+ if (@is_file($param)) {
+ $this->_type = 'local';
+ $options = $this->_downloader->getOptions();
+ $pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->_debug);
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $pf = &$pkg->fromAnyFile($param, PEAR_VALIDATE_INSTALLING);
+ PEAR::popErrorHandling();
+ if (PEAR::isError($pf)) {
+ $this->_valid = false;
+ $param = $saveparam;
+ return $pf;
+ }
+ $this->_packagefile = &$pf;
+ if (!$this->getGroup()) {
+ $this->setGroup('default'); // install the default dependency group
+ }
+ return $this->_valid = true;
+ }
+ }
+ $param = $saveparam;
+ return $this->_valid = false;
+ }
+
+ function _fromUrl($param, $saveparam = '')
+ {
+ if (!is_array($param) && (preg_match('#^(http|https|ftp)://#', $param))) {
+ $options = $this->_downloader->getOptions();
+ $this->_type = 'url';
+ $callback = $this->_downloader->ui ?
+ array(&$this->_downloader, '_downloadCallback') : null;
+ $this->_downloader->pushErrorHandling(PEAR_ERROR_RETURN);
+ if (PEAR::isError($dir = $this->_downloader->getDownloadDir())) {
+ $this->_downloader->popErrorHandling();
+ return $dir;
+ }
+
+ $this->_downloader->log(3, 'Downloading "' . $param . '"');
+ $file = $this->_downloader->downloadHttp($param, $this->_downloader->ui,
+ $dir, $callback, null, false, $this->getChannel());
+ $this->_downloader->popErrorHandling();
+ if (PEAR::isError($file)) {
+ if (!empty($saveparam)) {
+ $saveparam = ", cannot download \"$saveparam\"";
+ }
+ $err = PEAR::raiseError('Could not download from "' . $param .
+ '"' . $saveparam . ' (' . $file->getMessage() . ')');
+ return $err;
+ }
+
+ if ($this->_rawpackagefile) {
+ require_once 'Archive/Tar.php';
+ $tar = &new Archive_Tar($file);
+ $packagexml = $tar->extractInString('package2.xml');
+ if (!$packagexml) {
+ $packagexml = $tar->extractInString('package.xml');
+ }
+
+ if (str_replace(array("\n", "\r"), array('',''), $packagexml) !=
+ str_replace(array("\n", "\r"), array('',''), $this->_rawpackagefile)) {
+ if ($this->getChannel() != 'pear.php.net') {
+ return PEAR::raiseError('CRITICAL ERROR: package.xml downloaded does ' .
+ 'not match value returned from xml-rpc');
+ }
+
+ // be more lax for the existing PEAR packages that have not-ok
+ // characters in their package.xml
+ $this->_downloader->log(0, 'CRITICAL WARNING: The "' .
+ $this->getPackage() . '" package has invalid characters in its ' .
+ 'package.xml. The next version of PEAR may not be able to install ' .
+ 'this package for security reasons. Please open a bug report at ' .
+ 'http://pear.php.net/package/' . $this->getPackage() . '/bugs');
+ }
+ }
+
+ // whew, download worked!
+ $pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->debug);
+
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $pf = &$pkg->fromAnyFile($file, PEAR_VALIDATE_INSTALLING);
+ PEAR::popErrorHandling();
+ if (PEAR::isError($pf)) {
+ if (is_array($pf->getUserInfo())) {
+ foreach ($pf->getUserInfo() as $err) {
+ if (is_array($err)) {
+ $err = $err['message'];
+ }
+
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(0, "Validation Error: $err");
+ }
+ }
+ }
+
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(0, $pf->getMessage());
+ }
+
+ ///FIXME need to pass back some error code that we can use to match with to cancel all further operations
+ /// At least stop all deps of this package from being installed
+ $out = $saveparam ? $saveparam : $param;
+ $err = PEAR::raiseError('Download of "' . $out . '" succeeded, but it is not a valid package archive');
+ $this->_valid = false;
+ return $err;
+ }
+
+ $this->_packagefile = &$pf;
+ $this->setGroup('default'); // install the default dependency group
+ return $this->_valid = true;
+ }
+
+ return $this->_valid = false;
+ }
+
+ /**
+ *
+ * @param string|array pass in an array of format
+ * array(
+ * 'package' => 'pname',
+ * ['channel' => 'channame',]
+ * ['version' => 'version',]
+ * ['state' => 'state',])
+ * or a string of format [channame/]pname[-version|-state]
+ */
+ function _fromString($param)
+ {
+ $options = $this->_downloader->getOptions();
+ $channel = $this->_config->get('default_channel');
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $pname = $this->_registry->parsePackageName($param, $channel);
+ PEAR::popErrorHandling();
+ if (PEAR::isError($pname)) {
+ if ($pname->getCode() == 'invalid') {
+ $this->_valid = false;
+ return false;
+ }
+
+ if ($pname->getCode() == 'channel') {
+ $parsed = $pname->getUserInfo();
+ if ($this->_downloader->discover($parsed['channel'])) {
+ if ($this->_config->get('auto_discover')) {
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $pname = $this->_registry->parsePackageName($param, $channel);
+ PEAR::popErrorHandling();
+ } else {
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(0, 'Channel "' . $parsed['channel'] .
+ '" is not initialized, use ' .
+ '"pear channel-discover ' . $parsed['channel'] . '" to initialize ' .
+ 'or pear config-set auto_discover 1');
+ }
+ }
+ }
+
+ if (PEAR::isError($pname)) {
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(0, $pname->getMessage());
+ }
+
+ if (is_array($param)) {
+ $param = $this->_registry->parsedPackageNameToString($param);
+ }
+
+ $err = PEAR::raiseError('invalid package name/package file "' . $param . '"');
+ $this->_valid = false;
+ return $err;
+ }
+ } else {
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(0, $pname->getMessage());
+ }
+
+ $err = PEAR::raiseError('invalid package name/package file "' . $param . '"');
+ $this->_valid = false;
+ return $err;
+ }
+ }
+
+ if (!isset($this->_type)) {
+ $this->_type = 'rest';
+ }
+
+ $this->_parsedname = $pname;
+ $this->_explicitState = isset($pname['state']) ? $pname['state'] : false;
+ $this->_explicitGroup = isset($pname['group']) ? true : false;
+
+ $info = $this->_downloader->_getPackageDownloadUrl($pname);
+ if (PEAR::isError($info)) {
+ if ($info->getCode() != -976 && $pname['channel'] == 'pear.php.net') {
+ // try pecl
+ $pname['channel'] = 'pecl.php.net';
+ if ($test = $this->_downloader->_getPackageDownloadUrl($pname)) {
+ if (!PEAR::isError($test)) {
+ $info = PEAR::raiseError($info->getMessage() . ' - package ' .
+ $this->_registry->parsedPackageNameToString($pname, true) .
+ ' can be installed with "pecl install ' . $pname['package'] .
+ '"');
+ } else {
+ $pname['channel'] = 'pear.php.net';
+ }
+ } else {
+ $pname['channel'] = 'pear.php.net';
+ }
+ }
+
+ return $info;
+ }
+
+ $this->_rawpackagefile = $info['raw'];
+ $ret = $this->_analyzeDownloadURL($info, $param, $pname);
+ if (PEAR::isError($ret)) {
+ return $ret;
+ }
+
+ if ($ret) {
+ $this->_downloadURL = $ret;
+ return $this->_valid = (bool) $ret;
+ }
+ }
+
+ /**
+ * @param array output of package.getDownloadURL
+ * @param string|array|object information for detecting packages to be downloaded, and
+ * for errors
+ * @param array name information of the package
+ * @param array|null packages to be downloaded
+ * @param bool is this an optional dependency?
+ * @param bool is this any kind of dependency?
+ * @access private
+ */
+ function _analyzeDownloadURL($info, $param, $pname, $params = null, $optional = false,
+ $isdependency = false)
+ {
+ if (!is_string($param) && PEAR_Downloader_Package::willDownload($param, $params)) {
+ return false;
+ }
+
+ if ($info === false) {
+ $saveparam = !is_string($param) ? ", cannot download \"$param\"" : '';
+
+ // no releases exist
+ return PEAR::raiseError('No releases for package "' .
+ $this->_registry->parsedPackageNameToString($pname, true) . '" exist' . $saveparam);
+ }
+
+ if (strtolower($info['info']->getChannel()) != strtolower($pname['channel'])) {
+ $err = false;
+ if ($pname['channel'] == 'pecl.php.net') {
+ if ($info['info']->getChannel() != 'pear.php.net') {
+ $err = true;
+ }
+ } elseif ($info['info']->getChannel() == 'pecl.php.net') {
+ if ($pname['channel'] != 'pear.php.net') {
+ $err = true;
+ }
+ } else {
+ $err = true;
+ }
+
+ if ($err) {
+ return PEAR::raiseError('SECURITY ERROR: package in channel "' . $pname['channel'] .
+ '" retrieved another channel\'s name for download! ("' .
+ $info['info']->getChannel() . '")');
+ }
+ }
+
+ $preferred_state = $this->_config->get('preferred_state');
+ if (!isset($info['url'])) {
+ $package_version = $this->_registry->packageInfo($info['info']->getPackage(),
+ 'version', $info['info']->getChannel());
+ if ($this->isInstalled($info)) {
+ if ($isdependency && version_compare($info['version'], $package_version, '<=')) {
+ // ignore bogus errors of "failed to download dependency"
+ // if it is already installed and the one that would be
+ // downloaded is older or the same version (Bug #7219)
+ return false;
+ }
+ }
+
+ if ($info['version'] === $package_version) {
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] .
+ '/' . $pname['package'] . '-' . $package_version. ', additionally the suggested version' .
+ ' (' . $package_version . ') is the same as the locally installed one.');
+ }
+
+ return false;
+ }
+
+ if (version_compare($info['version'], $package_version, '<=')) {
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] .
+ '/' . $pname['package'] . '-' . $package_version . ', additionally the suggested version' .
+ ' (' . $info['version'] . ') is a lower version than the locally installed one (' . $package_version . ').');
+ }
+
+ return false;
+ }
+
+ $instead = ', will instead download version ' . $info['version'] .
+ ', stability "' . $info['info']->getState() . '"';
+ // releases exist, but we failed to get any
+ if (isset($this->_downloader->_options['force'])) {
+ if (isset($pname['version'])) {
+ $vs = ', version "' . $pname['version'] . '"';
+ } elseif (isset($pname['state'])) {
+ $vs = ', stability "' . $pname['state'] . '"';
+ } elseif ($param == 'dependency') {
+ if (!class_exists('PEAR_Common')) {
+ require_once 'PEAR/Common.php';
+ }
+
+ if (!in_array($info['info']->getState(),
+ PEAR_Common::betterStates($preferred_state, true))) {
+ if ($optional) {
+ // don't spit out confusing error message
+ return $this->_downloader->_getPackageDownloadUrl(
+ array('package' => $pname['package'],
+ 'channel' => $pname['channel'],
+ 'version' => $info['version']));
+ }
+ $vs = ' within preferred state "' . $preferred_state .
+ '"';
+ } else {
+ if (!class_exists('PEAR_Dependency2')) {
+ require_once 'PEAR/Dependency2.php';
+ }
+
+ if ($optional) {
+ // don't spit out confusing error message
+ return $this->_downloader->_getPackageDownloadUrl(
+ array('package' => $pname['package'],
+ 'channel' => $pname['channel'],
+ 'version' => $info['version']));
+ }
+ $vs = PEAR_Dependency2::_getExtraString($pname);
+ $instead = '';
+ }
+ } else {
+ $vs = ' within preferred state "' . $preferred_state . '"';
+ }
+
+ if (!isset($options['soft'])) {
+ $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] .
+ '/' . $pname['package'] . $vs . $instead);
+ }
+
+ // download the latest release
+ return $this->_downloader->_getPackageDownloadUrl(
+ array('package' => $pname['package'],
+ 'channel' => $pname['channel'],
+ 'version' => $info['version']));
+ } else {
+ if (isset($info['php']) && $info['php']) {
+ $err = PEAR::raiseError('Failed to download ' .
+ $this->_registry->parsedPackageNameToString(
+ array('channel' => $pname['channel'],
+ 'package' => $pname['package']),
+ true) .
+ ', latest release is version ' . $info['php']['v'] .
+ ', but it requires PHP version "' .
+ $info['php']['m'] . '", use "' .
+ $this->_registry->parsedPackageNameToString(
+ array('channel' => $pname['channel'], 'package' => $pname['package'],
+ 'version' => $info['php']['v'])) . '" to install',
+ PEAR_DOWNLOADER_PACKAGE_PHPVERSION);
+ return $err;
+ }
+
+ // construct helpful error message
+ if (isset($pname['version'])) {
+ $vs = ', version "' . $pname['version'] . '"';
+ } elseif (isset($pname['state'])) {
+ $vs = ', stability "' . $pname['state'] . '"';
+ } elseif ($param == 'dependency') {
+ if (!class_exists('PEAR_Common')) {
+ require_once 'PEAR/Common.php';
+ }
+
+ if (!in_array($info['info']->getState(),
+ PEAR_Common::betterStates($preferred_state, true))) {
+ if ($optional) {
+ // don't spit out confusing error message, and don't die on
+ // optional dep failure!
+ return $this->_downloader->_getPackageDownloadUrl(
+ array('package' => $pname['package'],
+ 'channel' => $pname['channel'],
+ 'version' => $info['version']));
+ }
+ $vs = ' within preferred state "' . $preferred_state . '"';
+ } else {
+ if (!class_exists('PEAR_Dependency2')) {
+ require_once 'PEAR/Dependency2.php';
+ }
+
+ if ($optional) {
+ // don't spit out confusing error message, and don't die on
+ // optional dep failure!
+ return $this->_downloader->_getPackageDownloadUrl(
+ array('package' => $pname['package'],
+ 'channel' => $pname['channel'],
+ 'version' => $info['version']));
+ }
+ $vs = PEAR_Dependency2::_getExtraString($pname);
+ }
+ } else {
+ $vs = ' within preferred state "' . $this->_downloader->config->get('preferred_state') . '"';
+ }
+
+ $options = $this->_downloader->getOptions();
+ // this is only set by the "download-all" command
+ if (isset($options['ignorepreferred_state'])) {
+ $err = PEAR::raiseError(
+ 'Failed to download ' . $this->_registry->parsedPackageNameToString(
+ array('channel' => $pname['channel'], 'package' => $pname['package']),
+ true)
+ . $vs .
+ ', latest release is version ' . $info['version'] .
+ ', stability "' . $info['info']->getState() . '", use "' .
+ $this->_registry->parsedPackageNameToString(
+ array('channel' => $pname['channel'], 'package' => $pname['package'],
+ 'version' => $info['version'])) . '" to install',
+ PEAR_DOWNLOADER_PACKAGE_STATE);
+ return $err;
+ }
+
+ // Checks if the user has a package installed already and checks the release against
+ // the state against the installed package, this allows upgrades for packages
+ // with lower stability than the preferred_state
+ $stability = $this->_registry->packageInfo($pname['package'], 'stability', $pname['channel']);
+ if (!$this->isInstalled($info)
+ || !in_array($info['info']->getState(), PEAR_Common::betterStates($stability['release'], true))
+ ) {
+ $err = PEAR::raiseError(
+ 'Failed to download ' . $this->_registry->parsedPackageNameToString(
+ array('channel' => $pname['channel'], 'package' => $pname['package']),
+ true)
+ . $vs .
+ ', latest release is version ' . $info['version'] .
+ ', stability "' . $info['info']->getState() . '", use "' .
+ $this->_registry->parsedPackageNameToString(
+ array('channel' => $pname['channel'], 'package' => $pname['package'],
+ 'version' => $info['version'])) . '" to install');
+ return $err;
+ }
+ }
+ }
+
+ if (isset($info['deprecated']) && $info['deprecated']) {
+ $this->_downloader->log(0,
+ 'WARNING: "' .
+ $this->_registry->parsedPackageNameToString(
+ array('channel' => $info['info']->getChannel(),
+ 'package' => $info['info']->getPackage()), true) .
+ '" is deprecated in favor of "' .
+ $this->_registry->parsedPackageNameToString($info['deprecated'], true) .
+ '"');
+ }
+
+ return $info;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/ErrorStack.php b/includes/pear/PEAR/ErrorStack.php
new file mode 100644
index 0000000..2cdddfc
--- /dev/null
+++ b/includes/pear/PEAR/ErrorStack.php
@@ -0,0 +1,985 @@
+<?php
+/**
+ * Error Stack Implementation
+ *
+ * This is an incredibly simple implementation of a very complex error handling
+ * facility. It contains the ability
+ * to track multiple errors from multiple packages simultaneously. In addition,
+ * it can track errors of many levels, save data along with the error, context
+ * information such as the exact file, line number, class and function that
+ * generated the error, and if necessary, it can raise a traditional PEAR_Error.
+ * It has built-in support for PEAR::Log, to log errors as they occur
+ *
+ * Since version 0.2alpha, it is also possible to selectively ignore errors,
+ * through the use of an error callback, see {@link pushCallback()}
+ *
+ * Since version 0.3alpha, it is possible to specify the exception class
+ * returned from {@link push()}
+ *
+ * Since version PEAR1.3.2, ErrorStack no longer instantiates an exception class. This can
+ * still be done quite handily in an error callback or by manipulating the returned array
+ * @category Debugging
+ * @package PEAR_ErrorStack
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 2004-2008 Greg Beaver
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR_ErrorStack
+ */
+
+/**
+ * Singleton storage
+ *
+ * Format:
+ * <pre>
+ * array(
+ * 'package1' => PEAR_ErrorStack object,
+ * 'package2' => PEAR_ErrorStack object,
+ * ...
+ * )
+ * </pre>
+ * @access private
+ * @global array $GLOBALS['_PEAR_ERRORSTACK_SINGLETON']
+ */
+$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] = array();
+
+/**
+ * Global error callback (default)
+ *
+ * This is only used if set to non-false. * is the default callback for
+ * all packages, whereas specific packages may set a default callback
+ * for all instances, regardless of whether they are a singleton or not.
+ *
+ * To exclude non-singletons, only set the local callback for the singleton
+ * @see PEAR_ErrorStack::setDefaultCallback()
+ * @access private
+ * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']
+ */
+$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] = array(
+ '*' => false,
+);
+
+/**
+ * Global Log object (default)
+ *
+ * This is only used if set to non-false. Use to set a default log object for
+ * all stacks, regardless of instantiation order or location
+ * @see PEAR_ErrorStack::setDefaultLogger()
+ * @access private
+ * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
+ */
+$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = false;
+
+/**
+ * Global Overriding Callback
+ *
+ * This callback will override any error callbacks that specific loggers have set.
+ * Use with EXTREME caution
+ * @see PEAR_ErrorStack::staticPushCallback()
+ * @access private
+ * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
+ */
+$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
+
+/**#@+
+ * One of four possible return values from the error Callback
+ * @see PEAR_ErrorStack::_errorCallback()
+ */
+/**
+ * If this is returned, then the error will be both pushed onto the stack
+ * and logged.
+ */
+define('PEAR_ERRORSTACK_PUSHANDLOG', 1);
+/**
+ * If this is returned, then the error will only be pushed onto the stack,
+ * and not logged.
+ */
+define('PEAR_ERRORSTACK_PUSH', 2);
+/**
+ * If this is returned, then the error will only be logged, but not pushed
+ * onto the error stack.
+ */
+define('PEAR_ERRORSTACK_LOG', 3);
+/**
+ * If this is returned, then the error is completely ignored.
+ */
+define('PEAR_ERRORSTACK_IGNORE', 4);
+/**
+ * If this is returned, then the error is logged and die() is called.
+ */
+define('PEAR_ERRORSTACK_DIE', 5);
+/**#@-*/
+
+/**
+ * Error code for an attempt to instantiate a non-class as a PEAR_ErrorStack in
+ * the singleton method.
+ */
+define('PEAR_ERRORSTACK_ERR_NONCLASS', 1);
+
+/**
+ * Error code for an attempt to pass an object into {@link PEAR_ErrorStack::getMessage()}
+ * that has no __toString() method
+ */
+define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2);
+/**
+ * Error Stack Implementation
+ *
+ * Usage:
+ * <code>
+ * // global error stack
+ * $global_stack = &PEAR_ErrorStack::singleton('MyPackage');
+ * // local error stack
+ * $local_stack = new PEAR_ErrorStack('MyPackage');
+ * </code>
+ * @author Greg Beaver <cellog@php.net>
+ * @version @package_version@
+ * @package PEAR_ErrorStack
+ * @category Debugging
+ * @copyright 2004-2008 Greg Beaver
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR_ErrorStack
+ */
+class PEAR_ErrorStack {
+ /**
+ * Errors are stored in the order that they are pushed on the stack.
+ * @since 0.4alpha Errors are no longer organized by error level.
+ * This renders pop() nearly unusable, and levels could be more easily
+ * handled in a callback anyway
+ * @var array
+ * @access private
+ */
+ var $_errors = array();
+
+ /**
+ * Storage of errors by level.
+ *
+ * Allows easy retrieval and deletion of only errors from a particular level
+ * @since PEAR 1.4.0dev
+ * @var array
+ * @access private
+ */
+ var $_errorsByLevel = array();
+
+ /**
+ * Package name this error stack represents
+ * @var string
+ * @access protected
+ */
+ var $_package;
+
+ /**
+ * Determines whether a PEAR_Error is thrown upon every error addition
+ * @var boolean
+ * @access private
+ */
+ var $_compat = false;
+
+ /**
+ * If set to a valid callback, this will be used to generate the error
+ * message from the error code, otherwise the message passed in will be
+ * used
+ * @var false|string|array
+ * @access private
+ */
+ var $_msgCallback = false;
+
+ /**
+ * If set to a valid callback, this will be used to generate the error
+ * context for an error. For PHP-related errors, this will be a file
+ * and line number as retrieved from debug_backtrace(), but can be
+ * customized for other purposes. The error might actually be in a separate
+ * configuration file, or in a database query.
+ * @var false|string|array
+ * @access protected
+ */
+ var $_contextCallback = false;
+
+ /**
+ * If set to a valid callback, this will be called every time an error
+ * is pushed onto the stack. The return value will be used to determine
+ * whether to allow an error to be pushed or logged.
+ *
+ * The return value must be one an PEAR_ERRORSTACK_* constant
+ * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
+ * @var false|string|array
+ * @access protected
+ */
+ var $_errorCallback = array();
+
+ /**
+ * PEAR::Log object for logging errors
+ * @var false|Log
+ * @access protected
+ */
+ var $_logger = false;
+
+ /**
+ * Error messages - designed to be overridden
+ * @var array
+ * @abstract
+ */
+ var $_errorMsgs = array();
+
+ /**
+ * Set up a new error stack
+ *
+ * @param string $package name of the package this error stack represents
+ * @param callback $msgCallback callback used for error message generation
+ * @param callback $contextCallback callback used for context generation,
+ * defaults to {@link getFileLine()}
+ * @param boolean $throwPEAR_Error
+ */
+ function PEAR_ErrorStack($package, $msgCallback = false, $contextCallback = false,
+ $throwPEAR_Error = false)
+ {
+ $this->_package = $package;
+ $this->setMessageCallback($msgCallback);
+ $this->setContextCallback($contextCallback);
+ $this->_compat = $throwPEAR_Error;
+ }
+
+ /**
+ * Return a single error stack for this package.
+ *
+ * Note that all parameters are ignored if the stack for package $package
+ * has already been instantiated
+ * @param string $package name of the package this error stack represents
+ * @param callback $msgCallback callback used for error message generation
+ * @param callback $contextCallback callback used for context generation,
+ * defaults to {@link getFileLine()}
+ * @param boolean $throwPEAR_Error
+ * @param string $stackClass class to instantiate
+ * @static
+ * @return PEAR_ErrorStack
+ */
+ function &singleton($package, $msgCallback = false, $contextCallback = false,
+ $throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack')
+ {
+ if (isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
+ return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
+ }
+ if (!class_exists($stackClass)) {
+ if (function_exists('debug_backtrace')) {
+ $trace = debug_backtrace();
+ }
+ PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_NONCLASS,
+ 'exception', array('stackclass' => $stackClass),
+ 'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)',
+ false, $trace);
+ }
+ $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package] =
+ new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error);
+
+ return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
+ }
+
+ /**
+ * Internal error handler for PEAR_ErrorStack class
+ *
+ * Dies if the error is an exception (and would have died anyway)
+ * @access private
+ */
+ function _handleError($err)
+ {
+ if ($err['level'] == 'exception') {
+ $message = $err['message'];
+ if (isset($_SERVER['REQUEST_URI'])) {
+ echo '<br />';
+ } else {
+ echo "\n";
+ }
+ var_dump($err['context']);
+ die($message);
+ }
+ }
+
+ /**
+ * Set up a PEAR::Log object for all error stacks that don't have one
+ * @param Log $log
+ * @static
+ */
+ function setDefaultLogger(&$log)
+ {
+ if (is_object($log) && method_exists($log, 'log') ) {
+ $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
+ } elseif (is_callable($log)) {
+ $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
+ }
+ }
+
+ /**
+ * Set up a PEAR::Log object for this error stack
+ * @param Log $log
+ */
+ function setLogger(&$log)
+ {
+ if (is_object($log) && method_exists($log, 'log') ) {
+ $this->_logger = &$log;
+ } elseif (is_callable($log)) {
+ $this->_logger = &$log;
+ }
+ }
+
+ /**
+ * Set an error code => error message mapping callback
+ *
+ * This method sets the callback that can be used to generate error
+ * messages for any instance
+ * @param array|string Callback function/method
+ */
+ function setMessageCallback($msgCallback)
+ {
+ if (!$msgCallback) {
+ $this->_msgCallback = array(&$this, 'getErrorMessage');
+ } else {
+ if (is_callable($msgCallback)) {
+ $this->_msgCallback = $msgCallback;
+ }
+ }
+ }
+
+ /**
+ * Get an error code => error message mapping callback
+ *
+ * This method returns the current callback that can be used to generate error
+ * messages
+ * @return array|string|false Callback function/method or false if none
+ */
+ function getMessageCallback()
+ {
+ return $this->_msgCallback;
+ }
+
+ /**
+ * Sets a default callback to be used by all error stacks
+ *
+ * This method sets the callback that can be used to generate error
+ * messages for a singleton
+ * @param array|string Callback function/method
+ * @param string Package name, or false for all packages
+ * @static
+ */
+ function setDefaultCallback($callback = false, $package = false)
+ {
+ if (!is_callable($callback)) {
+ $callback = false;
+ }
+ $package = $package ? $package : '*';
+ $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$package] = $callback;
+ }
+
+ /**
+ * Set a callback that generates context information (location of error) for an error stack
+ *
+ * This method sets the callback that can be used to generate context
+ * information for an error. Passing in NULL will disable context generation
+ * and remove the expensive call to debug_backtrace()
+ * @param array|string|null Callback function/method
+ */
+ function setContextCallback($contextCallback)
+ {
+ if ($contextCallback === null) {
+ return $this->_contextCallback = false;
+ }
+ if (!$contextCallback) {
+ $this->_contextCallback = array(&$this, 'getFileLine');
+ } else {
+ if (is_callable($contextCallback)) {
+ $this->_contextCallback = $contextCallback;
+ }
+ }
+ }
+
+ /**
+ * Set an error Callback
+ * If set to a valid callback, this will be called every time an error
+ * is pushed onto the stack. The return value will be used to determine
+ * whether to allow an error to be pushed or logged.
+ *
+ * The return value must be one of the ERRORSTACK_* constants.
+ *
+ * This functionality can be used to emulate PEAR's pushErrorHandling, and
+ * the PEAR_ERROR_CALLBACK mode, without affecting the integrity of
+ * the error stack or logging
+ * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
+ * @see popCallback()
+ * @param string|array $cb
+ */
+ function pushCallback($cb)
+ {
+ array_push($this->_errorCallback, $cb);
+ }
+
+ /**
+ * Remove a callback from the error callback stack
+ * @see pushCallback()
+ * @return array|string|false
+ */
+ function popCallback()
+ {
+ if (!count($this->_errorCallback)) {
+ return false;
+ }
+ return array_pop($this->_errorCallback);
+ }
+
+ /**
+ * Set a temporary overriding error callback for every package error stack
+ *
+ * Use this to temporarily disable all existing callbacks (can be used
+ * to emulate the @ operator, for instance)
+ * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
+ * @see staticPopCallback(), pushCallback()
+ * @param string|array $cb
+ * @static
+ */
+ function staticPushCallback($cb)
+ {
+ array_push($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'], $cb);
+ }
+
+ /**
+ * Remove a temporary overriding error callback
+ * @see staticPushCallback()
+ * @return array|string|false
+ * @static
+ */
+ function staticPopCallback()
+ {
+ $ret = array_pop($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK']);
+ if (!is_array($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'])) {
+ $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
+ }
+ return $ret;
+ }
+
+ /**
+ * Add an error to the stack
+ *
+ * If the message generator exists, it is called with 2 parameters.
+ * - the current Error Stack object
+ * - an array that is in the same format as an error. Available indices
+ * are 'code', 'package', 'time', 'params', 'level', and 'context'
+ *
+ * Next, if the error should contain context information, this is
+ * handled by the context grabbing method.
+ * Finally, the error is pushed onto the proper error stack
+ * @param int $code Package-specific error code
+ * @param string $level Error level. This is NOT spell-checked
+ * @param array $params associative array of error parameters
+ * @param string $msg Error message, or a portion of it if the message
+ * is to be generated
+ * @param array $repackage If this error re-packages an error pushed by
+ * another package, place the array returned from
+ * {@link pop()} in this parameter
+ * @param array $backtrace Protected parameter: use this to pass in the
+ * {@link debug_backtrace()} that should be used
+ * to find error context
+ * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
+ * thrown. If a PEAR_Error is returned, the userinfo
+ * property is set to the following array:
+ *
+ * <code>
+ * array(
+ * 'code' => $code,
+ * 'params' => $params,
+ * 'package' => $this->_package,
+ * 'level' => $level,
+ * 'time' => time(),
+ * 'context' => $context,
+ * 'message' => $msg,
+ * //['repackage' => $err] repackaged error array/Exception class
+ * );
+ * </code>
+ *
+ * Normally, the previous array is returned.
+ */
+ function push($code, $level = 'error', $params = array(), $msg = false,
+ $repackage = false, $backtrace = false)
+ {
+ $context = false;
+ // grab error context
+ if ($this->_contextCallback) {
+ if (!$backtrace) {
+ $backtrace = debug_backtrace();
+ }
+ $context = call_user_func($this->_contextCallback, $code, $params, $backtrace);
+ }
+
+ // save error
+ $time = explode(' ', microtime());
+ $time = $time[1] + $time[0];
+ $err = array(
+ 'code' => $code,
+ 'params' => $params,
+ 'package' => $this->_package,
+ 'level' => $level,
+ 'time' => $time,
+ 'context' => $context,
+ 'message' => $msg,
+ );
+
+ if ($repackage) {
+ $err['repackage'] = $repackage;
+ }
+
+ // set up the error message, if necessary
+ if ($this->_msgCallback) {
+ $msg = call_user_func_array($this->_msgCallback,
+ array(&$this, $err));
+ $err['message'] = $msg;
+ }
+ $push = $log = true;
+ $die = false;
+ // try the overriding callback first
+ $callback = $this->staticPopCallback();
+ if ($callback) {
+ $this->staticPushCallback($callback);
+ }
+ if (!is_callable($callback)) {
+ // try the local callback next
+ $callback = $this->popCallback();
+ if (is_callable($callback)) {
+ $this->pushCallback($callback);
+ } else {
+ // try the default callback
+ $callback = isset($GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package]) ?
+ $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package] :
+ $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']['*'];
+ }
+ }
+ if (is_callable($callback)) {
+ switch(call_user_func($callback, $err)){
+ case PEAR_ERRORSTACK_IGNORE:
+ return $err;
+ break;
+ case PEAR_ERRORSTACK_PUSH:
+ $log = false;
+ break;
+ case PEAR_ERRORSTACK_LOG:
+ $push = false;
+ break;
+ case PEAR_ERRORSTACK_DIE:
+ $die = true;
+ break;
+ // anything else returned has the same effect as pushandlog
+ }
+ }
+ if ($push) {
+ array_unshift($this->_errors, $err);
+ if (!isset($this->_errorsByLevel[$err['level']])) {
+ $this->_errorsByLevel[$err['level']] = array();
+ }
+ $this->_errorsByLevel[$err['level']][] = &$this->_errors[0];
+ }
+ if ($log) {
+ if ($this->_logger || $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']) {
+ $this->_log($err);
+ }
+ }
+ if ($die) {
+ die();
+ }
+ if ($this->_compat && $push) {
+ return $this->raiseError($msg, $code, null, null, $err);
+ }
+ return $err;
+ }
+
+ /**
+ * Static version of {@link push()}
+ *
+ * @param string $package Package name this error belongs to
+ * @param int $code Package-specific error code
+ * @param string $level Error level. This is NOT spell-checked
+ * @param array $params associative array of error parameters
+ * @param string $msg Error message, or a portion of it if the message
+ * is to be generated
+ * @param array $repackage If this error re-packages an error pushed by
+ * another package, place the array returned from
+ * {@link pop()} in this parameter
+ * @param array $backtrace Protected parameter: use this to pass in the
+ * {@link debug_backtrace()} that should be used
+ * to find error context
+ * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
+ * thrown. see docs for {@link push()}
+ * @static
+ */
+ function staticPush($package, $code, $level = 'error', $params = array(),
+ $msg = false, $repackage = false, $backtrace = false)
+ {
+ $s = &PEAR_ErrorStack::singleton($package);
+ if ($s->_contextCallback) {
+ if (!$backtrace) {
+ if (function_exists('debug_backtrace')) {
+ $backtrace = debug_backtrace();
+ }
+ }
+ }
+ return $s->push($code, $level, $params, $msg, $repackage, $backtrace);
+ }
+
+ /**
+ * Log an error using PEAR::Log
+ * @param array $err Error array
+ * @param array $levels Error level => Log constant map
+ * @access protected
+ */
+ function _log($err)
+ {
+ if ($this->_logger) {
+ $logger = &$this->_logger;
+ } else {
+ $logger = &$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'];
+ }
+ if (is_a($logger, 'Log')) {
+ $levels = array(
+ 'exception' => PEAR_LOG_CRIT,
+ 'alert' => PEAR_LOG_ALERT,
+ 'critical' => PEAR_LOG_CRIT,
+ 'error' => PEAR_LOG_ERR,
+ 'warning' => PEAR_LOG_WARNING,
+ 'notice' => PEAR_LOG_NOTICE,
+ 'info' => PEAR_LOG_INFO,
+ 'debug' => PEAR_LOG_DEBUG);
+ if (isset($levels[$err['level']])) {
+ $level = $levels[$err['level']];
+ } else {
+ $level = PEAR_LOG_INFO;
+ }
+ $logger->log($err['message'], $level, $err);
+ } else { // support non-standard logs
+ call_user_func($logger, $err);
+ }
+ }
+
+
+ /**
+ * Pop an error off of the error stack
+ *
+ * @return false|array
+ * @since 0.4alpha it is no longer possible to specify a specific error
+ * level to return - the last error pushed will be returned, instead
+ */
+ function pop()
+ {
+ $err = @array_shift($this->_errors);
+ if (!is_null($err)) {
+ @array_pop($this->_errorsByLevel[$err['level']]);
+ if (!count($this->_errorsByLevel[$err['level']])) {
+ unset($this->_errorsByLevel[$err['level']]);
+ }
+ }
+ return $err;
+ }
+
+ /**
+ * Pop an error off of the error stack, static method
+ *
+ * @param string package name
+ * @return boolean
+ * @since PEAR1.5.0a1
+ */
+ function staticPop($package)
+ {
+ if ($package) {
+ if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
+ return false;
+ }
+ return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->pop();
+ }
+ }
+
+ /**
+ * Determine whether there are any errors on the stack
+ * @param string|array Level name. Use to determine if any errors
+ * of level (string), or levels (array) have been pushed
+ * @return boolean
+ */
+ function hasErrors($level = false)
+ {
+ if ($level) {
+ return isset($this->_errorsByLevel[$level]);
+ }
+ return count($this->_errors);
+ }
+
+ /**
+ * Retrieve all errors since last purge
+ *
+ * @param boolean set in order to empty the error stack
+ * @param string level name, to return only errors of a particular severity
+ * @return array
+ */
+ function getErrors($purge = false, $level = false)
+ {
+ if (!$purge) {
+ if ($level) {
+ if (!isset($this->_errorsByLevel[$level])) {
+ return array();
+ } else {
+ return $this->_errorsByLevel[$level];
+ }
+ } else {
+ return $this->_errors;
+ }
+ }
+ if ($level) {
+ $ret = $this->_errorsByLevel[$level];
+ foreach ($this->_errorsByLevel[$level] as $i => $unused) {
+ // entries are references to the $_errors array
+ $this->_errorsByLevel[$level][$i] = false;
+ }
+ // array_filter removes all entries === false
+ $this->_errors = array_filter($this->_errors);
+ unset($this->_errorsByLevel[$level]);
+ return $ret;
+ }
+ $ret = $this->_errors;
+ $this->_errors = array();
+ $this->_errorsByLevel = array();
+ return $ret;
+ }
+
+ /**
+ * Determine whether there are any errors on a single error stack, or on any error stack
+ *
+ * The optional parameter can be used to test the existence of any errors without the need of
+ * singleton instantiation
+ * @param string|false Package name to check for errors
+ * @param string Level name to check for a particular severity
+ * @return boolean
+ * @static
+ */
+ function staticHasErrors($package = false, $level = false)
+ {
+ if ($package) {
+ if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
+ return false;
+ }
+ return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->hasErrors($level);
+ }
+ foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
+ if ($obj->hasErrors($level)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get a list of all errors since last purge, organized by package
+ * @since PEAR 1.4.0dev BC break! $level is now in the place $merge used to be
+ * @param boolean $purge Set to purge the error stack of existing errors
+ * @param string $level Set to a level name in order to retrieve only errors of a particular level
+ * @param boolean $merge Set to return a flat array, not organized by package
+ * @param array $sortfunc Function used to sort a merged array - default
+ * sorts by time, and should be good for most cases
+ * @static
+ * @return array
+ */
+ function staticGetErrors($purge = false, $level = false, $merge = false,
+ $sortfunc = array('PEAR_ErrorStack', '_sortErrors'))
+ {
+ $ret = array();
+ if (!is_callable($sortfunc)) {
+ $sortfunc = array('PEAR_ErrorStack', '_sortErrors');
+ }
+ foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
+ $test = $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->getErrors($purge, $level);
+ if ($test) {
+ if ($merge) {
+ $ret = array_merge($ret, $test);
+ } else {
+ $ret[$package] = $test;
+ }
+ }
+ }
+ if ($merge) {
+ usort($ret, $sortfunc);
+ }
+ return $ret;
+ }
+
+ /**
+ * Error sorting function, sorts by time
+ * @access private
+ */
+ function _sortErrors($a, $b)
+ {
+ if ($a['time'] == $b['time']) {
+ return 0;
+ }
+ if ($a['time'] < $b['time']) {
+ return 1;
+ }
+ return -1;
+ }
+
+ /**
+ * Standard file/line number/function/class context callback
+ *
+ * This function uses a backtrace generated from {@link debug_backtrace()}
+ * and so will not work at all in PHP < 4.3.0. The frame should
+ * reference the frame that contains the source of the error.
+ * @return array|false either array('file' => file, 'line' => line,
+ * 'function' => function name, 'class' => class name) or
+ * if this doesn't work, then false
+ * @param unused
+ * @param integer backtrace frame.
+ * @param array Results of debug_backtrace()
+ * @static
+ */
+ function getFileLine($code, $params, $backtrace = null)
+ {
+ if ($backtrace === null) {
+ return false;
+ }
+ $frame = 0;
+ $functionframe = 1;
+ if (!isset($backtrace[1])) {
+ $functionframe = 0;
+ } else {
+ while (isset($backtrace[$functionframe]['function']) &&
+ $backtrace[$functionframe]['function'] == 'eval' &&
+ isset($backtrace[$functionframe + 1])) {
+ $functionframe++;
+ }
+ }
+ if (isset($backtrace[$frame])) {
+ if (!isset($backtrace[$frame]['file'])) {
+ $frame++;
+ }
+ $funcbacktrace = $backtrace[$functionframe];
+ $filebacktrace = $backtrace[$frame];
+ $ret = array('file' => $filebacktrace['file'],
+ 'line' => $filebacktrace['line']);
+ // rearrange for eval'd code or create function errors
+ if (strpos($filebacktrace['file'], '(') &&
+ preg_match(';^(.*?)\((\d+)\) : (.*?)\\z;', $filebacktrace['file'],
+ $matches)) {
+ $ret['file'] = $matches[1];
+ $ret['line'] = $matches[2] + 0;
+ }
+ if (isset($funcbacktrace['function']) && isset($backtrace[1])) {
+ if ($funcbacktrace['function'] != 'eval') {
+ if ($funcbacktrace['function'] == '__lambda_func') {
+ $ret['function'] = 'create_function() code';
+ } else {
+ $ret['function'] = $funcbacktrace['function'];
+ }
+ }
+ }
+ if (isset($funcbacktrace['class']) && isset($backtrace[1])) {
+ $ret['class'] = $funcbacktrace['class'];
+ }
+ return $ret;
+ }
+ return false;
+ }
+
+ /**
+ * Standard error message generation callback
+ *
+ * This method may also be called by a custom error message generator
+ * to fill in template values from the params array, simply
+ * set the third parameter to the error message template string to use
+ *
+ * The special variable %__msg% is reserved: use it only to specify
+ * where a message passed in by the user should be placed in the template,
+ * like so:
+ *
+ * Error message: %msg% - internal error
+ *
+ * If the message passed like so:
+ *
+ * <code>
+ * $stack->push(ERROR_CODE, 'error', array(), 'server error 500');
+ * </code>
+ *
+ * The returned error message will be "Error message: server error 500 -
+ * internal error"
+ * @param PEAR_ErrorStack
+ * @param array
+ * @param string|false Pre-generated error message template
+ * @static
+ * @return string
+ */
+ function getErrorMessage(&$stack, $err, $template = false)
+ {
+ if ($template) {
+ $mainmsg = $template;
+ } else {
+ $mainmsg = $stack->getErrorMessageTemplate($err['code']);
+ }
+ $mainmsg = str_replace('%__msg%', $err['message'], $mainmsg);
+ if (is_array($err['params']) && count($err['params'])) {
+ foreach ($err['params'] as $name => $val) {
+ if (is_array($val)) {
+ // @ is needed in case $val is a multi-dimensional array
+ $val = @implode(', ', $val);
+ }
+ if (is_object($val)) {
+ if (method_exists($val, '__toString')) {
+ $val = $val->__toString();
+ } else {
+ PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_OBJTOSTRING,
+ 'warning', array('obj' => get_class($val)),
+ 'object %obj% passed into getErrorMessage, but has no __toString() method');
+ $val = 'Object';
+ }
+ }
+ $mainmsg = str_replace('%' . $name . '%', $val, $mainmsg);
+ }
+ }
+ return $mainmsg;
+ }
+
+ /**
+ * Standard Error Message Template generator from code
+ * @return string
+ */
+ function getErrorMessageTemplate($code)
+ {
+ if (!isset($this->_errorMsgs[$code])) {
+ return '%__msg%';
+ }
+ return $this->_errorMsgs[$code];
+ }
+
+ /**
+ * Set the Error Message Template array
+ *
+ * The array format must be:
+ * <pre>
+ * array(error code => 'message template',...)
+ * </pre>
+ *
+ * Error message parameters passed into {@link push()} will be used as input
+ * for the error message. If the template is 'message %foo% was %bar%', and the
+ * parameters are array('foo' => 'one', 'bar' => 'six'), the error message returned will
+ * be 'message one was six'
+ * @return string
+ */
+ function setErrorMessageTemplate($template)
+ {
+ $this->_errorMsgs = $template;
+ }
+
+
+ /**
+ * emulate PEAR::raiseError()
+ *
+ * @return PEAR_Error
+ */
+ function raiseError()
+ {
+ require_once 'PEAR.php';
+ $args = func_get_args();
+ return call_user_func_array(array('PEAR', 'raiseError'), $args);
+ }
+}
+$stack = &PEAR_ErrorStack::singleton('PEAR_ErrorStack');
+$stack->pushCallback(array('PEAR_ErrorStack', '_handleError'));
+?>
diff --git a/includes/pear/PEAR/ErrorStack5.php b/includes/pear/PEAR/ErrorStack5.php
new file mode 100644
index 0000000..52714d5
--- /dev/null
+++ b/includes/pear/PEAR/ErrorStack5.php
@@ -0,0 +1,921 @@
+<?php
+/**
+ * Error Stack Implementation - E_STRICT compliant
+ *
+ * This is an incredibly simple implementation of a very complex error handling
+ * facility. It contains the ability
+ * to track multiple errors from multiple packages simultaneously. In addition,
+ * it can track errors of many levels, save data along with the error, context
+ * information such as the exact file, line number, class and function that
+ * generated the error, and if necessary, it can raise a traditional PEAR_Error.
+ * It has built-in support for PEAR::Log, to log errors as they occur
+ * @author Greg Beaver <cellog@php.net>
+ * @version 0.1alpha (E_STRICT)
+ * @package PEAR_ErrorStack
+ * @category Debugging
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ */
+
+/**
+ * PEAR_Exception is used by default
+ */
+require_once 'PEAR/Exception.php';
+
+class PEAR_ErrorStack_Exception extends PEAR_Exception{}
+
+/**#@+
+ * One of four possible return values from the error Callback
+ * @see PEAR_ErrorStack::_errorCallback()
+ */
+/**
+ * If this is returned, then the error will be both pushed onto the stack
+ * and logged.
+ */
+define('PEAR_ERRORSTACK_PUSHANDLOG', 1);
+/**
+ * If this is returned, then the error will only be pushed onto the stack,
+ * and not logged.
+ */
+define('PEAR_ERRORSTACK_PUSH', 2);
+/**
+ * If this is returned, then the error will only be logged, but not pushed
+ * onto the error stack.
+ */
+define('PEAR_ERRORSTACK_LOG', 3);
+/**
+ * If this is returned, then the error is completely ignored.
+ */
+define('PEAR_ERRORSTACK_IGNORE', 4);
+/**#@-*/
+
+/**
+ * Error code for an attempt to instantiate a non-class as a PEAR_ErrorStack in
+ * the singleton method.
+ */
+define('PEAR_ERRORSTACK_ERR_NONCLASS', 1);
+
+/**
+ * Error code for an attempt to pass an object into {@link PEAR_ErrorStack::getMessage()}
+ * that has no __toString() method
+ */
+define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2);
+/**
+ * Error Stack Implementation
+ *
+ * Usage:
+ * <code>
+ * // global error stack
+ * $global_stack = &PEAR_ErrorStack::singleton('MyPackage');
+ * // local error stack
+ * $local_stack = new PEAR_ErrorStack('MyPackage');
+ * </code>
+ * @copyright 2004 Gregory Beaver <cellog@php.net>
+ * @package PEAR_ErrorStack
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ */
+class PEAR_ErrorStack {
+ /**
+ * Singleton storage
+ *
+ * Format:
+ * <pre>
+ * array(
+ * 'package1' => PEAR_ErrorStack object,
+ * 'package2' => PEAR_ErrorStack object,
+ * ...
+ * )
+ * </pre>
+ */
+ static protected $singleton;
+
+ /**
+ * Global error callback (default)
+ *
+ * This is only used if set to non-false. * is the default callback for
+ * all packages, whereas specific packages may set a default callback
+ * for all instances, regardless of whether they are a singleton or not.
+ *
+ * To exclude non-singletons, only set the local callback for the singleton
+ * @see PEAR_ErrorStack::setDefaultCallback()
+ */
+ static protected $globalcallback =
+ array(
+ '*' => false,
+ );
+
+ /**
+ * Global Log object (default)
+ *
+ * This is only used if set to non-false. Use to set a default log object for
+ * all stacks, regardless of instantiation order or location
+ * @see PEAR_ErrorStack::setDefaultLogger()
+ */
+ static protected $globallogger = false;
+
+ /**
+ * PEAR_Warning callback
+ *
+ * This is only used if set to non-false. Use to set a default log object for
+ * all stacks, regardless of instantiation order or location
+ * @see PEAR_ErrorStack::setDefaultLogger()
+ */
+ static protected $pearwarningcallback = false;
+
+ /**
+ * Global Overriding Callback
+ *
+ * This callback will override any error callbacks that specific loggers have set.
+ * Use with EXTREME caution
+ * @see PEAR_ErrorStack::staticPushCallback()
+ * @access private
+ * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
+ */
+ static protected $overridecallback = array();
+
+ /**
+ * Errors are stored in the order that they are pushed on the stack.
+ * @since 0.4alpha Errors are no longer organized by error level.
+ * This renders pop() nearly unusable, and levels could be more easily
+ * handled in a callback anyway
+ * @var array
+ */
+ private $_errors = array();
+
+ /**
+ * Storage of errors by level.
+ *
+ * Allows easy retrieval and deletion of only errors from a particular level
+ * @since PEAR 1.4.0dev
+ * @var array
+ */
+ private $_errorsByLevel = array();
+
+ /**
+ * Package name this error stack represents
+ * @var string
+ */
+ protected $_package;
+
+ /**
+ * Determines whether a PEAR_Error is thrown upon every error addition
+ * @var boolean
+ */
+ private $_compat = false;
+
+ /**
+ * If set to a valid callback, this will be used to generate the error
+ * message from the error code, otherwise the message passed in will be
+ * used
+ * @var false|string|array
+ */
+ private $_msgCallback = false;
+
+ /**
+ * If set to a valid callback, this will be used to generate the error
+ * context for an error. For PHP-related errors, this will be a file
+ * and line number as retrieved from debug_backtrace(), but can be
+ * customized for other purposes. The error might actually be in a separate
+ * configuration file, or in a database query.
+ * @var false|string|array
+ */
+ public $contextCallback = false;
+
+ /**
+ * If set to a valid callback, this will be called every time an error
+ * is pushed onto the stack. The return value will be used to determine
+ * whether to allow an error to be pushed or logged.
+ *
+ * The return value must be one an PEAR_ERRORSTACK_* constant
+ * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
+ * @var false|string|array
+ */
+ protected $_errorCallback = array();
+
+ /**
+ * PEAR::Log object for logging errors
+ * @var false|Log
+ */
+ protected $_logger = false;
+
+ /**
+ * Error messages - designed to be overridden
+ * @var array
+ * @abstract
+ */
+ protected $_errorMsgs = array();
+
+ /**
+ * Set up a new error stack
+ *
+ * @param string $package name of the package this error stack represents
+ * @param callback $msgCallback callback used for error message generation
+ * @param callback $contextCallback callback used for context generation,
+ * defaults to {@link getFileLine()}
+ * @param boolean $throwPEAR_Error (ignored)
+ */
+ public function PEAR_ErrorStack($package, $msgCallback = false, $contextCallback = false,
+ $throwPEAR_Error = false)
+ {
+ $this->_package = $package;
+ $this->setMessageCallback($msgCallback);
+ $this->setContextCallback($contextCallback);
+ $this->_compat = false;
+ }
+
+ /**
+ * Return a single error stack for this package.
+ *
+ * Note that all parameters are ignored if the stack for package $package
+ * has already been instantiated
+ * @param string $package name of the package this error stack represents
+ * @param callback $msgCallback callback used for error message generation
+ * @param callback $contextCallback callback used for context generation,
+ * defaults to {@link getFileLine()}
+ * @param boolean $throwPEAR_Error
+ * @param string $exceptionClass exception class to instantiate if
+ * in PHP 5
+ * @param string $stackClass class to instantiate
+ * @static
+ * @return PEAR_ErrorStack
+ */
+ static public function singleton($package, $msgCallback = false, $contextCallback = false,
+ $throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack')
+ {
+ if (isset(self::$singleton[$package])) {
+ return self::$singleton[$package];
+ }
+ if (!class_exists($stackClass)) {
+ throw PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_NONCLASS,
+ 'exception', array('stackclass' => $stackClass),
+ 'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)',
+ false, $trace);
+ }
+ return self::$singleton[$package] =
+ new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error);
+ }
+
+
+ /**
+ * Set up a PEAR::Log object for all error stacks that don't have one
+ * @param Log $log
+ * @static
+ */
+ static public function setDefaultLogger($log)
+ {
+ self::$globallogger = $log;
+ }
+
+ /**
+ * Set up a PEAR::Log object for this error stack
+ * @param Log $log
+ */
+ public function setLogger($log)
+ {
+ $this->_logger = $log;
+ }
+
+ /**
+ * Set an error code => error message mapping callback
+ *
+ * This method sets the callback that can be used to generate error
+ * messages for any instance
+ * @param array|string Callback function/method
+ */
+ public function setMessageCallback($msgCallback)
+ {
+ if (!$msgCallback) {
+ $this->_msgCallback = array($this, 'getErrorMessage');
+ } else {
+ if (is_callable($msgCallback)) {
+ $this->_msgCallback = $msgCallback;
+ }
+ }
+ }
+
+ /**
+ * Get an error code => error message mapping callback
+ *
+ * This method returns the current callback that can be used to generate error
+ * messages
+ * @return array|string|false Callback function/method or false if none
+ */
+ public function getMessageCallback()
+ {
+ return $this->_msgCallback;
+ }
+
+ /**
+ * Sets a default callback to be used by all error stacks
+ *
+ * This method sets the callback that can be used to generate error
+ * messages for a singleton
+ * @param array|string Callback function/method
+ * @param string Package name, or false for all packages
+ */
+ static public function setDefaultCallback($callback = false, $package = false)
+ {
+ if (!is_callable($callback)) {
+ $callback = false;
+ }
+ $package = $package ? $package : '*';
+ self::$globalcallback[$package] = $callback;
+ }
+
+ /**
+ * Do not use
+ * @access private
+ */
+ static public function setPEARWarningCallback($callback)
+ {
+ self::$pearwarningcallback = $callback;
+ }
+
+ /**
+ * Set an error code => error message mapping callback
+ *
+ * This method sets the callback that can be used to generate context
+ * information for an error. Passing in NULL will disable context generation
+ * and remove the expensive call to debug_backtrace()
+ * @param array|string Callback function/method
+ */
+ public function setContextCallback($contextCallback)
+ {
+ if ($contextCallback === null) {
+ return $this->contextCallback = false;
+ }
+ if (!$contextCallback) {
+ $this->contextCallback = array($this, 'getFileLine');
+ } else {
+ if (is_callable($contextCallback)) {
+ $this->contextCallback = $contextCallback;
+ }
+ }
+ }
+
+ /**
+ * Set an error Callback
+ * If set to a valid callback, this will be called every time an error
+ * is pushed onto the stack. The return value will be used to determine
+ * whether to allow an error to be pushed or logged.
+ *
+ * The return value must be one of the ERRORSTACK_* constants.
+ *
+ * This functionality can be used to emulate PEAR's pushErrorHandling, and
+ * the PEAR_ERROR_CALLBACK mode, without affecting the integrity of
+ * the error stack or logging
+ * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
+ * @see popCallback()
+ * @param string|array $cb
+ */
+ public function pushCallback($cb)
+ {
+ array_push($this->_errorCallback, $cb);
+ }
+
+ /**
+ * Remove a callback from the error callback stack
+ * @see pushCallback()
+ * @return array|string|false
+ */
+ public function popCallback()
+ {
+ if (!count($this->_errorCallback)) {
+ return false;
+ }
+ return array_pop($this->_errorCallback);
+ }
+
+ /**
+ * Set a temporary overriding error callback for every package error stack
+ *
+ * Use this to temporarily disable all existing callbacks (can be used
+ * to emulate the @ operator, for instance)
+ * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
+ * @see staticPopCallback(), pushCallback()
+ * @param string|array $cb
+ */
+ static public function staticPushCallback($cb)
+ {
+ array_push(self::$overridecallback, $cb);
+ }
+
+ /**
+ * Remove a temporary overriding error callback
+ * @see staticPushCallback()
+ * @return array|string|false
+ */
+ static public function staticPopCallback()
+ {
+ $ret = array_pop(self::$overridecallback);
+ if (!is_array(self::$overridecallback)) {
+ self::$overridecallback = array();
+ }
+ return $ret;
+ }
+
+ /**
+ * demote an exception to a non-fatal error (default is warning)
+ * @param Exception
+ * @param string
+ */
+ public function demoteException($e, $level = 'warning')
+ {
+ $this->push(get_class($e), $level, array(),
+ $e->getMessage(), $e, $e->getTrace());
+ }
+
+ /**
+ * promote a warning into a PEAR_Exception
+ *
+ * It is probably best to do this manually, to take advantage of the
+ * use of exception classnames to categorize errors
+ * @return PEAR_Exception
+ */
+ public function promoteWarning($warning, $exceptionclass = 'PEAR_Exception')
+ {
+ return new $exceptionclass($warning['message'],
+ array($warning), $warning['code']);
+ }
+
+ /**
+ * Add an error to the stack
+ *
+ * If the message generator exists, it is called with 2 parameters.
+ * - the current Error Stack object
+ * - an array that is in the same format as an error. Available indices
+ * are 'code', 'package', 'time', 'params', 'level', and 'context'
+ *
+ * Next, if the error should contain context information, this is
+ * handled by the context grabbing method.
+ * Finally, the error is pushed onto the proper error stack
+ * @param int $code Package-specific error code
+ * @param string $level Error level. This is NOT spell-checked
+ * @param array $params associative array of error parameters
+ * @param string $msg Error message, or a portion of it if the message
+ * is to be generated
+ * @param array $repackage If this error re-packages an error pushed by
+ * another package, place the array returned from
+ * {@link pop()} in this parameter
+ * @param array $backtrace Protected parameter: use this to pass in the
+ * {@link debug_backtrace()} that should be used
+ * to find error context
+ * @return PEAR_Error|array|Exception
+ * if compatibility mode is on, a PEAR_Error is also
+ * thrown. If the class Exception exists, then one
+ * is returned to allow code like:
+ * <code>
+ * throw ($stack->push(MY_ERROR_CODE, 'error', array('username' => 'grob')));
+ * </code>
+ *
+ * The errorData property of the exception class will be set to the array
+ * that would normally be returned. If a PEAR_Error is returned, the userinfo
+ * property is set to the array
+ *
+ * Otherwise, an array is returned in this format:
+ * <code>
+ * array(
+ * 'code' => $code,
+ * 'params' => $params,
+ * 'package' => $this->_package,
+ * 'level' => $level,
+ * 'time' => time(),
+ * 'context' => $context,
+ * 'message' => $msg,
+ * //['repackage' => $err] repackaged error array
+ * );
+ * </code>
+ */
+ public function push($code, $level = 'error', $params = array(), $msg = false,
+ $repackage = false, $backtrace = false)
+ {
+ $context = false;
+ // grab error context
+ if ($this->contextCallback) {
+ if (!$backtrace) {
+ $backtrace = debug_backtrace();
+ }
+ $context = call_user_func($this->contextCallback, $code, $params, $backtrace);
+ }
+
+ // save error
+ $time = explode(' ', microtime());
+ $time = $time[1] + $time[0];
+ $err = array(
+ 'code' => $code,
+ 'params' => $params,
+ 'package' => $this->_package,
+ 'level' => $level,
+ 'time' => $time,
+ 'context' => $context,
+ 'message' => $msg,
+ );
+
+ // set up the error message, if necessary
+ if ($this->_msgCallback) {
+ $msg = call_user_func_array($this->_msgCallback,
+ array($this, $err));
+ $err['message'] = $msg;
+ }
+
+
+ if ($repackage) {
+ $err['repackage'] = $repackage;
+ }
+ $push = $log = true;
+ // try the overriding callback first
+ $callback = $this->staticPopCallback();
+ if ($callback) {
+ $this->staticPushCallback($callback);
+ }
+ if (!is_callable($callback)) {
+ // try the local callback next
+ $callback = $this->popCallback();
+ if (is_callable($callback)) {
+ $this->pushCallback($callback);
+ } else {
+ // try the default callback
+ $callback = isset(self::$globalcallback[$this->_package]) ?
+ self::$globalcallback[$this->_package] :
+ self::$globalcallback['*'];
+ }
+ }
+ if (is_callable($callback)) {
+ switch(call_user_func($callback, $err)){
+ case PEAR_ERRORSTACK_IGNORE:
+ return $err;
+ break;
+ case PEAR_ERRORSTACK_PUSH:
+ $log = false;
+ break;
+ case PEAR_ERRORSTACK_LOG:
+ $push = false;
+ break;
+ // anything else returned has the same effect as pushandlog
+ }
+ }
+ if ($push) {
+ if (is_callable(self::$pearwarningcallback)) {
+ call_user_func(self::$pearwarningcallback, $err);
+ }
+ array_unshift($this->_errors, $err);
+ $this->_errorsByLevel[$err['level']][] = &$this->_errors[0];
+ }
+ if ($log) {
+ if ($this->_logger || self::$globallogger) {
+ $this->_log($err);
+ }
+ }
+ return $err;
+ }
+
+ /**
+ * Static version of {@link push()}
+ *
+ * @param string $package Package name this error belongs to
+ * @param int $code Package-specific error code
+ * @param string $level Error level. This is NOT spell-checked
+ * @param array $params associative array of error parameters
+ * @param string $msg Error message, or a portion of it if the message
+ * is to be generated
+ * @param array $repackage If this error re-packages an error pushed by
+ * another package, place the array returned from
+ * {@link pop()} in this parameter
+ * @param array $backtrace Protected parameter: use this to pass in the
+ * {@link debug_backtrace()} that should be used
+ * to find error context
+ * @return PEAR_Error|null|Exception
+ * if compatibility mode is on, a PEAR_Error is also
+ * thrown. If the class Exception exists, then one
+ * is returned to allow code like:
+ * <code>
+ * throw ($stack->push(MY_ERROR_CODE, 'error', array('username' => 'grob')));
+ * </code>
+ * @static
+ */
+ static public function staticPush($package, $code, $level = 'error', $params = array(),
+ $msg = false, $repackage = false, $backtrace = false)
+ {
+ $s = PEAR_ErrorStack::singleton($package);
+ if ($s->contextCallback) {
+ if (!$backtrace) {
+ $backtrace = debug_backtrace();
+ }
+ }
+ return $s->push($code, $level, $params, $msg, $repackage, $backtrace);
+ }
+
+ /**
+ * Log an error using PEAR::Log
+ * @param array $err Error array
+ * @param array $levels Error level => Log constant map
+ * @access protected
+ */
+ protected function _log($err, $levels = array(
+ 'exception' => PEAR_LOG_CRIT,
+ 'alert' => PEAR_LOG_ALERT,
+ 'critical' => PEAR_LOG_CRIT,
+ 'error' => PEAR_LOG_ERR,
+ 'warning' => PEAR_LOG_WARNING,
+ 'notice' => PEAR_LOG_NOTICE,
+ 'info' => PEAR_LOG_INFO,
+ 'debug' => PEAR_LOG_DEBUG))
+ {
+ if (isset($levels[$err['level']])) {
+ $level = $levels[$err['level']];
+ } else {
+ $level = PEAR_LOG_INFO;
+ }
+ if ($this->_logger) {
+ $this->_logger->log($err['message'], $level, $err);
+ } else {
+ self::$globallogger->log($err['message'], $level, $err);
+ }
+ }
+
+
+ /**
+ * Pop an error off of the error stack
+ *
+ * @return false|array
+ * @since 0.4alpha it is no longer possible to specify a specific error
+ * level to return - the last error pushed will be returned, instead
+ */
+ public function pop()
+ {
+ return @array_shift($this->_errors);
+ }
+
+ /**
+ * Determine whether there are any errors on the stack
+ * @param string|array Level name. Use to determine if any errors
+ * of level (string), or levels (array) have been pushed
+ * @return boolean
+ */
+ public function hasErrors($level = false)
+ {
+ if ($level) {
+ return isset($this->_errorsByLevel[$level]);
+ }
+ return count($this->_errors);
+ }
+
+ /**
+ * Retrieve all errors since last purge
+ *
+ * @param boolean set in order to empty the error stack
+ * @param string level name, to return only errors of a particular severity
+ * @return array
+ */
+ public function getErrors($purge = false, $level = false)
+ {
+ if (!$purge) {
+ if ($level) {
+ if (!isset($this->_errorsByLevel[$level])) {
+ return array();
+ } else {
+ return $this->_errorsByLevel[$level];
+ }
+ } else {
+ return $this->_errors;
+ }
+ }
+ if ($level) {
+ $ret = $this->_errorsByLevel[$level];
+ foreach ($this->_errorsByLevel[$level] as $i => $unused) {
+ // entries are references to the $_errors array
+ $this->_errorsByLevel[$level][$i] = false;
+ }
+ // array_filter removes all entries === false
+ $this->_errors = array_filter($this->_errors);
+ unset($this->_errorsByLevel[$level]);
+ return $ret;
+ }
+ $ret = $this->_errors;
+ $this->_errors = array();
+ $this->_errorsByLevel = array();
+ return $ret;
+ }
+
+ /**
+ * Determine whether there are any errors on a single error stack, or on any error stack
+ *
+ * The optional parameter can be used to test the existence of any errors without the need of
+ * singleton instantiation
+ * @param string|false Package name to check for errors
+ * @param string Level name to check for a particular severity
+ * @return boolean
+ */
+ static public function staticHasErrors($package = false, $level = false)
+ {
+ if ($package) {
+ if (!isset(self::$singleton[$package])) {
+ return false;
+ }
+ return self::$singleton[$package]->hasErrors($level);
+ }
+ foreach (self::$singleton as $package => $obj) {
+ if ($obj->hasErrors($level)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get a list of all errors since last purge, organized by package
+ * @since PEAR 1.4.0dev BC break! $level is now in the place $merge used to be
+ * @param boolean $clearStack Set to purge the error stack of existing errors
+ * @param string $level Set to a level name in order to retrieve only errors of a particular level
+ * @param boolean $merge Set to return a flat array, not organized by package
+ * @param array $sortfunc Function used to sort a merged array - default
+ * sorts by time, and should be good for most cases
+ * @return array
+ */
+ static public function staticGetErrors($purge = false, $level = false, $merge = false, $sortfunc = array('PEAR_ErrorStack', '_sortErrors'))
+ {
+ $ret = array();
+ if (!is_callable($sortfunc)) {
+ $sortfunc = array('PEAR_ErrorStack', '_sortErrors');
+ }
+ foreach (self::$singleton as $package => $obj) {
+ $test = self::$singleton[$package]->getErrors($purge, $level);
+ if ($test) {
+ if ($merge) {
+ $ret = array_merge($ret, $test);
+ } else {
+ $ret[$package] = $test;
+ }
+ }
+ }
+ if ($merge) {
+ usort($ret, $sortfunc);
+ }
+ return $ret;
+ }
+
+ /**
+ * Error sorting function, sorts by time
+ * @access private
+ */
+ function _sortErrors($a, $b)
+ {
+ if ($a['time'] == $b['time']) {
+ return 0;
+ }
+ if ($a['time'] < $b['time']) {
+ return 1;
+ }
+ return -1;
+ }
+
+ /**
+ * Standard file/line number/function/class context callback
+ *
+ * This function uses a backtrace generated from {@link debug_backtrace()}
+ * and so will not work at all in PHP < 4.3.0. The frame should
+ * reference the frame that contains the source of the error.
+ * @return array|false either array('file' => file, 'line' => line,
+ * 'function' => function name, 'class' => class name) or
+ * if this doesn't work, then false
+ * @param unused
+ * @param integer backtrace frame.
+ * @param array Results of debug_backtrace()
+ */
+ static public function getFileLine($code, $params, $backtrace = null)
+ {
+ if ($backtrace === null) {
+ return false;
+ }
+ $frame = 0;
+ $functionframe = 1;
+ if (!isset($backtrace[1])) {
+ $functionframe = 0;
+ } else {
+ while (isset($backtrace[$functionframe]['function']) &&
+ $backtrace[$functionframe]['function'] == 'eval' &&
+ isset($backtrace[$functionframe + 1])) {
+ $functionframe++;
+ }
+ }
+ if (isset($backtrace[$frame])) {
+ if (!isset($backtrace[$frame]['file'])) {
+ $frame++;
+ }
+ $funcbacktrace = $backtrace[$functionframe];
+ $filebacktrace = $backtrace[$frame];
+ $ret = array('file' => $filebacktrace['file'],
+ 'line' => $filebacktrace['line']);
+ // rearrange for eval'd code or create function errors
+ if (strpos($filebacktrace['file'], '(') &&
+ preg_match(';^(.*?)\((\d+)\) : (.*?)\\z;', $filebacktrace['file'],
+ $matches)) {
+ $ret['file'] = $matches[1];
+ $ret['line'] = $matches[2] + 0;
+ }
+ if (isset($funcbacktrace['function']) && isset($backtrace[1])) {
+ if ($funcbacktrace['function'] != 'eval') {
+ if ($funcbacktrace['function'] == '__lambda_func') {
+ $ret['function'] = 'create_function() code';
+ } else {
+ $ret['function'] = $funcbacktrace['function'];
+ }
+ }
+ }
+ if (isset($funcbacktrace['class']) && isset($backtrace[1])) {
+ $ret['class'] = $funcbacktrace['class'];
+ }
+ return $ret;
+ }
+ return false;
+ }
+
+ /**
+ * Standard error message generation callback
+ *
+ * This method may also be called by a custom error message generator
+ * to fill in template values from the params array, simply
+ * set the third parameter to the error message template string to use
+ *
+ * The special variable %__msg% is reserved: use it only to specify
+ * where a message passed in by the user should be placed in the template,
+ * like so:
+ *
+ * Error message: %msg% - internal error
+ *
+ * If the message passed like so:
+ *
+ * <code>
+ * $stack->push(ERROR_CODE, 'error', array(), 'server error 500');
+ * </code>
+ *
+ * The returned error message will be "Error message: server error 500 -
+ * internal error"
+ * @param PEAR_ErrorStack
+ * @param array
+ * @param string|false Pre-generated error message template
+ * @static
+ * @return string
+ */
+ public function getErrorMessage(&$stack, $err, $template = false)
+ {
+ if ($template) {
+ $mainmsg = $template;
+ } else {
+ $mainmsg = $stack->getErrorMessageTemplate($err['code']);
+ }
+ $mainmsg = str_replace('%__msg%', $err['message'], $mainmsg);
+ if (count($err['params'])) {
+ foreach ($err['params'] as $name => $val) {
+ if (is_array($val)) {
+ // @ is needed in case $val is a multi-dimensional array
+ $val = @implode(', ', $val);
+ }
+ if (is_object($val)) {
+ if (method_exists($val, '__toString')) {
+ $val = $val->__toString();
+ } else {
+ throw PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_OBJTOSTRING,
+ 'warning', array('obj' => get_class($val)),
+ 'object %obj% passed into getErrorMessage, but has no __toString() method');
+ $val = 'Object';
+ }
+ }
+ $mainmsg = str_replace('%' . $name . '%', $val, $mainmsg);
+ }
+ }
+ return $mainmsg;
+ }
+
+ /**
+ * Standard Error Message Template generator from code
+ * @return string
+ */
+ public function getErrorMessageTemplate($code)
+ {
+ if (!isset($this->_errorMsgs[$code])) {
+ return '%__msg%';
+ }
+ return $this->_errorMsgs[$code];
+ }
+
+ /**
+ * Set the Error Message Template array
+ *
+ * The array format must be:
+ * <pre>
+ * array(error code => 'message template',...)
+ * </pre>
+ *
+ * Error message parameters passed into {@link push()} will be used as input
+ * for the error message. If the template is 'message %foo% was %bar%', and the
+ * parameters are array('foo' => 'one', 'bar' => 'six'), the error message returned will
+ * be 'message one was six'
+ * @return string
+ */
+ public function setErrorMessageTemplate($template)
+ {
+ $this->_errorMsgs = $template;
+ }
+}
+PEAR_ErrorStack::singleton('PEAR_ErrorStack', false, null, false, 'PEAR_ErrorStack_Exception');
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Exception.php b/includes/pear/PEAR/Exception.php
new file mode 100644
index 0000000..85eaf2c
--- /dev/null
+++ b/includes/pear/PEAR/Exception.php
@@ -0,0 +1,389 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
+/**
+ * PEAR_Exception
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR_Exception
+ * @author Tomas V. V. Cox <cox@idecnet.com>
+ * @author Hans Lellelid <hans@velum.net>
+ * @author Bertrand Mansion <bmansion@mamasam.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR_Exception
+ * @since File available since Release 1.0.0
+ */
+
+
+/**
+ * Base PEAR_Exception Class
+ *
+ * 1) Features:
+ *
+ * - Nestable exceptions (throw new PEAR_Exception($msg, $prev_exception))
+ * - Definable triggers, shot when exceptions occur
+ * - Pretty and informative error messages
+ * - Added more context info available (like class, method or cause)
+ * - cause can be a PEAR_Exception or an array of mixed
+ * PEAR_Exceptions/PEAR_ErrorStack warnings
+ * - callbacks for specific exception classes and their children
+ *
+ * 2) Ideas:
+ *
+ * - Maybe a way to define a 'template' for the output
+ *
+ * 3) Inherited properties from PHP Exception Class:
+ *
+ * protected $message
+ * protected $code
+ * protected $line
+ * protected $file
+ * private $trace
+ *
+ * 4) Inherited methods from PHP Exception Class:
+ *
+ * __clone
+ * __construct
+ * getMessage
+ * getCode
+ * getFile
+ * getLine
+ * getTraceSafe
+ * getTraceSafeAsString
+ * __toString
+ *
+ * 5) Usage example
+ *
+ * <code>
+ * require_once 'PEAR/Exception.php';
+ *
+ * class Test {
+ * function foo() {
+ * throw new PEAR_Exception('Error Message', ERROR_CODE);
+ * }
+ * }
+ *
+ * function myLogger($pear_exception) {
+ * echo $pear_exception->getMessage();
+ * }
+ * // each time a exception is thrown the 'myLogger' will be called
+ * // (its use is completely optional)
+ * PEAR_Exception::addObserver('myLogger');
+ * $test = new Test;
+ * try {
+ * $test->foo();
+ * } catch (PEAR_Exception $e) {
+ * print $e;
+ * }
+ * </code>
+ *
+ * @category pear
+ * @package PEAR_Exception
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Hans Lellelid <hans@velum.net>
+ * @author Bertrand Mansion <bmansion@mamasam.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR_Exception
+ * @since Class available since Release 1.0.0
+ *
+ */
+class PEAR_Exception extends Exception
+{
+ const OBSERVER_PRINT = -2;
+ const OBSERVER_TRIGGER = -4;
+ const OBSERVER_DIE = -8;
+ protected $cause;
+ private static $_observers = array();
+ private static $_uniqueid = 0;
+ private $_trace;
+
+ /**
+ * Supported signatures:
+ * - PEAR_Exception(string $message);
+ * - PEAR_Exception(string $message, int $code);
+ * - PEAR_Exception(string $message, Exception $cause);
+ * - PEAR_Exception(string $message, Exception $cause, int $code);
+ * - PEAR_Exception(string $message, PEAR_Error $cause);
+ * - PEAR_Exception(string $message, PEAR_Error $cause, int $code);
+ * - PEAR_Exception(string $message, array $causes);
+ * - PEAR_Exception(string $message, array $causes, int $code);
+ * @param string exception message
+ * @param int|Exception|PEAR_Error|array|null exception cause
+ * @param int|null exception code or null
+ */
+ public function __construct($message, $p2 = null, $p3 = null)
+ {
+ if (is_int($p2)) {
+ $code = $p2;
+ $this->cause = null;
+ } elseif (is_object($p2) || is_array($p2)) {
+ // using is_object allows both Exception and PEAR_Error
+ if (is_object($p2) && !($p2 instanceof Exception)) {
+ if (!class_exists('PEAR_Error') || !($p2 instanceof PEAR_Error)) {
+ throw new PEAR_Exception('exception cause must be Exception, ' .
+ 'array, or PEAR_Error');
+ }
+ }
+ $code = $p3;
+ if (is_array($p2) && isset($p2['message'])) {
+ // fix potential problem of passing in a single warning
+ $p2 = array($p2);
+ }
+ $this->cause = $p2;
+ } else {
+ $code = null;
+ $this->cause = null;
+ }
+ parent::__construct($message, $code);
+ $this->signal();
+ }
+
+ /**
+ * @param mixed $callback - A valid php callback, see php func is_callable()
+ * - A PEAR_Exception::OBSERVER_* constant
+ * - An array(const PEAR_Exception::OBSERVER_*,
+ * mixed $options)
+ * @param string $label The name of the observer. Use this if you want
+ * to remove it later with removeObserver()
+ */
+ public static function addObserver($callback, $label = 'default')
+ {
+ self::$_observers[$label] = $callback;
+ }
+
+ public static function removeObserver($label = 'default')
+ {
+ unset(self::$_observers[$label]);
+ }
+
+ /**
+ * @return int unique identifier for an observer
+ */
+ public static function getUniqueId()
+ {
+ return self::$_uniqueid++;
+ }
+
+ private function signal()
+ {
+ foreach (self::$_observers as $func) {
+ if (is_callable($func)) {
+ call_user_func($func, $this);
+ continue;
+ }
+ settype($func, 'array');
+ switch ($func[0]) {
+ case self::OBSERVER_PRINT :
+ $f = (isset($func[1])) ? $func[1] : '%s';
+ printf($f, $this->getMessage());
+ break;
+ case self::OBSERVER_TRIGGER :
+ $f = (isset($func[1])) ? $func[1] : E_USER_NOTICE;
+ trigger_error($this->getMessage(), $f);
+ break;
+ case self::OBSERVER_DIE :
+ $f = (isset($func[1])) ? $func[1] : '%s';
+ die(printf($f, $this->getMessage()));
+ break;
+ default:
+ trigger_error('invalid observer type', E_USER_WARNING);
+ }
+ }
+ }
+
+ /**
+ * Return specific error information that can be used for more detailed
+ * error messages or translation.
+ *
+ * This method may be overridden in child exception classes in order
+ * to add functionality not present in PEAR_Exception and is a placeholder
+ * to define API
+ *
+ * The returned array must be an associative array of parameter => value like so:
+ * <pre>
+ * array('name' => $name, 'context' => array(...))
+ * </pre>
+ * @return array
+ */
+ public function getErrorData()
+ {
+ return array();
+ }
+
+ /**
+ * Returns the exception that caused this exception to be thrown
+ * @access public
+ * @return Exception|array The context of the exception
+ */
+ public function getCause()
+ {
+ return $this->cause;
+ }
+
+ /**
+ * Function must be public to call on caused exceptions
+ * @param array
+ */
+ public function getCauseMessage(&$causes)
+ {
+ $trace = $this->getTraceSafe();
+ $cause = array('class' => get_class($this),
+ 'message' => $this->message,
+ 'file' => 'unknown',
+ 'line' => 'unknown');
+ if (isset($trace[0])) {
+ if (isset($trace[0]['file'])) {
+ $cause['file'] = $trace[0]['file'];
+ $cause['line'] = $trace[0]['line'];
+ }
+ }
+ $causes[] = $cause;
+ if ($this->cause instanceof PEAR_Exception) {
+ $this->cause->getCauseMessage($causes);
+ } elseif ($this->cause instanceof Exception) {
+ $causes[] = array('class' => get_class($this->cause),
+ 'message' => $this->cause->getMessage(),
+ 'file' => $this->cause->getFile(),
+ 'line' => $this->cause->getLine());
+ } elseif (class_exists('PEAR_Error') && $this->cause instanceof PEAR_Error) {
+ $causes[] = array('class' => get_class($this->cause),
+ 'message' => $this->cause->getMessage(),
+ 'file' => 'unknown',
+ 'line' => 'unknown');
+ } elseif (is_array($this->cause)) {
+ foreach ($this->cause as $cause) {
+ if ($cause instanceof PEAR_Exception) {
+ $cause->getCauseMessage($causes);
+ } elseif ($cause instanceof Exception) {
+ $causes[] = array('class' => get_class($cause),
+ 'message' => $cause->getMessage(),
+ 'file' => $cause->getFile(),
+ 'line' => $cause->getLine());
+ } elseif (class_exists('PEAR_Error') && $cause instanceof PEAR_Error) {
+ $causes[] = array('class' => get_class($cause),
+ 'message' => $cause->getMessage(),
+ 'file' => 'unknown',
+ 'line' => 'unknown');
+ } elseif (is_array($cause) && isset($cause['message'])) {
+ // PEAR_ErrorStack warning
+ $causes[] = array(
+ 'class' => $cause['package'],
+ 'message' => $cause['message'],
+ 'file' => isset($cause['context']['file']) ?
+ $cause['context']['file'] :
+ 'unknown',
+ 'line' => isset($cause['context']['line']) ?
+ $cause['context']['line'] :
+ 'unknown',
+ );
+ }
+ }
+ }
+ }
+
+ public function getTraceSafe()
+ {
+ if (!isset($this->_trace)) {
+ $this->_trace = $this->getTrace();
+ if (empty($this->_trace)) {
+ $backtrace = debug_backtrace();
+ $this->_trace = array($backtrace[count($backtrace)-1]);
+ }
+ }
+ return $this->_trace;
+ }
+
+ public function getErrorClass()
+ {
+ $trace = $this->getTraceSafe();
+ return $trace[0]['class'];
+ }
+
+ public function getErrorMethod()
+ {
+ $trace = $this->getTraceSafe();
+ return $trace[0]['function'];
+ }
+
+ public function __toString()
+ {
+ if (isset($_SERVER['REQUEST_URI'])) {
+ return $this->toHtml();
+ }
+ return $this->toText();
+ }
+
+ public function toHtml()
+ {
+ $trace = $this->getTraceSafe();
+ $causes = array();
+ $this->getCauseMessage($causes);
+ $html = '<table style="border: 1px" cellspacing="0">' . "\n";
+ foreach ($causes as $i => $cause) {
+ $html .= '<tr><td colspan="3" style="background: #ff9999">'
+ . str_repeat('-', $i) . ' <b>' . $cause['class'] . '</b>: '
+ . htmlspecialchars($cause['message']) . ' in <b>' . $cause['file'] . '</b> '
+ . 'on line <b>' . $cause['line'] . '</b>'
+ . "</td></tr>\n";
+ }
+ $html .= '<tr><td colspan="3" style="background-color: #aaaaaa; text-align: center; font-weight: bold;">Exception trace</td></tr>' . "\n"
+ . '<tr><td style="text-align: center; background: #cccccc; width:20px; font-weight: bold;">#</td>'
+ . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Function</td>'
+ . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Location</td></tr>' . "\n";
+
+ foreach ($trace as $k => $v) {
+ $html .= '<tr><td style="text-align: center;">' . $k . '</td>'
+ . '<td>';
+ if (!empty($v['class'])) {
+ $html .= $v['class'] . $v['type'];
+ }
+ $html .= $v['function'];
+ $args = array();
+ if (!empty($v['args'])) {
+ foreach ($v['args'] as $arg) {
+ if (is_null($arg)) $args[] = 'null';
+ elseif (is_array($arg)) $args[] = 'Array';
+ elseif (is_object($arg)) $args[] = 'Object('.get_class($arg).')';
+ elseif (is_bool($arg)) $args[] = $arg ? 'true' : 'false';
+ elseif (is_int($arg) || is_double($arg)) $args[] = $arg;
+ else {
+ $arg = (string)$arg;
+ $str = htmlspecialchars(substr($arg, 0, 16));
+ if (strlen($arg) > 16) $str .= '&hellip;';
+ $args[] = "'" . $str . "'";
+ }
+ }
+ }
+ $html .= '(' . implode(', ',$args) . ')'
+ . '</td>'
+ . '<td>' . (isset($v['file']) ? $v['file'] : 'unknown')
+ . ':' . (isset($v['line']) ? $v['line'] : 'unknown')
+ . '</td></tr>' . "\n";
+ }
+ $html .= '<tr><td style="text-align: center;">' . ($k+1) . '</td>'
+ . '<td>{main}</td>'
+ . '<td>&nbsp;</td></tr>' . "\n"
+ . '</table>';
+ return $html;
+ }
+
+ public function toText()
+ {
+ $causes = array();
+ $this->getCauseMessage($causes);
+ $causeMsg = '';
+ foreach ($causes as $i => $cause) {
+ $causeMsg .= str_repeat(' ', $i) . $cause['class'] . ': '
+ . $cause['message'] . ' in ' . $cause['file']
+ . ' on line ' . $cause['line'] . "\n";
+ }
+ return $causeMsg . $this->getTraceAsString();
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/FixPHP5PEARWarnings.php b/includes/pear/PEAR/FixPHP5PEARWarnings.php
new file mode 100644
index 0000000..be5dc3c
--- /dev/null
+++ b/includes/pear/PEAR/FixPHP5PEARWarnings.php
@@ -0,0 +1,7 @@
+<?php
+if ($skipmsg) {
+ $a = &new $ec($code, $mode, $options, $userinfo);
+} else {
+ $a = &new $ec($message, $code, $mode, $options, $userinfo);
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Frontend.php b/includes/pear/PEAR/Frontend.php
new file mode 100644
index 0000000..b7447b5
--- /dev/null
+++ b/includes/pear/PEAR/Frontend.php
@@ -0,0 +1,228 @@
+<?php
+/**
+ * PEAR_Frontend, the singleton-based frontend for user input/output
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * Include error handling
+ */
+//require_once 'PEAR.php';
+
+/**
+ * Which user interface class is being used.
+ * @var string class name
+ */
+$GLOBALS['_PEAR_FRONTEND_CLASS'] = 'PEAR_Frontend_CLI';
+
+/**
+ * Instance of $_PEAR_Command_uiclass.
+ * @var object
+ */
+$GLOBALS['_PEAR_FRONTEND_SINGLETON'] = null;
+
+/**
+ * Singleton-based frontend for PEAR user input/output
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Frontend extends PEAR
+{
+ /**
+ * Retrieve the frontend object
+ * @return PEAR_Frontend_CLI|PEAR_Frontend_Web|PEAR_Frontend_Gtk
+ * @static
+ */
+ function &singleton($type = null)
+ {
+ if ($type === null) {
+ if (!isset($GLOBALS['_PEAR_FRONTEND_SINGLETON'])) {
+ $a = false;
+ return $a;
+ }
+ return $GLOBALS['_PEAR_FRONTEND_SINGLETON'];
+ }
+
+ $a = PEAR_Frontend::setFrontendClass($type);
+ return $a;
+ }
+
+ /**
+ * Set the frontend class that will be used by calls to {@link singleton()}
+ *
+ * Frontends are expected to conform to the PEAR naming standard of
+ * _ => DIRECTORY_SEPARATOR (PEAR_Frontend_CLI is in PEAR/Frontend/CLI.php)
+ * @param string $uiclass full class name
+ * @return PEAR_Frontend
+ * @static
+ */
+ function &setFrontendClass($uiclass)
+ {
+ if (is_object($GLOBALS['_PEAR_FRONTEND_SINGLETON']) &&
+ is_a($GLOBALS['_PEAR_FRONTEND_SINGLETON'], $uiclass)) {
+ return $GLOBALS['_PEAR_FRONTEND_SINGLETON'];
+ }
+
+ if (!class_exists($uiclass)) {
+ $file = str_replace('_', '/', $uiclass) . '.php';
+ if (PEAR_Frontend::isIncludeable($file)) {
+ include_once $file;
+ }
+ }
+
+ if (class_exists($uiclass)) {
+ $obj = &new $uiclass;
+ // quick test to see if this class implements a few of the most
+ // important frontend methods
+ if (is_a($obj, 'PEAR_Frontend')) {
+ $GLOBALS['_PEAR_FRONTEND_SINGLETON'] = &$obj;
+ $GLOBALS['_PEAR_FRONTEND_CLASS'] = $uiclass;
+ return $obj;
+ }
+
+ $err = PEAR::raiseError("not a frontend class: $uiclass");
+ return $err;
+ }
+
+ $err = PEAR::raiseError("no such class: $uiclass");
+ return $err;
+ }
+
+ /**
+ * Set the frontend class that will be used by calls to {@link singleton()}
+ *
+ * Frontends are expected to be a descendant of PEAR_Frontend
+ * @param PEAR_Frontend
+ * @return PEAR_Frontend
+ * @static
+ */
+ function &setFrontendObject($uiobject)
+ {
+ if (is_object($GLOBALS['_PEAR_FRONTEND_SINGLETON']) &&
+ is_a($GLOBALS['_PEAR_FRONTEND_SINGLETON'], get_class($uiobject))) {
+ return $GLOBALS['_PEAR_FRONTEND_SINGLETON'];
+ }
+
+ if (!is_a($uiobject, 'PEAR_Frontend')) {
+ $err = PEAR::raiseError('not a valid frontend class: (' .
+ get_class($uiobject) . ')');
+ return $err;
+ }
+
+ $GLOBALS['_PEAR_FRONTEND_SINGLETON'] = &$uiobject;
+ $GLOBALS['_PEAR_FRONTEND_CLASS'] = get_class($uiobject);
+ return $uiobject;
+ }
+
+ /**
+ * @param string $path relative or absolute include path
+ * @return boolean
+ * @static
+ */
+ function isIncludeable($path)
+ {
+ if (file_exists($path) && is_readable($path)) {
+ return true;
+ }
+
+ $fp = @fopen($path, 'r', true);
+ if ($fp) {
+ fclose($fp);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param PEAR_Config
+ */
+ function setConfig(&$config)
+ {
+ }
+
+ /**
+ * This can be overridden to allow session-based temporary file management
+ *
+ * By default, all files are deleted at the end of a session. The web installer
+ * needs to be able to sustain a list over many sessions in order to support
+ * user interaction with install scripts
+ */
+ function addTempFile($file)
+ {
+ $GLOBALS['_PEAR_Common_tempfiles'][] = $file;
+ }
+
+ /**
+ * Log an action
+ *
+ * @param string $msg the message to log
+ * @param boolean $append_crlf
+ * @return boolean true
+ * @abstract
+ */
+ function log($msg, $append_crlf = true)
+ {
+ }
+
+ /**
+ * Run a post-installation script
+ *
+ * @param array $scripts array of post-install scripts
+ * @abstract
+ */
+ function runPostinstallScripts(&$scripts)
+ {
+ }
+
+ /**
+ * Display human-friendly output formatted depending on the
+ * $command parameter.
+ *
+ * This should be able to handle basic output data with no command
+ * @param mixed $data data structure containing the information to display
+ * @param string $command command from which this method was called
+ * @abstract
+ */
+ function outputData($data, $command = '_default')
+ {
+ }
+
+ /**
+ * Display a modal form dialog and return the given input
+ *
+ * A frontend that requires multiple requests to retrieve and process
+ * data must take these needs into account, and implement the request
+ * handling code.
+ * @param string $command command from which this method was called
+ * @param array $prompts associative array. keys are the input field names
+ * and values are the description
+ * @param array $types array of input field types (text, password,
+ * etc.) keys have to be the same like in $prompts
+ * @param array $defaults array of default values. again keys have
+ * to be the same like in $prompts. Do not depend
+ * on a default value being set.
+ * @return array input sent by the user
+ * @abstract
+ */
+ function userDialog($command, $prompts, $types = array(), $defaults = array())
+ {
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Frontend/CLI.php b/includes/pear/PEAR/Frontend/CLI.php
new file mode 100644
index 0000000..8c52e4e
--- /dev/null
+++ b/includes/pear/PEAR/Frontend/CLI.php
@@ -0,0 +1,751 @@
+<?php
+/**
+ * PEAR_Frontend_CLI
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+/**
+ * base class
+ */
+require_once 'PEAR/Frontend.php';
+
+/**
+ * Command-line Frontend for the PEAR Installer
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ */
+class PEAR_Frontend_CLI extends PEAR_Frontend
+{
+ /**
+ * What type of user interface this frontend is for.
+ * @var string
+ * @access public
+ */
+ var $type = 'CLI';
+ var $lp = ''; // line prefix
+
+ var $params = array();
+ var $term = array(
+ 'bold' => '',
+ 'normal' => '',
+ );
+
+ function PEAR_Frontend_CLI()
+ {
+ parent::PEAR();
+ $term = getenv('TERM'); //(cox) $_ENV is empty for me in 4.1.1
+ if (function_exists('posix_isatty') && !posix_isatty(1)) {
+ // output is being redirected to a file or through a pipe
+ } elseif ($term) {
+ if (preg_match('/^(xterm|vt220|linux)/', $term)) {
+ $this->term['bold'] = sprintf("%c%c%c%c", 27, 91, 49, 109);
+ $this->term['normal'] = sprintf("%c%c%c", 27, 91, 109);
+ } elseif (preg_match('/^vt100/', $term)) {
+ $this->term['bold'] = sprintf("%c%c%c%c%c%c", 27, 91, 49, 109, 0, 0);
+ $this->term['normal'] = sprintf("%c%c%c%c%c", 27, 91, 109, 0, 0);
+ }
+ } elseif (OS_WINDOWS) {
+ // XXX add ANSI codes here
+ }
+ }
+
+ /**
+ * @param object PEAR_Error object
+ */
+ function displayError($e)
+ {
+ return $this->_displayLine($e->getMessage());
+ }
+
+ /**
+ * @param object PEAR_Error object
+ */
+ function displayFatalError($eobj)
+ {
+ $this->displayError($eobj);
+ if (class_exists('PEAR_Config')) {
+ $config = &PEAR_Config::singleton();
+ if ($config->get('verbose') > 5) {
+ if (function_exists('debug_print_backtrace')) {
+ debug_print_backtrace();
+ exit(1);
+ }
+
+ $raised = false;
+ foreach (debug_backtrace() as $i => $frame) {
+ if (!$raised) {
+ if (isset($frame['class'])
+ && strtolower($frame['class']) == 'pear'
+ && strtolower($frame['function']) == 'raiseerror'
+ ) {
+ $raised = true;
+ } else {
+ continue;
+ }
+ }
+
+ $frame['class'] = !isset($frame['class']) ? '' : $frame['class'];
+ $frame['type'] = !isset($frame['type']) ? '' : $frame['type'];
+ $frame['function'] = !isset($frame['function']) ? '' : $frame['function'];
+ $frame['line'] = !isset($frame['line']) ? '' : $frame['line'];
+ $this->_displayLine("#$i: $frame[class]$frame[type]$frame[function] $frame[line]");
+ }
+ }
+ }
+
+ exit(1);
+ }
+
+ /**
+ * Instruct the runInstallScript method to skip a paramgroup that matches the
+ * id value passed in.
+ *
+ * This method is useful for dynamically configuring which sections of a post-install script
+ * will be run based on the user's setup, which is very useful for making flexible
+ * post-install scripts without losing the cross-Frontend ability to retrieve user input
+ * @param string
+ */
+ function skipParamgroup($id)
+ {
+ $this->_skipSections[$id] = true;
+ }
+
+ function runPostinstallScripts(&$scripts)
+ {
+ foreach ($scripts as $i => $script) {
+ $this->runInstallScript($scripts[$i]->_params, $scripts[$i]->_obj);
+ }
+ }
+
+ /**
+ * @param array $xml contents of postinstallscript tag
+ * @param object $script post-installation script
+ * @param string install|upgrade
+ */
+ function runInstallScript($xml, &$script)
+ {
+ $this->_skipSections = array();
+ if (!is_array($xml) || !isset($xml['paramgroup'])) {
+ $script->run(array(), '_default');
+ return;
+ }
+
+ $completedPhases = array();
+ if (!isset($xml['paramgroup'][0])) {
+ $xml['paramgroup'] = array($xml['paramgroup']);
+ }
+
+ foreach ($xml['paramgroup'] as $group) {
+ if (isset($this->_skipSections[$group['id']])) {
+ // the post-install script chose to skip this section dynamically
+ continue;
+ }
+
+ if (isset($group['name'])) {
+ $paramname = explode('::', $group['name']);
+ if ($lastgroup['id'] != $paramname[0]) {
+ continue;
+ }
+
+ $group['name'] = $paramname[1];
+ if (!isset($answers)) {
+ return;
+ }
+
+ if (isset($answers[$group['name']])) {
+ switch ($group['conditiontype']) {
+ case '=' :
+ if ($answers[$group['name']] != $group['value']) {
+ continue 2;
+ }
+ break;
+ case '!=' :
+ if ($answers[$group['name']] == $group['value']) {
+ continue 2;
+ }
+ break;
+ case 'preg_match' :
+ if (!@preg_match('/' . $group['value'] . '/',
+ $answers[$group['name']])) {
+ continue 2;
+ }
+ break;
+ default :
+ return;
+ }
+ }
+ }
+
+ $lastgroup = $group;
+ if (isset($group['instructions'])) {
+ $this->_display($group['instructions']);
+ }
+
+ if (!isset($group['param'][0])) {
+ $group['param'] = array($group['param']);
+ }
+
+ if (isset($group['param'])) {
+ if (method_exists($script, 'postProcessPrompts')) {
+ $prompts = $script->postProcessPrompts($group['param'], $group['id']);
+ if (!is_array($prompts) || count($prompts) != count($group['param'])) {
+ $this->outputData('postinstall', 'Error: post-install script did not ' .
+ 'return proper post-processed prompts');
+ $prompts = $group['param'];
+ } else {
+ foreach ($prompts as $i => $var) {
+ if (!is_array($var) || !isset($var['prompt']) ||
+ !isset($var['name']) ||
+ ($var['name'] != $group['param'][$i]['name']) ||
+ ($var['type'] != $group['param'][$i]['type'])
+ ) {
+ $this->outputData('postinstall', 'Error: post-install script ' .
+ 'modified the variables or prompts, severe security risk. ' .
+ 'Will instead use the defaults from the package.xml');
+ $prompts = $group['param'];
+ }
+ }
+ }
+
+ $answers = $this->confirmDialog($prompts);
+ } else {
+ $answers = $this->confirmDialog($group['param']);
+ }
+ }
+
+ if ((isset($answers) && $answers) || !isset($group['param'])) {
+ if (!isset($answers)) {
+ $answers = array();
+ }
+
+ array_unshift($completedPhases, $group['id']);
+ if (!$script->run($answers, $group['id'])) {
+ $script->run($completedPhases, '_undoOnError');
+ return;
+ }
+ } else {
+ $script->run($completedPhases, '_undoOnError');
+ return;
+ }
+ }
+ }
+
+ /**
+ * Ask for user input, confirm the answers and continue until the user is satisfied
+ * @param array an array of arrays, format array('name' => 'paramname', 'prompt' =>
+ * 'text to display', 'type' => 'string'[, default => 'default value'])
+ * @return array
+ */
+ function confirmDialog($params)
+ {
+ $answers = $prompts = $types = array();
+ foreach ($params as $param) {
+ $prompts[$param['name']] = $param['prompt'];
+ $types[$param['name']] = $param['type'];
+ $answers[$param['name']] = isset($param['default']) ? $param['default'] : '';
+ }
+
+ $tried = false;
+ do {
+ if ($tried) {
+ $i = 1;
+ foreach ($answers as $var => $value) {
+ if (!strlen($value)) {
+ echo $this->bold("* Enter an answer for #" . $i . ": ({$prompts[$var]})\n");
+ }
+ $i++;
+ }
+ }
+
+ $answers = $this->userDialog('', $prompts, $types, $answers);
+ $tried = true;
+ } while (is_array($answers) && count(array_filter($answers)) != count($prompts));
+
+ return $answers;
+ }
+
+ function userDialog($command, $prompts, $types = array(), $defaults = array(), $screensize = 20)
+ {
+ if (!is_array($prompts)) {
+ return array();
+ }
+
+ $testprompts = array_keys($prompts);
+ $result = $defaults;
+
+ reset($prompts);
+ if (count($prompts) === 1) {
+ foreach ($prompts as $key => $prompt) {
+ $type = $types[$key];
+ $default = @$defaults[$key];
+ print "$prompt ";
+ if ($default) {
+ print "[$default] ";
+ }
+ print ": ";
+
+ $line = fgets(STDIN, 2048);
+ $result[$key] = ($default && trim($line) == '') ? $default : trim($line);
+ }
+
+ return $result;
+ }
+
+ $first_run = true;
+ while (true) {
+ $descLength = max(array_map('strlen', $prompts));
+ $descFormat = "%-{$descLength}s";
+ $last = count($prompts);
+
+ $i = 0;
+ foreach ($prompts as $n => $var) {
+ $res = isset($result[$n]) ? $result[$n] : null;
+ printf("%2d. $descFormat : %s\n", ++$i, $prompts[$n], $res);
+ }
+ print "\n1-$last, 'all', 'abort', or Enter to continue: ";
+
+ $tmp = trim(fgets(STDIN, 1024));
+ if (empty($tmp)) {
+ break;
+ }
+
+ if ($tmp == 'abort') {
+ return false;
+ }
+
+ if (isset($testprompts[(int)$tmp - 1])) {
+ $var = $testprompts[(int)$tmp - 1];
+ $desc = $prompts[$var];
+ $current = @$result[$var];
+ print "$desc [$current] : ";
+ $tmp = trim(fgets(STDIN, 1024));
+ if ($tmp !== '') {
+ $result[$var] = $tmp;
+ }
+ } elseif ($tmp == 'all') {
+ foreach ($prompts as $var => $desc) {
+ $current = $result[$var];
+ print "$desc [$current] : ";
+ $tmp = trim(fgets(STDIN, 1024));
+ if (trim($tmp) !== '') {
+ $result[$var] = trim($tmp);
+ }
+ }
+ }
+
+ $first_run = false;
+ }
+
+ return $result;
+ }
+
+ function userConfirm($prompt, $default = 'yes')
+ {
+ trigger_error("PEAR_Frontend_CLI::userConfirm not yet converted", E_USER_ERROR);
+ static $positives = array('y', 'yes', 'on', '1');
+ static $negatives = array('n', 'no', 'off', '0');
+ print "$this->lp$prompt [$default] : ";
+ $fp = fopen("php://stdin", "r");
+ $line = fgets($fp, 2048);
+ fclose($fp);
+ $answer = strtolower(trim($line));
+ if (empty($answer)) {
+ $answer = $default;
+ }
+ if (in_array($answer, $positives)) {
+ return true;
+ }
+ if (in_array($answer, $negatives)) {
+ return false;
+ }
+ if (in_array($default, $positives)) {
+ return true;
+ }
+ return false;
+ }
+
+ function outputData($data, $command = '_default')
+ {
+ switch ($command) {
+ case 'channel-info':
+ foreach ($data as $type => $section) {
+ if ($type == 'main') {
+ $section['data'] = array_values($section['data']);
+ }
+
+ $this->outputData($section);
+ }
+ break;
+ case 'install':
+ case 'upgrade':
+ case 'upgrade-all':
+ if (is_array($data) && isset($data['release_warnings'])) {
+ $this->_displayLine('');
+ $this->_startTable(array(
+ 'border' => false,
+ 'caption' => 'Release Warnings'
+ ));
+ $this->_tableRow(array($data['release_warnings']), null, array(1 => array('wrap' => 55)));
+ $this->_endTable();
+ $this->_displayLine('');
+ }
+
+ $this->_displayLine(is_array($data) ? $data['data'] : $data);
+ break;
+ case 'search':
+ $this->_startTable($data);
+ if (isset($data['headline']) && is_array($data['headline'])) {
+ $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55)));
+ }
+
+ $packages = array();
+ foreach($data['data'] as $category) {
+ foreach($category as $name => $pkg) {
+ $packages[$pkg[0]] = $pkg;
+ }
+ }
+
+ $p = array_keys($packages);
+ natcasesort($p);
+ foreach ($p as $name) {
+ $this->_tableRow($packages[$name], null, array(1 => array('wrap' => 55)));
+ }
+
+ $this->_endTable();
+ break;
+ case 'list-all':
+ if (!isset($data['data'])) {
+ $this->_displayLine('No packages in channel');
+ break;
+ }
+
+ $this->_startTable($data);
+ if (isset($data['headline']) && is_array($data['headline'])) {
+ $this->_tableRow($data['headline'], array('bold' => true), array(1 => array('wrap' => 55)));
+ }
+
+ $packages = array();
+ foreach($data['data'] as $category) {
+ foreach($category as $name => $pkg) {
+ $packages[$pkg[0]] = $pkg;
+ }
+ }
+
+ $p = array_keys($packages);
+ natcasesort($p);
+ foreach ($p as $name) {
+ $pkg = $packages[$name];
+ unset($pkg[4], $pkg[5]);
+ $this->_tableRow($pkg, null, array(1 => array('wrap' => 55)));
+ }
+
+ $this->_endTable();
+ break;
+ case 'config-show':
+ $data['border'] = false;
+ $opts = array(
+ 0 => array('wrap' => 30),
+ 1 => array('wrap' => 20),
+ 2 => array('wrap' => 35)
+ );
+
+ $this->_startTable($data);
+ if (isset($data['headline']) && is_array($data['headline'])) {
+ $this->_tableRow($data['headline'], array('bold' => true), $opts);
+ }
+
+ foreach ($data['data'] as $group) {
+ foreach ($group as $value) {
+ if ($value[2] == '') {
+ $value[2] = "<not set>";
+ }
+
+ $this->_tableRow($value, null, $opts);
+ }
+ }
+
+ $this->_endTable();
+ break;
+ case 'remote-info':
+ $d = $data;
+ $data = array(
+ 'caption' => 'Package details:',
+ 'border' => false,
+ 'data' => array(
+ array("Latest", $data['stable']),
+ array("Installed", $data['installed']),
+ array("Package", $data['name']),
+ array("License", $data['license']),
+ array("Category", $data['category']),
+ array("Summary", $data['summary']),
+ array("Description", $data['description']),
+ ),
+ );
+
+ if (isset($d['deprecated']) && $d['deprecated']) {
+ $conf = &PEAR_Config::singleton();
+ $reg = $conf->getRegistry();
+ $name = $reg->parsedPackageNameToString($d['deprecated'], true);
+ $data['data'][] = array('Deprecated! use', $name);
+ }
+ default: {
+ if (is_array($data)) {
+ $this->_startTable($data);
+ $count = count($data['data'][0]);
+ if ($count == 2) {
+ $opts = array(0 => array('wrap' => 25),
+ 1 => array('wrap' => 48)
+ );
+ } elseif ($count == 3) {
+ $opts = array(0 => array('wrap' => 30),
+ 1 => array('wrap' => 20),
+ 2 => array('wrap' => 35)
+ );
+ } else {
+ $opts = null;
+ }
+ if (isset($data['headline']) && is_array($data['headline'])) {
+ $this->_tableRow($data['headline'],
+ array('bold' => true),
+ $opts);
+ }
+
+ if (is_array($data['data'])) {
+ foreach($data['data'] as $row) {
+ $this->_tableRow($row, null, $opts);
+ }
+ } else {
+ $this->_tableRow(array($data['data']), null, $opts);
+ }
+ $this->_endTable();
+ } else {
+ $this->_displayLine($data);
+ }
+ }
+ }
+ }
+
+ function log($text, $append_crlf = true)
+ {
+ if ($append_crlf) {
+ return $this->_displayLine($text);
+ }
+
+ return $this->_display($text);
+ }
+
+ function bold($text)
+ {
+ if (empty($this->term['bold'])) {
+ return strtoupper($text);
+ }
+
+ return $this->term['bold'] . $text . $this->term['normal'];
+ }
+
+ function _displayHeading($title)
+ {
+ print $this->lp.$this->bold($title)."\n";
+ print $this->lp.str_repeat("=", strlen($title))."\n";
+ }
+
+ function _startTable($params = array())
+ {
+ $params['table_data'] = array();
+ $params['widest'] = array(); // indexed by column
+ $params['highest'] = array(); // indexed by row
+ $params['ncols'] = 0;
+ $this->params = $params;
+ }
+
+ function _tableRow($columns, $rowparams = array(), $colparams = array())
+ {
+ $highest = 1;
+ for ($i = 0; $i < count($columns); $i++) {
+ $col = &$columns[$i];
+ if (isset($colparams[$i]) && !empty($colparams[$i]['wrap'])) {
+ $col = wordwrap($col, $colparams[$i]['wrap']);
+ }
+
+ if (strpos($col, "\n") !== false) {
+ $multiline = explode("\n", $col);
+ $w = 0;
+ foreach ($multiline as $n => $line) {
+ $len = strlen($line);
+ if ($len > $w) {
+ $w = $len;
+ }
+ }
+ $lines = count($multiline);
+ } else {
+ $w = strlen($col);
+ }
+
+ if (isset($this->params['widest'][$i])) {
+ if ($w > $this->params['widest'][$i]) {
+ $this->params['widest'][$i] = $w;
+ }
+ } else {
+ $this->params['widest'][$i] = $w;
+ }
+
+ $tmp = count_chars($columns[$i], 1);
+ // handle unix, mac and windows formats
+ $lines = (isset($tmp[10]) ? $tmp[10] : (isset($tmp[13]) ? $tmp[13] : 0)) + 1;
+ if ($lines > $highest) {
+ $highest = $lines;
+ }
+ }
+
+ if (count($columns) > $this->params['ncols']) {
+ $this->params['ncols'] = count($columns);
+ }
+
+ $new_row = array(
+ 'data' => $columns,
+ 'height' => $highest,
+ 'rowparams' => $rowparams,
+ 'colparams' => $colparams,
+ );
+ $this->params['table_data'][] = $new_row;
+ }
+
+ function _endTable()
+ {
+ extract($this->params);
+ if (!empty($caption)) {
+ $this->_displayHeading($caption);
+ }
+
+ if (count($table_data) === 0) {
+ return;
+ }
+
+ if (!isset($width)) {
+ $width = $widest;
+ } else {
+ for ($i = 0; $i < $ncols; $i++) {
+ if (!isset($width[$i])) {
+ $width[$i] = $widest[$i];
+ }
+ }
+ }
+
+ $border = false;
+ if (empty($border)) {
+ $cellstart = '';
+ $cellend = ' ';
+ $rowend = '';
+ $padrowend = false;
+ $borderline = '';
+ } else {
+ $cellstart = '| ';
+ $cellend = ' ';
+ $rowend = '|';
+ $padrowend = true;
+ $borderline = '+';
+ foreach ($width as $w) {
+ $borderline .= str_repeat('-', $w + strlen($cellstart) + strlen($cellend) - 1);
+ $borderline .= '+';
+ }
+ }
+
+ if ($borderline) {
+ $this->_displayLine($borderline);
+ }
+
+ for ($i = 0; $i < count($table_data); $i++) {
+ extract($table_data[$i]);
+ if (!is_array($rowparams)) {
+ $rowparams = array();
+ }
+
+ if (!is_array($colparams)) {
+ $colparams = array();
+ }
+
+ $rowlines = array();
+ if ($height > 1) {
+ for ($c = 0; $c < count($data); $c++) {
+ $rowlines[$c] = preg_split('/(\r?\n|\r)/', $data[$c]);
+ if (count($rowlines[$c]) < $height) {
+ $rowlines[$c] = array_pad($rowlines[$c], $height, '');
+ }
+ }
+ } else {
+ for ($c = 0; $c < count($data); $c++) {
+ $rowlines[$c] = array($data[$c]);
+ }
+ }
+
+ for ($r = 0; $r < $height; $r++) {
+ $rowtext = '';
+ for ($c = 0; $c < count($data); $c++) {
+ if (isset($colparams[$c])) {
+ $attribs = array_merge($rowparams, $colparams);
+ } else {
+ $attribs = $rowparams;
+ }
+
+ $w = isset($width[$c]) ? $width[$c] : 0;
+ //$cell = $data[$c];
+ $cell = $rowlines[$c][$r];
+ $l = strlen($cell);
+ if ($l > $w) {
+ $cell = substr($cell, 0, $w);
+ }
+
+ if (isset($attribs['bold'])) {
+ $cell = $this->bold($cell);
+ }
+
+ if ($l < $w) {
+ // not using str_pad here because we may
+ // add bold escape characters to $cell
+ $cell .= str_repeat(' ', $w - $l);
+ }
+
+ $rowtext .= $cellstart . $cell . $cellend;
+ }
+
+ if (!$border) {
+ $rowtext = rtrim($rowtext);
+ }
+
+ $rowtext .= $rowend;
+ $this->_displayLine($rowtext);
+ }
+ }
+
+ if ($borderline) {
+ $this->_displayLine($borderline);
+ }
+ }
+
+ function _displayLine($text)
+ {
+ print "$this->lp$text\n";
+ }
+
+ function _display($text)
+ {
+ print $text;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer.php b/includes/pear/PEAR/Installer.php
new file mode 100644
index 0000000..867b9ed
--- /dev/null
+++ b/includes/pear/PEAR/Installer.php
@@ -0,0 +1,1753 @@
+<?php
+/**
+ * PEAR_Installer
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V. Cox <cox@idecnet.com>
+ * @author Martin Jansen <mj@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**
+ * Used for installation groups in package.xml 2.0 and platform exceptions
+ */
+require_once 'OS/Guess.php';
+require_once 'PEAR/Downloader.php';
+
+define('PEAR_INSTALLER_NOBINARY', -240);
+/**
+ * Administration class used to install PEAR packages and maintain the
+ * installed package database.
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V. Cox <cox@idecnet.com>
+ * @author Martin Jansen <mj@php.net>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ */
+class PEAR_Installer extends PEAR_Downloader
+{
+ /** name of the package directory, for example Foo-1.0
+ * @var string
+ */
+ var $pkgdir;
+
+ /** directory where PHP code files go
+ * @var string
+ */
+ var $phpdir;
+
+ /** directory where PHP extension files go
+ * @var string
+ */
+ var $extdir;
+
+ /** directory where documentation goes
+ * @var string
+ */
+ var $docdir;
+
+ /** installation root directory (ala PHP's INSTALL_ROOT or
+ * automake's DESTDIR
+ * @var string
+ */
+ var $installroot = '';
+
+ /** debug level
+ * @var int
+ */
+ var $debug = 1;
+
+ /** temporary directory
+ * @var string
+ */
+ var $tmpdir;
+
+ /**
+ * PEAR_Registry object used by the installer
+ * @var PEAR_Registry
+ */
+ var $registry;
+
+ /**
+ * array of PEAR_Downloader_Packages
+ * @var array
+ */
+ var $_downloadedPackages;
+
+ /** List of file transactions queued for an install/upgrade/uninstall.
+ *
+ * Format:
+ * array(
+ * 0 => array("rename => array("from-file", "to-file")),
+ * 1 => array("delete" => array("file-to-delete")),
+ * ...
+ * )
+ *
+ * @var array
+ */
+ var $file_operations = array();
+
+ /**
+ * PEAR_Installer constructor.
+ *
+ * @param object $ui user interface object (instance of PEAR_Frontend_*)
+ *
+ * @access public
+ */
+ function PEAR_Installer(&$ui)
+ {
+ parent::PEAR_Common();
+ $this->setFrontendObject($ui);
+ $this->debug = $this->config->get('verbose');
+ }
+
+ function setOptions($options)
+ {
+ $this->_options = $options;
+ }
+
+ function setConfig(&$config)
+ {
+ $this->config = &$config;
+ $this->_registry = &$config->getRegistry();
+ }
+
+ function _removeBackups($files)
+ {
+ foreach ($files as $path) {
+ $this->addFileOperation('removebackup', array($path));
+ }
+ }
+
+ /**
+ * Delete a package's installed files, does not remove empty directories.
+ *
+ * @param string package name
+ * @param string channel name
+ * @param bool if true, then files are backed up first
+ * @return bool TRUE on success, or a PEAR error on failure
+ * @access protected
+ */
+ function _deletePackageFiles($package, $channel = false, $backup = false)
+ {
+ if (!$channel) {
+ $channel = 'pear.php.net';
+ }
+
+ if (!strlen($package)) {
+ return $this->raiseError("No package to uninstall given");
+ }
+
+ if (strtolower($package) == 'pear' && $channel == 'pear.php.net') {
+ // to avoid race conditions, include all possible needed files
+ require_once 'PEAR/Task/Common.php';
+ require_once 'PEAR/Task/Replace.php';
+ require_once 'PEAR/Task/Unixeol.php';
+ require_once 'PEAR/Task/Windowseol.php';
+ require_once 'PEAR/PackageFile/v1.php';
+ require_once 'PEAR/PackageFile/v2.php';
+ require_once 'PEAR/PackageFile/Generator/v1.php';
+ require_once 'PEAR/PackageFile/Generator/v2.php';
+ }
+
+ $filelist = $this->_registry->packageInfo($package, 'filelist', $channel);
+ if ($filelist == null) {
+ return $this->raiseError("$channel/$package not installed");
+ }
+
+ $ret = array();
+ foreach ($filelist as $file => $props) {
+ if (empty($props['installed_as'])) {
+ continue;
+ }
+
+ $path = $props['installed_as'];
+ if ($backup) {
+ $this->addFileOperation('backup', array($path));
+ $ret[] = $path;
+ }
+
+ $this->addFileOperation('delete', array($path));
+ }
+
+ if ($backup) {
+ return $ret;
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string filename
+ * @param array attributes from <file> tag in package.xml
+ * @param string path to install the file in
+ * @param array options from command-line
+ * @access private
+ */
+ function _installFile($file, $atts, $tmp_path, $options)
+ {
+ // return if this file is meant for another platform
+ static $os;
+ if (!isset($this->_registry)) {
+ $this->_registry = &$this->config->getRegistry();
+ }
+
+ if (isset($atts['platform'])) {
+ if (empty($os)) {
+ $os = new OS_Guess;
+ }
+
+ if (strlen($atts['platform']) && $atts['platform']{0} == '!') {
+ $negate = true;
+ $platform = substr($atts['platform'], 1);
+ } else {
+ $negate = false;
+ $platform = $atts['platform'];
+ }
+
+ if ((bool) $os->matchSignature($platform) === $negate) {
+ $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")");
+ return PEAR_INSTALLER_SKIPPED;
+ }
+ }
+
+ $channel = $this->pkginfo->getChannel();
+ // assemble the destination paths
+ switch ($atts['role']) {
+ case 'src':
+ case 'extsrc':
+ $this->source_files++;
+ return;
+ case 'doc':
+ case 'data':
+ case 'test':
+ $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) .
+ DIRECTORY_SEPARATOR . $this->pkginfo->getPackage();
+ unset($atts['baseinstalldir']);
+ break;
+ case 'ext':
+ case 'php':
+ $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel);
+ break;
+ case 'script':
+ $dest_dir = $this->config->get('bin_dir', null, $channel);
+ break;
+ default:
+ return $this->raiseError("Invalid role `$atts[role]' for file $file");
+ }
+
+ $save_destdir = $dest_dir;
+ if (!empty($atts['baseinstalldir'])) {
+ $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
+ }
+
+ if (dirname($file) != '.' && empty($atts['install-as'])) {
+ $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
+ }
+
+ if (empty($atts['install-as'])) {
+ $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
+ } else {
+ $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
+ }
+ $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
+
+ // Clean up the DIRECTORY_SEPARATOR mess
+ $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
+ list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
+ array(DIRECTORY_SEPARATOR,
+ DIRECTORY_SEPARATOR,
+ DIRECTORY_SEPARATOR),
+ array($dest_file, $orig_file));
+ $final_dest_file = $installed_as = $dest_file;
+ if (isset($this->_options['packagingroot'])) {
+ $installedas_dest_dir = dirname($final_dest_file);
+ $installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
+ $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']);
+ } else {
+ $installedas_dest_dir = dirname($final_dest_file);
+ $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
+ }
+
+ $dest_dir = dirname($final_dest_file);
+ $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
+ if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) {
+ return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
+ }
+
+ if (
+ empty($this->_options['register-only']) &&
+ (!file_exists($dest_dir) || !is_dir($dest_dir))
+ ) {
+ if (!$this->mkDirHier($dest_dir)) {
+ return $this->raiseError("failed to mkdir $dest_dir",
+ PEAR_INSTALLER_FAILED);
+ }
+ $this->log(3, "+ mkdir $dest_dir");
+ }
+
+ // pretty much nothing happens if we are only registering the install
+ if (empty($this->_options['register-only'])) {
+ if (empty($atts['replacements'])) {
+ if (!file_exists($orig_file)) {
+ return $this->raiseError("file $orig_file does not exist",
+ PEAR_INSTALLER_FAILED);
+ }
+
+ if (!@copy($orig_file, $dest_file)) {
+ return $this->raiseError("failed to write $dest_file: $php_errormsg",
+ PEAR_INSTALLER_FAILED);
+ }
+
+ $this->log(3, "+ cp $orig_file $dest_file");
+ if (isset($atts['md5sum'])) {
+ $md5sum = md5_file($dest_file);
+ }
+ } else {
+ // file with replacements
+ if (!file_exists($orig_file)) {
+ return $this->raiseError("file does not exist",
+ PEAR_INSTALLER_FAILED);
+ }
+
+ $contents = file_get_contents($orig_file);
+ if ($contents === false) {
+ $contents = '';
+ }
+
+ if (isset($atts['md5sum'])) {
+ $md5sum = md5($contents);
+ }
+
+ $subst_from = $subst_to = array();
+ foreach ($atts['replacements'] as $a) {
+ $to = '';
+ if ($a['type'] == 'php-const') {
+ if (preg_match('/^[a-z0-9_]+\\z/i', $a['to'])) {
+ eval("\$to = $a[to];");
+ } else {
+ if (!isset($options['soft'])) {
+ $this->log(0, "invalid php-const replacement: $a[to]");
+ }
+ continue;
+ }
+ } elseif ($a['type'] == 'pear-config') {
+ if ($a['to'] == 'master_server') {
+ $chan = $this->_registry->getChannel($channel);
+ if (!PEAR::isError($chan)) {
+ $to = $chan->getServer();
+ } else {
+ $to = $this->config->get($a['to'], null, $channel);
+ }
+ } else {
+ $to = $this->config->get($a['to'], null, $channel);
+ }
+
+ if (is_null($to)) {
+ if (!isset($options['soft'])) {
+ $this->log(0, "invalid pear-config replacement: $a[to]");
+ }
+
+ continue;
+ }
+ } elseif ($a['type'] == 'package-info') {
+ if ($t = $this->pkginfo->packageInfo($a['to'])) {
+ $to = $t;
+ } else {
+ if (!isset($options['soft'])) {
+ $this->log(0, "invalid package-info replacement: $a[to]");
+ }
+
+ continue;
+ }
+ }
+
+ if (!is_null($to)) {
+ $subst_from[] = $a['from'];
+ $subst_to[] = $to;
+ }
+ }
+
+ $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file");
+ if (sizeof($subst_from)) {
+ $contents = str_replace($subst_from, $subst_to, $contents);
+ }
+
+ $wp = @fopen($dest_file, "wb");
+ if (!is_resource($wp)) {
+ return $this->raiseError("failed to create $dest_file: $php_errormsg",
+ PEAR_INSTALLER_FAILED);
+ }
+
+ if (@fwrite($wp, $contents) === false) {
+ return $this->raiseError("failed writing to $dest_file: $php_errormsg",
+ PEAR_INSTALLER_FAILED);
+ }
+
+ fclose($wp);
+ }
+
+ // check the md5
+ if (isset($md5sum)) {
+ if (strtolower($md5sum) === strtolower($atts['md5sum'])) {
+ $this->log(2, "md5sum ok: $final_dest_file");
+ } else {
+ if (empty($options['force'])) {
+ // delete the file
+ if (file_exists($dest_file)) {
+ unlink($dest_file);
+ }
+
+ if (!isset($options['ignore-errors'])) {
+ return $this->raiseError("bad md5sum for file $final_dest_file",
+ PEAR_INSTALLER_FAILED);
+ }
+
+ if (!isset($options['soft'])) {
+ $this->log(0, "warning : bad md5sum for file $final_dest_file");
+ }
+ } else {
+ if (!isset($options['soft'])) {
+ $this->log(0, "warning : bad md5sum for file $final_dest_file");
+ }
+ }
+ }
+ }
+
+ // set file permissions
+ if (!OS_WINDOWS) {
+ $umask = $this->config->get('umask');
+ if ($atts['role'] == 'script') {
+ $mode = 0777 & ~(int)octdec($umask);
+ $this->log(3, "+ chmod +x $dest_file");
+ } else {
+ $mode = 0666 & ~(int)octdec($umask);
+ }
+
+ if ($atts['role'] != 'src') {
+ $this->addFileOperation("chmod", array($mode, $dest_file));
+ if (!@chmod($dest_file, $mode)) {
+ if (!isset($options['soft'])) {
+ $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
+ }
+ }
+ }
+ }
+
+ if ($atts['role'] == 'src') {
+ rename($dest_file, $final_dest_file);
+ $this->log(2, "renamed source file $dest_file to $final_dest_file");
+ } else {
+ $this->addFileOperation("rename", array($dest_file, $final_dest_file,
+ $atts['role'] == 'ext'));
+ }
+ }
+
+ // Store the full path where the file was installed for easy unistall
+ if ($atts['role'] != 'script') {
+ $loc = $this->config->get($atts['role'] . '_dir');
+ } else {
+ $loc = $this->config->get('bin_dir');
+ }
+
+ if ($atts['role'] != 'src') {
+ $this->addFileOperation("installed_as", array($file, $installed_as,
+ $loc,
+ dirname(substr($installedas_dest_file, strlen($loc)))));
+ }
+
+ //$this->log(2, "installed: $dest_file");
+ return PEAR_INSTALLER_OK;
+ }
+
+ /**
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * @param string filename
+ * @param array attributes from <file> tag in package.xml
+ * @param string path to install the file in
+ * @param array options from command-line
+ * @access private
+ */
+ function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options)
+ {
+ $atts = $real_atts;
+ if (!isset($this->_registry)) {
+ $this->_registry = &$this->config->getRegistry();
+ }
+
+ $channel = $pkg->getChannel();
+ // assemble the destination paths
+ $roles = PEAR_Installer_Role::getValidRoles($pkg->getPackageType());
+ if (!in_array($atts['attribs']['role'], $roles)) {
+ return $this->raiseError('Invalid role `' . $atts['attribs']['role'] .
+ "' for file $file");
+ }
+
+ $role = &PEAR_Installer_Role::factory($pkg, $atts['attribs']['role'], $this->config);
+ $err = $role->setup($this, $pkg, $atts['attribs'], $file);
+ if (PEAR::isError($err)) {
+ return $err;
+ }
+
+ if (!$role->isInstallable()) {
+ return;
+ }
+
+ $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path);
+ if (PEAR::isError($info)) {
+ return $info;
+ }
+
+ list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info;
+ if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) {
+ return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
+ }
+
+ $final_dest_file = $installed_as = $dest_file;
+ if (isset($this->_options['packagingroot'])) {
+ $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']);
+ }
+
+ $dest_dir = dirname($final_dest_file);
+ $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
+
+ if (empty($this->_options['register-only'])) {
+ if (!file_exists($dest_dir) || !is_dir($dest_dir)) {
+ if (!$this->mkDirHier($dest_dir)) {
+ return $this->raiseError("failed to mkdir $dest_dir",
+ PEAR_INSTALLER_FAILED);
+ }
+ $this->log(3, "+ mkdir $dest_dir");
+ }
+ }
+
+ $attribs = $atts['attribs'];
+ unset($atts['attribs']);
+ // pretty much nothing happens if we are only registering the install
+ if (empty($this->_options['register-only'])) {
+ if (!count($atts)) { // no tasks
+ if (!file_exists($orig_file)) {
+ return $this->raiseError("file $orig_file does not exist",
+ PEAR_INSTALLER_FAILED);
+ }
+
+ if (!@copy($orig_file, $dest_file)) {
+ return $this->raiseError("failed to write $dest_file: $php_errormsg",
+ PEAR_INSTALLER_FAILED);
+ }
+
+ $this->log(3, "+ cp $orig_file $dest_file");
+ if (isset($attribs['md5sum'])) {
+ $md5sum = md5_file($dest_file);
+ }
+ } else { // file with tasks
+ if (!file_exists($orig_file)) {
+ return $this->raiseError("file $orig_file does not exist",
+ PEAR_INSTALLER_FAILED);
+ }
+
+ $contents = file_get_contents($orig_file);
+ if ($contents === false) {
+ $contents = '';
+ }
+
+ if (isset($attribs['md5sum'])) {
+ $md5sum = md5($contents);
+ }
+
+ foreach ($atts as $tag => $raw) {
+ $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag);
+ $task = "PEAR_Task_$tag";
+ $task = &new $task($this->config, $this, PEAR_TASK_INSTALL);
+ if (!$task->isScript()) { // scripts are only handled after installation
+ $task->init($raw, $attribs, $pkg->getLastInstalledVersion());
+ $res = $task->startSession($pkg, $contents, $final_dest_file);
+ if ($res === false) {
+ continue; // skip this file
+ }
+
+ if (PEAR::isError($res)) {
+ return $res;
+ }
+
+ $contents = $res; // save changes
+ }
+
+ $wp = @fopen($dest_file, "wb");
+ if (!is_resource($wp)) {
+ return $this->raiseError("failed to create $dest_file: $php_errormsg",
+ PEAR_INSTALLER_FAILED);
+ }
+
+ if (fwrite($wp, $contents) === false) {
+ return $this->raiseError("failed writing to $dest_file: $php_errormsg",
+ PEAR_INSTALLER_FAILED);
+ }
+
+ fclose($wp);
+ }
+ }
+
+ // check the md5
+ if (isset($md5sum)) {
+ // Make sure the original md5 sum matches with expected
+ if (strtolower($md5sum) === strtolower($attribs['md5sum'])) {
+ $this->log(2, "md5sum ok: $final_dest_file");
+
+ if (isset($contents)) {
+ // set md5 sum based on $content in case any tasks were run.
+ $real_atts['attribs']['md5sum'] = md5($contents);
+ }
+ } else {
+ if (empty($options['force'])) {
+ // delete the file
+ if (file_exists($dest_file)) {
+ unlink($dest_file);
+ }
+
+ if (!isset($options['ignore-errors'])) {
+ return $this->raiseError("bad md5sum for file $final_dest_file",
+ PEAR_INSTALLER_FAILED);
+ }
+
+ if (!isset($options['soft'])) {
+ $this->log(0, "warning : bad md5sum for file $final_dest_file");
+ }
+ } else {
+ if (!isset($options['soft'])) {
+ $this->log(0, "warning : bad md5sum for file $final_dest_file");
+ }
+ }
+ }
+ } else {
+ $real_atts['attribs']['md5sum'] = md5_file($dest_file);
+ }
+
+ //set file permissions
+ if (!OS_WINDOWS) {
+ if ($role->isExecutable()) {
+ $mode = 0777 & ~(int)octdec($this->config->get('umask'));
+ $this->log(3, "+ chmod +x $dest_file");
+ } else {
+ $mode = 0666 & ~(int)octdec($this->config->get('umask'));
+ }
+
+ if ($attribs['role'] != 'src') {
+ $this->addFileOperation("chmod", array($mode, $dest_file));
+ if (!@chmod($dest_file, $mode)) {
+ if (!isset($options['soft'])) {
+ $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
+ }
+ }
+ }
+ }
+
+ if ($attribs['role'] == 'src') {
+ rename($dest_file, $final_dest_file);
+ $this->log(2, "renamed source file $dest_file to $final_dest_file");
+ } else {
+ $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension()));
+ }
+ }
+
+ // Store the full path where the file was installed for easy uninstall
+ if ($attribs['role'] != 'src') {
+ $loc = $this->config->get($role->getLocationConfig(), null, $channel);
+ $this->addFileOperation('installed_as', array($file, $installed_as,
+ $loc,
+ dirname(substr($installed_as, strlen($loc)))));
+ }
+
+ //$this->log(2, "installed: $dest_file");
+ return PEAR_INSTALLER_OK;
+ }
+
+ /**
+ * Add a file operation to the current file transaction.
+ *
+ * @see startFileTransaction()
+ * @param string $type This can be one of:
+ * - rename: rename a file ($data has 3 values)
+ * - backup: backup an existing file ($data has 1 value)
+ * - removebackup: clean up backups created during install ($data has 1 value)
+ * - chmod: change permissions on a file ($data has 2 values)
+ * - delete: delete a file ($data has 1 value)
+ * - rmdir: delete a directory if empty ($data has 1 value)
+ * - installed_as: mark a file as installed ($data has 4 values).
+ * @param array $data For all file operations, this array must contain the
+ * full path to the file or directory that is being operated on. For
+ * the rename command, the first parameter must be the file to rename,
+ * the second its new name, the third whether this is a PHP extension.
+ *
+ * The installed_as operation contains 4 elements in this order:
+ * 1. Filename as listed in the filelist element from package.xml
+ * 2. Full path to the installed file
+ * 3. Full path from the php_dir configuration variable used in this
+ * installation
+ * 4. Relative path from the php_dir that this file is installed in
+ */
+ function addFileOperation($type, $data)
+ {
+ if (!is_array($data)) {
+ return $this->raiseError('Internal Error: $data in addFileOperation'
+ . ' must be an array, was ' . gettype($data));
+ }
+
+ if ($type == 'chmod') {
+ $octmode = decoct($data[0]);
+ $this->log(3, "adding to transaction: $type $octmode $data[1]");
+ } else {
+ $this->log(3, "adding to transaction: $type " . implode(" ", $data));
+ }
+ $this->file_operations[] = array($type, $data);
+ }
+
+ function startFileTransaction($rollback_in_case = false)
+ {
+ if (count($this->file_operations) && $rollback_in_case) {
+ $this->rollbackFileTransaction();
+ }
+ $this->file_operations = array();
+ }
+
+ function commitFileTransaction()
+ {
+ //first, check permissions and such manually
+ $errors = array();
+ foreach ($this->file_operations as $key => $tr) {
+ list($type, $data) = $tr;
+ switch ($type) {
+ case 'rename':
+ if (!file_exists($data[0])) {
+ $errors[] = "cannot rename file $data[0], doesn't exist";
+ }
+
+ // check that dest dir. is writable
+ if (!is_writable(dirname($data[1]))) {
+ $errors[] = "permission denied ($type): $data[1]";
+ }
+ break;
+ case 'chmod':
+ // check that file is writable
+ if (!is_writable($data[1])) {
+ $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
+ }
+ break;
+ case 'delete':
+ if (!file_exists($data[0])) {
+ $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
+ }
+
+ // check that directory is writable
+ if (file_exists($data[0])) {
+ if (!is_writable(dirname($data[0]))) {
+ $errors[] = "permission denied ($type): $data[0]";
+ } else {
+ // make sure the file to be deleted can be opened for writing
+ $fp = false;
+ if (!is_dir($data[0]) &&
+ (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) {
+ $errors[] = "permission denied ($type): $data[0]";
+ } elseif ($fp) {
+ fclose($fp);
+ }
+ }
+
+ /* Verify we are not deleting a file owned by another package
+ * This can happen when a file moves from package A to B in
+ * an upgrade ala http://pear.php.net/17986
+ */
+ $info = array(
+ 'package' => strtolower($this->pkginfo->getName()),
+ 'channel' => strtolower($this->pkginfo->getChannel()),
+ );
+ $result = $this->_registry->checkFileMap($data[0], $info, '1.1');
+ if (is_array($result)) {
+ $res = array_diff($result, $info);
+ if (!empty($res)) {
+ $new = $this->_registry->getPackage($result[1], $result[0]);
+ $this->file_operations[$key] = false;
+ $pkginfoName = $this->pkginfo->getName();
+ $newChannel = $new->getChannel();
+ $newPackage = $new->getName();
+ $this->log(3, "file $data[0] was scheduled for removal from $pkginfoName but is owned by $newChannel/$newPackage, removal has been cancelled.");
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ $n = count($this->file_operations);
+ $this->log(2, "about to commit $n file operations for " . $this->pkginfo->getName());
+
+ $m = count($errors);
+ if ($m > 0) {
+ foreach ($errors as $error) {
+ if (!isset($this->_options['soft'])) {
+ $this->log(1, $error);
+ }
+ }
+
+ if (!isset($this->_options['ignore-errors'])) {
+ return false;
+ }
+ }
+
+ $this->_dirtree = array();
+ // really commit the transaction
+ foreach ($this->file_operations as $i => $tr) {
+ if (!$tr) {
+ // support removal of non-existing backups
+ continue;
+ }
+
+ list($type, $data) = $tr;
+ switch ($type) {
+ case 'backup':
+ if (!file_exists($data[0])) {
+ $this->file_operations[$i] = false;
+ break;
+ }
+
+ if (!@copy($data[0], $data[0] . '.bak')) {
+ $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] .
+ '.bak ' . $php_errormsg);
+ return false;
+ }
+ $this->log(3, "+ backup $data[0] to $data[0].bak");
+ break;
+ case 'removebackup':
+ if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
+ unlink($data[0] . '.bak');
+ $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
+ }
+ break;
+ case 'rename':
+ $test = file_exists($data[1]) ? @unlink($data[1]) : null;
+ if (!$test && file_exists($data[1])) {
+ if ($data[2]) {
+ $extra = ', this extension must be installed manually. Rename to "' .
+ basename($data[1]) . '"';
+ } else {
+ $extra = '';
+ }
+
+ if (!isset($this->_options['soft'])) {
+ $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' .
+ $data[0] . $extra);
+ }
+
+ if (!isset($this->_options['ignore-errors'])) {
+ return false;
+ }
+ }
+
+ // permissions issues with rename - copy() is far superior
+ $perms = @fileperms($data[0]);
+ if (!@copy($data[0], $data[1])) {
+ $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] .
+ ' ' . $php_errormsg);
+ return false;
+ }
+
+ // copy over permissions, otherwise they are lost
+ @chmod($data[1], $perms);
+ @unlink($data[0]);
+ $this->log(3, "+ mv $data[0] $data[1]");
+ break;
+ case 'chmod':
+ if (!@chmod($data[1], $data[0])) {
+ $this->log(1, 'Could not chmod ' . $data[1] . ' to ' .
+ decoct($data[0]) . ' ' . $php_errormsg);
+ return false;
+ }
+
+ $octmode = decoct($data[0]);
+ $this->log(3, "+ chmod $octmode $data[1]");
+ break;
+ case 'delete':
+ if (file_exists($data[0])) {
+ if (!@unlink($data[0])) {
+ $this->log(1, 'Could not delete ' . $data[0] . ' ' .
+ $php_errormsg);
+ return false;
+ }
+ $this->log(3, "+ rm $data[0]");
+ }
+ break;
+ case 'rmdir':
+ if (file_exists($data[0])) {
+ do {
+ $testme = opendir($data[0]);
+ while (false !== ($entry = readdir($testme))) {
+ if ($entry == '.' || $entry == '..') {
+ continue;
+ }
+ closedir($testme);
+ break 2; // this directory is not empty and can't be
+ // deleted
+ }
+
+ closedir($testme);
+ if (!@rmdir($data[0])) {
+ $this->log(1, 'Could not rmdir ' . $data[0] . ' ' .
+ $php_errormsg);
+ return false;
+ }
+ $this->log(3, "+ rmdir $data[0]");
+ } while (false);
+ }
+ break;
+ case 'installed_as':
+ $this->pkginfo->setInstalledAs($data[0], $data[1]);
+ if (!isset($this->_dirtree[dirname($data[1])])) {
+ $this->_dirtree[dirname($data[1])] = true;
+ $this->pkginfo->setDirtree(dirname($data[1]));
+
+ while(!empty($data[3]) && dirname($data[3]) != $data[3] &&
+ $data[3] != '/' && $data[3] != '\\') {
+ $this->pkginfo->setDirtree($pp =
+ $this->_prependPath($data[3], $data[2]));
+ $this->_dirtree[$pp] = true;
+ $data[3] = dirname($data[3]);
+ }
+ }
+ break;
+ }
+ }
+
+ $this->log(2, "successfully committed $n file operations");
+ $this->file_operations = array();
+ return true;
+ }
+
+ function rollbackFileTransaction()
+ {
+ $n = count($this->file_operations);
+ $this->log(2, "rolling back $n file operations");
+ foreach ($this->file_operations as $tr) {
+ list($type, $data) = $tr;
+ switch ($type) {
+ case 'backup':
+ if (file_exists($data[0] . '.bak')) {
+ if (file_exists($data[0] && is_writable($data[0]))) {
+ unlink($data[0]);
+ }
+ @copy($data[0] . '.bak', $data[0]);
+ $this->log(3, "+ restore $data[0] from $data[0].bak");
+ }
+ break;
+ case 'removebackup':
+ if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
+ unlink($data[0] . '.bak');
+ $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
+ }
+ break;
+ case 'rename':
+ @unlink($data[0]);
+ $this->log(3, "+ rm $data[0]");
+ break;
+ case 'mkdir':
+ @rmdir($data[0]);
+ $this->log(3, "+ rmdir $data[0]");
+ break;
+ case 'chmod':
+ break;
+ case 'delete':
+ break;
+ case 'installed_as':
+ $this->pkginfo->setInstalledAs($data[0], false);
+ break;
+ }
+ }
+ $this->pkginfo->resetDirtree();
+ $this->file_operations = array();
+ }
+
+ function mkDirHier($dir)
+ {
+ $this->addFileOperation('mkdir', array($dir));
+ return parent::mkDirHier($dir);
+ }
+
+ /**
+ * Download any files and their dependencies, if necessary
+ *
+ * @param array a mixed list of package names, local files, or package.xml
+ * @param PEAR_Config
+ * @param array options from the command line
+ * @param array this is the array that will be populated with packages to
+ * install. Format of each entry:
+ *
+ * <code>
+ * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
+ * 'info' => array() // parsed package.xml
+ * );
+ * </code>
+ * @param array this will be populated with any error messages
+ * @param false private recursion variable
+ * @param false private recursion variable
+ * @param false private recursion variable
+ * @deprecated in favor of PEAR_Downloader
+ */
+ function download($packages, $options, &$config, &$installpackages,
+ &$errors, $installed = false, $willinstall = false, $state = false)
+ {
+ // trickiness: initialize here
+ parent::PEAR_Downloader($this->ui, $options, $config);
+ $ret = parent::download($packages);
+ $errors = $this->getErrorMsgs();
+ $installpackages = $this->getDownloadedPackages();
+ trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " .
+ "in favor of PEAR_Downloader class", E_USER_WARNING);
+ return $ret;
+ }
+
+ function _parsePackageXml(&$descfile)
+ {
+ // Parse xml file
+ $pkg = new PEAR_PackageFile($this->config, $this->debug);
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING);
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($p)) {
+ if (is_array($p->getUserInfo())) {
+ foreach ($p->getUserInfo() as $err) {
+ $loglevel = $err['level'] == 'error' ? 0 : 1;
+ if (!isset($this->_options['soft'])) {
+ $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']);
+ }
+ }
+ }
+ return $this->raiseError('Installation failed: invalid package file');
+ }
+
+ $descfile = $p->getPackageFile();
+ return $p;
+ }
+
+ /**
+ * Set the list of PEAR_Downloader_Package objects to allow more sane
+ * dependency validation
+ * @param array
+ */
+ function setDownloadedPackages(&$pkgs)
+ {
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $err = $this->analyzeDependencies($pkgs);
+ PEAR::popErrorHandling();
+ if (PEAR::isError($err)) {
+ return $err;
+ }
+ $this->_downloadedPackages = &$pkgs;
+ }
+
+ /**
+ * Set the list of PEAR_Downloader_Package objects to allow more sane
+ * dependency validation
+ * @param array
+ */
+ function setUninstallPackages(&$pkgs)
+ {
+ $this->_downloadedPackages = &$pkgs;
+ }
+
+ function getInstallPackages()
+ {
+ return $this->_downloadedPackages;
+ }
+
+ /**
+ * Installs the files within the package file specified.
+ *
+ * @param string|PEAR_Downloader_Package $pkgfile path to the package file,
+ * or a pre-initialized packagefile object
+ * @param array $options
+ * recognized options:
+ * - installroot : optional prefix directory for installation
+ * - force : force installation
+ * - register-only : update registry but don't install files
+ * - upgrade : upgrade existing install
+ * - soft : fail silently
+ * - nodeps : ignore dependency conflicts/missing dependencies
+ * - alldeps : install all dependencies
+ * - onlyreqdeps : install only required dependencies
+ *
+ * @return array|PEAR_Error package info if successful
+ */
+ function install($pkgfile, $options = array())
+ {
+ $this->_options = $options;
+ $this->_registry = &$this->config->getRegistry();
+ if (is_object($pkgfile)) {
+ $dlpkg = &$pkgfile;
+ $pkg = $pkgfile->getPackageFile();
+ $pkgfile = $pkg->getArchiveFile();
+ $descfile = $pkg->getPackageFile();
+ } else {
+ $descfile = $pkgfile;
+ $pkg = $this->_parsePackageXml($descfile);
+ if (PEAR::isError($pkg)) {
+ return $pkg;
+ }
+ }
+
+ $tmpdir = dirname($descfile);
+ if (realpath($descfile) != realpath($pkgfile)) {
+ // Use the temp_dir since $descfile can contain the download dir path
+ $tmpdir = $this->config->get('temp_dir', null, 'pear.php.net');
+ $tmpdir = System::mktemp('-d -t "' . $tmpdir . '"');
+
+ $tar = new Archive_Tar($pkgfile);
+ if (!$tar->extract($tmpdir)) {
+ return $this->raiseError("unable to unpack $pkgfile");
+ }
+ }
+
+ $name = $pkg->getName();
+ $channel = $pkg->getChannel();
+ if (isset($this->_options['packagingroot'])) {
+ $regdir = $this->_prependPath(
+ $this->config->get('php_dir', null, 'pear.php.net'),
+ $this->_options['packagingroot']);
+
+ $packrootphp_dir = $this->_prependPath(
+ $this->config->get('php_dir', null, $channel),
+ $this->_options['packagingroot']);
+ }
+
+ if (isset($options['installroot'])) {
+ $this->config->setInstallRoot($options['installroot']);
+ $this->_registry = &$this->config->getRegistry();
+ $installregistry = &$this->_registry;
+ $this->installroot = ''; // all done automagically now
+ $php_dir = $this->config->get('php_dir', null, $channel);
+ } else {
+ $this->config->setInstallRoot(false);
+ $this->_registry = &$this->config->getRegistry();
+ if (isset($this->_options['packagingroot'])) {
+ $installregistry = &new PEAR_Registry($regdir);
+ if (!$installregistry->channelExists($channel, true)) {
+ // we need to fake a channel-discover of this channel
+ $chanobj = $this->_registry->getChannel($channel, true);
+ $installregistry->addChannel($chanobj);
+ }
+ $php_dir = $packrootphp_dir;
+ } else {
+ $installregistry = &$this->_registry;
+ $php_dir = $this->config->get('php_dir', null, $channel);
+ }
+ $this->installroot = '';
+ }
+
+ // checks to do when not in "force" mode
+ if (empty($options['force']) &&
+ (file_exists($this->config->get('php_dir')) &&
+ is_dir($this->config->get('php_dir')))) {
+ $testp = $channel == 'pear.php.net' ? $name : array($channel, $name);
+ $instfilelist = $pkg->getInstallationFileList(true);
+ if (PEAR::isError($instfilelist)) {
+ return $instfilelist;
+ }
+
+ // ensure we have the most accurate registry
+ $installregistry->flushFileMap();
+ $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1');
+ if (PEAR::isError($test)) {
+ return $test;
+ }
+
+ if (sizeof($test)) {
+ $pkgs = $this->getInstallPackages();
+ $found = false;
+ foreach ($pkgs as $param) {
+ if ($pkg->isSubpackageOf($param)) {
+ $found = true;
+ break;
+ }
+ }
+
+ if ($found) {
+ // subpackages can conflict with earlier versions of parent packages
+ $parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel());
+ $tmp = $test;
+ foreach ($tmp as $file => $info) {
+ if (is_array($info)) {
+ if (strtolower($info[1]) == strtolower($param->getPackage()) &&
+ strtolower($info[0]) == strtolower($param->getChannel())
+ ) {
+ if (isset($parentreg['filelist'][$file])) {
+ unset($parentreg['filelist'][$file]);
+ } else{
+ $pos = strpos($file, '/');
+ $basedir = substr($file, 0, $pos);
+ $file2 = substr($file, $pos + 1);
+ if (isset($parentreg['filelist'][$file2]['baseinstalldir'])
+ && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
+ ) {
+ unset($parentreg['filelist'][$file2]);
+ }
+ }
+
+ unset($test[$file]);
+ }
+ } else {
+ if (strtolower($param->getChannel()) != 'pear.php.net') {
+ continue;
+ }
+
+ if (strtolower($info) == strtolower($param->getPackage())) {
+ if (isset($parentreg['filelist'][$file])) {
+ unset($parentreg['filelist'][$file]);
+ } else{
+ $pos = strpos($file, '/');
+ $basedir = substr($file, 0, $pos);
+ $file2 = substr($file, $pos + 1);
+ if (isset($parentreg['filelist'][$file2]['baseinstalldir'])
+ && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
+ ) {
+ unset($parentreg['filelist'][$file2]);
+ }
+ }
+
+ unset($test[$file]);
+ }
+ }
+ }
+
+ $pfk = &new PEAR_PackageFile($this->config);
+ $parentpkg = &$pfk->fromArray($parentreg);
+ $installregistry->updatePackage2($parentpkg);
+ }
+
+ if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) {
+ $tmp = $test;
+ foreach ($tmp as $file => $info) {
+ if (is_string($info)) {
+ // pear.php.net packages are always stored as strings
+ if (strtolower($info) == strtolower($param->getPackage())) {
+ // upgrading existing package
+ unset($test[$file]);
+ }
+ }
+ }
+ }
+
+ if (count($test)) {
+ $msg = "$channel/$name: conflicting files found:\n";
+ $longest = max(array_map("strlen", array_keys($test)));
+ $fmt = "%${longest}s (%s)\n";
+ foreach ($test as $file => $info) {
+ if (!is_array($info)) {
+ $info = array('pear.php.net', $info);
+ }
+ $info = $info[0] . '/' . $info[1];
+ $msg .= sprintf($fmt, $file, $info);
+ }
+
+ if (!isset($options['ignore-errors'])) {
+ return $this->raiseError($msg);
+ }
+
+ if (!isset($options['soft'])) {
+ $this->log(0, "WARNING: $msg");
+ }
+ }
+ }
+ }
+
+ $this->startFileTransaction();
+
+ // Figure out what channel to use and if the package exists or not
+ $usechannel = $channel;
+ if ($channel == 'pecl.php.net') {
+ $test = $installregistry->packageExists($name, $channel);
+ if (!$test) {
+ $test = $installregistry->packageExists($name, 'pear.php.net');
+ $usechannel = 'pear.php.net';
+ }
+ } else {
+ $test = $installregistry->packageExists($name, $channel);
+ }
+
+ // checks to do only when installing new packages
+ if (empty($options['upgrade']) && empty($options['soft'])) {
+ if (empty($options['force']) && $test) {
+ return $this->raiseError("$channel/$name is already installed");
+ }
+ } else {
+ // Upgrade
+ if ($test) {
+ $v1 = $installregistry->packageInfo($name, 'version', $usechannel);
+ $v2 = $pkg->getVersion();
+ $cmp = version_compare("$v1", "$v2", 'gt');
+ if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) {
+ return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)");
+ }
+ }
+ }
+
+ // Do cleanups for upgrade and install, remove old release's files first
+ if ($test && empty($options['register-only'])) {
+ $err = $this->_deletePackageFiles($name, $usechannel, true);
+ if (PEAR::isError($err)) {
+ if (!isset($options['ignore-errors'])) {
+ return $this->raiseError($err);
+ }
+
+ if (!isset($options['soft'])) {
+ $this->log(0, 'WARNING: ' . $err->getMessage());
+ }
+ } else {
+ $backedup = $err;
+ }
+ }
+
+ // Copy files to dest dir
+
+ // info from the package it self we want to access from _installFile
+ $this->pkginfo = &$pkg;
+ // used to determine whether we should build any C code
+ $this->source_files = 0;
+
+ $savechannel = $this->config->get('default_channel');
+ if (empty($options['register-only']) && !is_dir($php_dir)) {
+ if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) {
+ return $this->raiseError("no installation destination directory '$php_dir'\n");
+ }
+ }
+
+ if (substr($pkgfile, -4) != '.xml') {
+ $tmpdir .= DIRECTORY_SEPARATOR . $name . '-' . $pkg->getVersion();
+ }
+
+ $this->configSet('default_channel', $channel);
+ // install files
+
+ $filelist = $pkg->getInstallationFilelist();
+ if (PEAR::isError($filelist)) {
+ return $filelist;
+ }
+
+ $p = &$installregistry->getPackage($name, $channel);
+ $dirtree = (empty($options['register-only']) && $p) ? $p->getDirTree() : false;
+
+ $pkg->resetFilelist();
+ $version = $installregistry->packageInfo($pkg->getPackage(), 'version', $pkg->getChannel());
+ $pkg->setLastInstalledVersion($version);
+ foreach ($filelist as $file => $atts) {
+ $this->expectError(PEAR_INSTALLER_FAILED);
+ if ($pkg->getPackagexmlVersion() == '1.0') {
+ $res = $this->_installFile($file, $atts, $tmpdir, $options);
+ } else {
+ $res = $this->_installFile2($pkg, $file, $atts, $tmpdir, $options);
+ }
+ $this->popExpect();
+
+ if (PEAR::isError($res)) {
+ if (empty($options['ignore-errors'])) {
+ $this->rollbackFileTransaction();
+ if ($res->getMessage() == "file does not exist") {
+ $this->raiseError("file $file in package.xml does not exist");
+ }
+
+ return $this->raiseError($res);
+ }
+
+ if (!isset($options['soft'])) {
+ $this->log(0, "Warning: " . $res->getMessage());
+ }
+ }
+
+ $real = isset($atts['attribs']) ? $atts['attribs'] : $atts;
+ if ($res == PEAR_INSTALLER_OK && $real['role'] != 'src') {
+ // Register files that were installed
+ $pkg->installedFile($file, $atts);
+ }
+ }
+
+ // compile and install source files
+ if ($this->source_files > 0 && empty($options['nobuild'])) {
+ if (!isset($options['__compile_configureoptions'])) {
+ $options['__compile_configureoptions'] = array();
+ }
+
+ if (PEAR::isError($err =
+ $this->_compileSourceFiles($savechannel, $pkg, $options['__compile_configureoptions']))) {
+ return $err;
+ }
+ }
+
+ if (isset($backedup)) {
+ $this->_removeBackups($backedup);
+ }
+
+ if (!$this->commitFileTransaction()) {
+ $this->rollbackFileTransaction();
+ $this->configSet('default_channel', $savechannel);
+ return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
+ }
+
+ // See if package already exists
+ $usechannel = $channel;
+ if ($channel == 'pecl.php.net') {
+ $test = $installregistry->packageExists($name, $channel);
+ if (!$test) {
+ $test = $installregistry->packageExists($name, 'pear.php.net');
+ $usechannel = 'pear.php.net';
+ }
+ } else {
+ $test = $installregistry->packageExists($name, $channel);
+ }
+
+ $ret = false;
+ $installphase = 'install';
+ $oldversion = false;
+ // Register that the package is installed
+ if (empty($options['upgrade'])) {
+ // if 'force' is used, replace the info in registry
+ if (!empty($options['force']) && $test) {
+ $oldversion = $installregistry->packageInfo($name, 'version', $usechannel);
+ $installregistry->deletePackage($name, $usechannel);
+ }
+ $ret = $installregistry->addPackage2($pkg);
+ } else {
+ if ($dirtree) {
+ $this->startFileTransaction();
+ // attempt to delete empty directories
+ uksort($dirtree, array($this, '_sortDirs'));
+ foreach($dirtree as $dir => $notused) {
+ $this->addFileOperation('rmdir', array($dir));
+ }
+ $this->commitFileTransaction();
+ }
+
+ // new: upgrade installs a package if it isn't installed
+ if (!$test) {
+ $ret = $installregistry->addPackage2($pkg);
+ } else {
+ if ($usechannel != $channel) {
+ $installregistry->deletePackage($name, $usechannel);
+ $ret = $installregistry->addPackage2($pkg);
+ } else {
+ $ret = $installregistry->updatePackage2($pkg);
+ }
+ $installphase = 'upgrade';
+ }
+ }
+
+ if (!$ret) {
+ $this->configSet('default_channel', $savechannel);
+ return $this->raiseError("Adding package $channel/$name to registry failed");
+ }
+ // }}}
+
+ $this->configSet('default_channel', $savechannel);
+ if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist
+ if (PEAR_Task_Common::hasPostinstallTasks()) {
+ PEAR_Task_Common::runPostinstallTasks($installphase);
+ }
+ }
+
+ return $pkg->toArray(true);
+ }
+
+ /**
+ * @param string
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ */
+ function _compileSourceFiles($savechannel, &$filelist, $configure_options = array())
+ {
+ require_once 'PEAR/Builder.php';
+ $this->log(1, "$this->source_files source files, building");
+ $bob = &new PEAR_Builder($this->ui);
+ $bob->debug = $this->debug;
+ $built = $bob->build($filelist, array(&$this, '_buildCallback'), $configure_options);
+ if (PEAR::isError($built)) {
+ $this->rollbackFileTransaction();
+ $this->configSet('default_channel', $savechannel);
+ return $built;
+ }
+
+ $this->log(1, "\nBuild process completed successfully");
+ foreach ($built as $ext) {
+ $bn = basename($ext['file']);
+ list($_ext_name, $_ext_suff) = explode('.', $bn);
+ if ($_ext_suff == '.so' || $_ext_suff == '.dll') {
+ if (extension_loaded($_ext_name)) {
+ $this->raiseError("Extension '$_ext_name' already loaded. " .
+ 'Please unload it in your php.ini file ' .
+ 'prior to install or upgrade');
+ }
+ $role = 'ext';
+ } else {
+ $role = 'src';
+ }
+
+ $dest = $ext['dest'];
+ $packagingroot = '';
+ if (isset($this->_options['packagingroot'])) {
+ $packagingroot = $this->_options['packagingroot'];
+ }
+
+ $copyto = $this->_prependPath($dest, $packagingroot);
+ $extra = $copyto != $dest ? " as '$copyto'" : '';
+ $this->log(1, "Installing '$dest'$extra");
+
+ $copydir = dirname($copyto);
+ // pretty much nothing happens if we are only registering the install
+ if (empty($this->_options['register-only'])) {
+ if (!file_exists($copydir) || !is_dir($copydir)) {
+ if (!$this->mkDirHier($copydir)) {
+ return $this->raiseError("failed to mkdir $copydir",
+ PEAR_INSTALLER_FAILED);
+ }
+
+ $this->log(3, "+ mkdir $copydir");
+ }
+
+ if (!@copy($ext['file'], $copyto)) {
+ return $this->raiseError("failed to write $copyto ($php_errormsg)", PEAR_INSTALLER_FAILED);
+ }
+
+ $this->log(3, "+ cp $ext[file] $copyto");
+ $this->addFileOperation('rename', array($ext['file'], $copyto));
+ if (!OS_WINDOWS) {
+ $mode = 0666 & ~(int)octdec($this->config->get('umask'));
+ $this->addFileOperation('chmod', array($mode, $copyto));
+ if (!@chmod($copyto, $mode)) {
+ $this->log(0, "failed to change mode of $copyto ($php_errormsg)");
+ }
+ }
+ }
+
+ $data = array(
+ 'role' => $role,
+ 'name' => $bn,
+ 'installed_as' => $dest,
+ 'php_api' => $ext['php_api'],
+ 'zend_mod_api' => $ext['zend_mod_api'],
+ 'zend_ext_api' => $ext['zend_ext_api'],
+ );
+
+ if ($filelist->getPackageXmlVersion() == '1.0') {
+ $filelist->installedFile($bn, $data);
+ } else {
+ $filelist->installedFile($bn, array('attribs' => $data));
+ }
+ }
+ }
+
+ function &getUninstallPackages()
+ {
+ return $this->_downloadedPackages;
+ }
+
+ /**
+ * Uninstall a package
+ *
+ * This method removes all files installed by the application, and then
+ * removes any empty directories.
+ * @param string package name
+ * @param array Command-line options. Possibilities include:
+ *
+ * - installroot: base installation dir, if not the default
+ * - register-only : update registry but don't remove files
+ * - nodeps: do not process dependencies of other packages to ensure
+ * uninstallation does not break things
+ */
+ function uninstall($package, $options = array())
+ {
+ $installRoot = isset($options['installroot']) ? $options['installroot'] : '';
+ $this->config->setInstallRoot($installRoot);
+
+ $this->installroot = '';
+ $this->_registry = &$this->config->getRegistry();
+ if (is_object($package)) {
+ $channel = $package->getChannel();
+ $pkg = $package;
+ $package = $pkg->getPackage();
+ } else {
+ $pkg = false;
+ $info = $this->_registry->parsePackageName($package,
+ $this->config->get('default_channel'));
+ $channel = $info['channel'];
+ $package = $info['package'];
+ }
+
+ $savechannel = $this->config->get('default_channel');
+ $this->configSet('default_channel', $channel);
+ if (!is_object($pkg)) {
+ $pkg = $this->_registry->getPackage($package, $channel);
+ }
+
+ if (!$pkg) {
+ $this->configSet('default_channel', $savechannel);
+ return $this->raiseError($this->_registry->parsedPackageNameToString(
+ array(
+ 'channel' => $channel,
+ 'package' => $package
+ ), true) . ' not installed');
+ }
+
+ if ($pkg->getInstalledBinary()) {
+ // this is just an alias for a binary package
+ return $this->_registry->deletePackage($package, $channel);
+ }
+
+ $filelist = $pkg->getFilelist();
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ if (!class_exists('PEAR_Dependency2')) {
+ require_once 'PEAR/Dependency2.php';
+ }
+
+ $depchecker = &new PEAR_Dependency2($this->config, $options,
+ array('channel' => $channel, 'package' => $package),
+ PEAR_VALIDATE_UNINSTALLING);
+ $e = $depchecker->validatePackageUninstall($this);
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($e)) {
+ if (!isset($options['ignore-errors'])) {
+ return $this->raiseError($e);
+ }
+
+ if (!isset($options['soft'])) {
+ $this->log(0, 'WARNING: ' . $e->getMessage());
+ }
+ } elseif (is_array($e)) {
+ if (!isset($options['soft'])) {
+ $this->log(0, $e[0]);
+ }
+ }
+
+ $this->pkginfo = &$pkg;
+ // pretty much nothing happens if we are only registering the uninstall
+ if (empty($options['register-only'])) {
+ // Delete the files
+ $this->startFileTransaction();
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ if (PEAR::isError($err = $this->_deletePackageFiles($package, $channel))) {
+ PEAR::popErrorHandling();
+ $this->rollbackFileTransaction();
+ $this->configSet('default_channel', $savechannel);
+ if (!isset($options['ignore-errors'])) {
+ return $this->raiseError($err);
+ }
+
+ if (!isset($options['soft'])) {
+ $this->log(0, 'WARNING: ' . $err->getMessage());
+ }
+ } else {
+ PEAR::popErrorHandling();
+ }
+
+ if (!$this->commitFileTransaction()) {
+ $this->rollbackFileTransaction();
+ if (!isset($options['ignore-errors'])) {
+ return $this->raiseError("uninstall failed");
+ }
+
+ if (!isset($options['soft'])) {
+ $this->log(0, 'WARNING: uninstall failed');
+ }
+ } else {
+ $this->startFileTransaction();
+ $dirtree = $pkg->getDirTree();
+ if ($dirtree === false) {
+ $this->configSet('default_channel', $savechannel);
+ return $this->_registry->deletePackage($package, $channel);
+ }
+
+ // attempt to delete empty directories
+ uksort($dirtree, array($this, '_sortDirs'));
+ foreach($dirtree as $dir => $notused) {
+ $this->addFileOperation('rmdir', array($dir));
+ }
+
+ if (!$this->commitFileTransaction()) {
+ $this->rollbackFileTransaction();
+ if (!isset($options['ignore-errors'])) {
+ return $this->raiseError("uninstall failed");
+ }
+
+ if (!isset($options['soft'])) {
+ $this->log(0, 'WARNING: uninstall failed');
+ }
+ }
+ }
+ }
+
+ $this->configSet('default_channel', $savechannel);
+ // Register that the package is no longer installed
+ return $this->_registry->deletePackage($package, $channel);
+ }
+
+ /**
+ * Sort a list of arrays of array(downloaded packagefilename) by dependency.
+ *
+ * It also removes duplicate dependencies
+ * @param array an array of PEAR_PackageFile_v[1/2] objects
+ * @return array|PEAR_Error array of array(packagefilename, package.xml contents)
+ */
+ function sortPackagesForUninstall(&$packages)
+ {
+ $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config);
+ if (PEAR::isError($this->_dependencyDB)) {
+ return $this->_dependencyDB;
+ }
+ usort($packages, array(&$this, '_sortUninstall'));
+ }
+
+ function _sortUninstall($a, $b)
+ {
+ if (!$a->getDeps() && !$b->getDeps()) {
+ return 0; // neither package has dependencies, order is insignificant
+ }
+ if ($a->getDeps() && !$b->getDeps()) {
+ return -1; // $a must be installed after $b because $a has dependencies
+ }
+ if (!$a->getDeps() && $b->getDeps()) {
+ return 1; // $b must be installed after $a because $b has dependencies
+ }
+ // both packages have dependencies
+ if ($this->_dependencyDB->dependsOn($a, $b)) {
+ return -1;
+ }
+ if ($this->_dependencyDB->dependsOn($b, $a)) {
+ return 1;
+ }
+ return 0;
+ }
+
+ function _sortDirs($a, $b)
+ {
+ if (strnatcmp($a, $b) == -1) return 1;
+ if (strnatcmp($a, $b) == 1) return -1;
+ return 0;
+ }
+
+ function _buildCallback($what, $data)
+ {
+ if (($what == 'cmdoutput' && $this->debug > 1) ||
+ ($what == 'output' && $this->debug > 0)) {
+ $this->ui->outputData(rtrim($data), 'build');
+ }
+ }
+}
diff --git a/includes/pear/PEAR/Installer/Role.php b/includes/pear/PEAR/Installer/Role.php
new file mode 100644
index 0000000..2eba3da
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role.php
@@ -0,0 +1,267 @@
+<?php
+/**
+ * PEAR_Installer_Role
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * base class for installer roles
+ */
+require_once 'PEAR/Installer/Role/Common.php';
+require_once 'PEAR/XMLParser.php';
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Installer_Role
+{
+ /**
+ * Set up any additional configuration variables that file roles require
+ *
+ * Never call this directly, it is called by the PEAR_Config constructor
+ * @param PEAR_Config
+ */
+ public static function initializeConfig(&$config)
+ {
+ if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) {
+ PEAR_Installer_Role::registerRoles();
+ }
+
+ foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $class => $info) {
+ if (!$info['config_vars']) {
+ continue;
+ }
+
+ $config->_addConfigVars($class, $info['config_vars']);
+ }
+ }
+
+ /**
+ * @param PEAR_PackageFile_v2
+ * @param string role name
+ * @param PEAR_Config
+ * @return PEAR_Installer_Role_Common
+ */
+ public static function &factory($pkg, $role, &$config)
+ {
+ if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) {
+ PEAR_Installer_Role::registerRoles();
+ }
+
+ if (!in_array($role, PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) {
+ $a = false;
+ return $a;
+ }
+
+ $a = 'PEAR_Installer_Role_' . ucfirst($role);
+ if (!class_exists($a)) {
+ require_once str_replace('_', '/', $a) . '.php';
+ }
+
+ $b = new $a($config);
+ return $b;
+ }
+
+ /**
+ * Get a list of file roles that are valid for the particular release type.
+ *
+ * For instance, src files serve no purpose in regular php releases.
+ * @param string
+ * @param bool clear cache
+ * @return array
+ */
+ public static function getValidRoles($release, $clear = false)
+ {
+ if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) {
+ PEAR_Installer_Role::registerRoles();
+ }
+
+ static $ret = array();
+ if ($clear) {
+ $ret = array();
+ }
+
+ if (isset($ret[$release])) {
+ return $ret[$release];
+ }
+
+ $ret[$release] = array();
+ foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) {
+ if (in_array($release, $okreleases['releasetypes'])) {
+ $ret[$release][] = strtolower(str_replace('PEAR_Installer_Role_', '', $role));
+ }
+ }
+
+ return $ret[$release];
+ }
+
+ /**
+ * Get a list of roles that require their files to be installed
+ *
+ * Most roles must be installed, but src and package roles, for instance
+ * are pseudo-roles. src files are compiled into a new extension. Package
+ * roles are actually fully bundled releases of a package
+ * @param bool clear cache
+ * @return array
+ */
+ public static function getInstallableRoles($clear = false)
+ {
+ if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) {
+ PEAR_Installer_Role::registerRoles();
+ }
+
+ static $ret;
+ if ($clear) {
+ unset($ret);
+ }
+
+ if (isset($ret)) {
+ return $ret;
+ }
+
+ $ret = array();
+ foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) {
+ if ($okreleases['installable']) {
+ $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role));
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Return an array of roles that are affected by the baseinstalldir attribute
+ *
+ * Most roles ignore this attribute, and instead install directly into:
+ * PackageName/filepath
+ * so a tests file tests/file.phpt is installed into PackageName/tests/filepath.php
+ * @param bool clear cache
+ * @return array
+ */
+ public static function getBaseinstallRoles($clear = false)
+ {
+ if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) {
+ PEAR_Installer_Role::registerRoles();
+ }
+
+ static $ret;
+ if ($clear) {
+ unset($ret);
+ }
+
+ if (isset($ret)) {
+ return $ret;
+ }
+
+ $ret = array();
+ foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) {
+ if ($okreleases['honorsbaseinstall']) {
+ $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role));
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Return an array of file roles that should be analyzed for PHP content at package time,
+ * like the "php" role.
+ * @param bool clear cache
+ * @return array
+ */
+ public static function getPhpRoles($clear = false)
+ {
+ if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'])) {
+ PEAR_Installer_Role::registerRoles();
+ }
+
+ static $ret;
+ if ($clear) {
+ unset($ret);
+ }
+
+ if (isset($ret)) {
+ return $ret;
+ }
+
+ $ret = array();
+ foreach ($GLOBALS['_PEAR_INSTALLER_ROLES'] as $role => $okreleases) {
+ if ($okreleases['phpfile']) {
+ $ret[] = strtolower(str_replace('PEAR_Installer_Role_', '', $role));
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Scan through the Command directory looking for classes
+ * and see what commands they implement.
+ * @param string which directory to look for classes, defaults to
+ * the Installer/Roles subdirectory of
+ * the directory from where this file (__FILE__) is
+ * included.
+ *
+ * @return bool TRUE on success, a PEAR error on failure
+ */
+ public static function registerRoles($dir = null)
+ {
+ $GLOBALS['_PEAR_INSTALLER_ROLES'] = array();
+ $parser = new PEAR_XMLParser;
+ if ($dir === null) {
+ $dir = dirname(__FILE__) . '/Role';
+ }
+
+ if (!file_exists($dir) || !is_dir($dir)) {
+ return PEAR::raiseError("registerRoles: opendir($dir) failed: does not exist/is not directory");
+ }
+
+ $dp = @opendir($dir);
+ if (empty($dp)) {
+ return PEAR::raiseError("registerRoles: opendir($dir) failed: $php_errmsg");
+ }
+
+ while ($entry = readdir($dp)) {
+ if ($entry{0} == '.' || substr($entry, -4) != '.xml') {
+ continue;
+ }
+
+ $class = "PEAR_Installer_Role_".substr($entry, 0, -4);
+ // List of roles
+ if (!isset($GLOBALS['_PEAR_INSTALLER_ROLES'][$class])) {
+ $file = "$dir/$entry";
+ $parser->parse(file_get_contents($file));
+ $data = $parser->getData();
+ if (!is_array($data['releasetypes'])) {
+ $data['releasetypes'] = array($data['releasetypes']);
+ }
+
+ $GLOBALS['_PEAR_INSTALLER_ROLES'][$class] = $data;
+ }
+ }
+
+ closedir($dp);
+ ksort($GLOBALS['_PEAR_INSTALLER_ROLES']);
+ PEAR_Installer_Role::getBaseinstallRoles(true);
+ PEAR_Installer_Role::getInstallableRoles(true);
+ PEAR_Installer_Role::getPhpRoles(true);
+ PEAR_Installer_Role::getValidRoles('****', true);
+ return true;
+ }
+}
diff --git a/includes/pear/PEAR/Installer/Role/Cfg.php b/includes/pear/PEAR/Installer/Role/Cfg.php
new file mode 100644
index 0000000..23af39c
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Cfg.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * PEAR_Installer_Role_Cfg
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 2007-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.7.0
+ */
+
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 2007-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.7.0
+ */
+class PEAR_Installer_Role_Cfg extends PEAR_Installer_Role_Common
+{
+ /**
+ * @var PEAR_Installer
+ */
+ var $installer;
+
+ /**
+ * the md5 of the original file
+ *
+ * @var unknown_type
+ */
+ var $md5 = null;
+
+ /**
+ * Do any unusual setup here
+ * @param PEAR_Installer
+ * @param PEAR_PackageFile_v2
+ * @param array file attributes
+ * @param string file name
+ */
+ function setup(&$installer, $pkg, $atts, $file)
+ {
+ $this->installer = &$installer;
+ $reg = &$this->installer->config->getRegistry();
+ $package = $reg->getPackage($pkg->getPackage(), $pkg->getChannel());
+ if ($package) {
+ $filelist = $package->getFilelist();
+ if (isset($filelist[$file]) && isset($filelist[$file]['md5sum'])) {
+ $this->md5 = $filelist[$file]['md5sum'];
+ }
+ }
+ }
+
+ function processInstallation($pkg, $atts, $file, $tmp_path, $layer = null)
+ {
+ $test = parent::processInstallation($pkg, $atts, $file, $tmp_path, $layer);
+ if (@file_exists($test[2]) && @file_exists($test[3])) {
+ $md5 = md5_file($test[2]);
+ // configuration has already been installed, check for mods
+ if ($md5 !== $this->md5 && $md5 !== md5_file($test[3])) {
+ // configuration has been modified, so save our version as
+ // configfile-version
+ $old = $test[2];
+ $test[2] .= '.new-' . $pkg->getVersion();
+ // backup original and re-install it
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $tmpcfg = $this->config->get('temp_dir');
+ $newloc = System::mkdir(array('-p', $tmpcfg));
+ if (!$newloc) {
+ // try temp_dir
+ $newloc = System::mktemp(array('-d'));
+ if (!$newloc || PEAR::isError($newloc)) {
+ PEAR::popErrorHandling();
+ return PEAR::raiseError('Could not save existing configuration file '.
+ $old . ', unable to install. Please set temp_dir ' .
+ 'configuration variable to a writeable location and try again');
+ }
+ } else {
+ $newloc = $tmpcfg;
+ }
+
+ $temp_file = $newloc . DIRECTORY_SEPARATOR . uniqid('savefile');
+ if (!@copy($old, $temp_file)) {
+ PEAR::popErrorHandling();
+ return PEAR::raiseError('Could not save existing configuration file '.
+ $old . ', unable to install. Please set temp_dir ' .
+ 'configuration variable to a writeable location and try again');
+ }
+
+ PEAR::popErrorHandling();
+ $this->installer->log(0, "WARNING: configuration file $old is being installed as $test[2], you should manually merge in changes to the existing configuration file");
+ $this->installer->addFileOperation('rename', array($temp_file, $old, false));
+ $this->installer->addFileOperation('delete', array($temp_file));
+ }
+ }
+
+ return $test;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Cfg.xml b/includes/pear/PEAR/Installer/Role/Cfg.xml
new file mode 100644
index 0000000..7a415dc
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Cfg.xml
@@ -0,0 +1,15 @@
+<role version="1.0">
+ <releasetypes>php</releasetypes>
+ <releasetypes>extsrc</releasetypes>
+ <releasetypes>extbin</releasetypes>
+ <releasetypes>zendextsrc</releasetypes>
+ <releasetypes>zendextbin</releasetypes>
+ <installable>1</installable>
+ <locationconfig>cfg_dir</locationconfig>
+ <honorsbaseinstall />
+ <unusualbaseinstall>1</unusualbaseinstall>
+ <phpfile />
+ <executable />
+ <phpextension />
+ <config_vars />
+</role> \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Common.php b/includes/pear/PEAR/Installer/Role/Common.php
new file mode 100644
index 0000000..b2b2a61
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Common.php
@@ -0,0 +1,189 @@
+<?php
+/**
+ * Base class for all installation roles.
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+/**
+ * Base class for all installation roles.
+ *
+ * This class allows extensibility of file roles. Packages with complex
+ * customization can now provide custom file roles along with the possibility of
+ * adding configuration values to match.
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Installer_Role_Common
+{
+ /**
+ * @var PEAR_Config
+ * @access protected
+ */
+ var $config;
+
+ /**
+ * @param PEAR_Config
+ */
+ function PEAR_Installer_Role_Common(&$config)
+ {
+ $this->config = $config;
+ }
+
+ /**
+ * Retrieve configuration information about a file role from its XML info
+ *
+ * @param string $role Role Classname, as in "PEAR_Installer_Role_Data"
+ * @return array
+ */
+ function getInfo($role)
+ {
+ if (empty($GLOBALS['_PEAR_INSTALLER_ROLES'][$role])) {
+ return PEAR::raiseError('Unknown Role class: "' . $role . '"');
+ }
+ return $GLOBALS['_PEAR_INSTALLER_ROLES'][$role];
+ }
+
+ /**
+ * This is called for each file to set up the directories and files
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * @param array attributes from the <file> tag
+ * @param string file name
+ * @return array an array consisting of:
+ *
+ * 1 the original, pre-baseinstalldir installation directory
+ * 2 the final installation directory
+ * 3 the full path to the final location of the file
+ * 4 the location of the pre-installation file
+ */
+ function processInstallation($pkg, $atts, $file, $tmp_path, $layer = null)
+ {
+ $role = $this->getRoleFromClass();
+ $info = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . $role);
+ if (PEAR::isError($info)) {
+ return $info;
+ }
+
+ if (!$info['locationconfig']) {
+ return false;
+ }
+
+ if ($info['honorsbaseinstall']) {
+ $dest_dir = $save_destdir = $this->config->get($info['locationconfig'], $layer,
+ $pkg->getChannel());
+ if (!empty($atts['baseinstalldir'])) {
+ $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
+ }
+ } elseif ($info['unusualbaseinstall']) {
+ $dest_dir = $save_destdir = $this->config->get($info['locationconfig'],
+ $layer, $pkg->getChannel()) . DIRECTORY_SEPARATOR . $pkg->getPackage();
+ if (!empty($atts['baseinstalldir'])) {
+ $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
+ }
+ } else {
+ $save_destdir = $dest_dir = $this->config->get($info['locationconfig'],
+ $layer, $pkg->getChannel()) . DIRECTORY_SEPARATOR . $pkg->getPackage();
+ }
+
+ if (dirname($file) != '.' && empty($atts['install-as'])) {
+ $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
+ }
+
+ if (empty($atts['install-as'])) {
+ $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
+ } else {
+ $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
+ }
+
+ $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
+
+ // Clean up the DIRECTORY_SEPARATOR mess
+ $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
+
+ list($dest_dir, $dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
+ array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR,
+ DIRECTORY_SEPARATOR),
+ array($dest_dir, $dest_file, $orig_file));
+
+ return array($save_destdir, $dest_dir, $dest_file, $orig_file);
+ }
+
+ /**
+ * Get the name of the configuration variable that specifies the location of this file
+ * @return string|false
+ */
+ function getLocationConfig()
+ {
+ $role = ucfirst(str_replace('pear_installer_role_', '', strtolower(get_class($this))));
+ $info = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . $role);
+ if (PEAR::isError($info)) {
+ return $info;
+ }
+
+ return $info['locationconfig'];
+ }
+
+ /**
+ * Do any unusual setup here
+ * @param PEAR_Installer
+ * @param PEAR_PackageFile_v2
+ * @param array file attributes
+ * @param string file name
+ */
+ function setup(&$installer, $pkg, $atts, $file)
+ {
+ }
+
+ function isExecutable()
+ {
+ $role = $this->getRoleFromClass();
+ $info = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . $role);
+ if (PEAR::isError($info)) {
+ return $info;
+ }
+
+ return $info['executable'];
+ }
+
+ function isInstallable()
+ {
+ $role = $this->getRoleFromClass();
+ $info = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . $role);
+ if (PEAR::isError($info)) {
+ return $info;
+ }
+
+ return $info['installable'];
+ }
+
+ function isExtension()
+ {
+ $role = $this->getRoleFromClass();
+ $info = PEAR_Installer_Role_Common::getInfo('PEAR_Installer_Role_' . $role);
+ if (PEAR::isError($info)) {
+ return $info;
+ }
+
+ return $info['phpextension'];
+ }
+
+ function getRoleFromClass()
+ {
+ $lower = strtolower(get_class($this));
+ return ucfirst(str_replace('pear_installer_role_', '', $lower));
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Data.php b/includes/pear/PEAR/Installer/Role/Data.php
new file mode 100644
index 0000000..27d05d4
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Data.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * PEAR_Installer_Role_Data
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Installer_Role_Data extends PEAR_Installer_Role_Common {}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Data.xml b/includes/pear/PEAR/Installer/Role/Data.xml
new file mode 100644
index 0000000..eae6372
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Data.xml
@@ -0,0 +1,15 @@
+<role version="1.0">
+ <releasetypes>php</releasetypes>
+ <releasetypes>extsrc</releasetypes>
+ <releasetypes>extbin</releasetypes>
+ <releasetypes>zendextsrc</releasetypes>
+ <releasetypes>zendextbin</releasetypes>
+ <installable>1</installable>
+ <locationconfig>data_dir</locationconfig>
+ <honorsbaseinstall />
+ <unusualbaseinstall />
+ <phpfile />
+ <executable />
+ <phpextension />
+ <config_vars />
+</role> \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Doc.php b/includes/pear/PEAR/Installer/Role/Doc.php
new file mode 100644
index 0000000..5194e6e
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Doc.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * PEAR_Installer_Role_Doc
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Installer_Role_Doc extends PEAR_Installer_Role_Common {}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Doc.xml b/includes/pear/PEAR/Installer/Role/Doc.xml
new file mode 100644
index 0000000..173afba
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Doc.xml
@@ -0,0 +1,15 @@
+<role version="1.0">
+ <releasetypes>php</releasetypes>
+ <releasetypes>extsrc</releasetypes>
+ <releasetypes>extbin</releasetypes>
+ <releasetypes>zendextsrc</releasetypes>
+ <releasetypes>zendextbin</releasetypes>
+ <installable>1</installable>
+ <locationconfig>doc_dir</locationconfig>
+ <honorsbaseinstall />
+ <unusualbaseinstall />
+ <phpfile />
+ <executable />
+ <phpextension />
+ <config_vars />
+</role> \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Ext.php b/includes/pear/PEAR/Installer/Role/Ext.php
new file mode 100644
index 0000000..ad8988c
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Ext.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * PEAR_Installer_Role_Ext
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Installer_Role_Ext extends PEAR_Installer_Role_Common {}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Ext.xml b/includes/pear/PEAR/Installer/Role/Ext.xml
new file mode 100644
index 0000000..e2940fe
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Ext.xml
@@ -0,0 +1,12 @@
+<role version="1.0">
+ <releasetypes>extbin</releasetypes>
+ <releasetypes>zendextbin</releasetypes>
+ <installable>1</installable>
+ <locationconfig>ext_dir</locationconfig>
+ <honorsbaseinstall>1</honorsbaseinstall>
+ <unusualbaseinstall />
+ <phpfile />
+ <executable />
+ <phpextension>1</phpextension>
+ <config_vars />
+</role> \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Php.php b/includes/pear/PEAR/Installer/Role/Php.php
new file mode 100644
index 0000000..ee7f935
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Php.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * PEAR_Installer_Role_Php
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Installer_Role_Php extends PEAR_Installer_Role_Common {}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Php.xml b/includes/pear/PEAR/Installer/Role/Php.xml
new file mode 100644
index 0000000..6b9a0e6
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Php.xml
@@ -0,0 +1,15 @@
+<role version="1.0">
+ <releasetypes>php</releasetypes>
+ <releasetypes>extsrc</releasetypes>
+ <releasetypes>extbin</releasetypes>
+ <releasetypes>zendextsrc</releasetypes>
+ <releasetypes>zendextbin</releasetypes>
+ <installable>1</installable>
+ <locationconfig>php_dir</locationconfig>
+ <honorsbaseinstall>1</honorsbaseinstall>
+ <unusualbaseinstall />
+ <phpfile>1</phpfile>
+ <executable />
+ <phpextension />
+ <config_vars />
+</role> \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Script.php b/includes/pear/PEAR/Installer/Role/Script.php
new file mode 100644
index 0000000..8259305
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Script.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * PEAR_Installer_Role_Script
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Installer_Role_Script extends PEAR_Installer_Role_Common {}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Script.xml b/includes/pear/PEAR/Installer/Role/Script.xml
new file mode 100644
index 0000000..e732cf2
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Script.xml
@@ -0,0 +1,15 @@
+<role version="1.0">
+ <releasetypes>php</releasetypes>
+ <releasetypes>extsrc</releasetypes>
+ <releasetypes>extbin</releasetypes>
+ <releasetypes>zendextsrc</releasetypes>
+ <releasetypes>zendextbin</releasetypes>
+ <installable>1</installable>
+ <locationconfig>bin_dir</locationconfig>
+ <honorsbaseinstall>1</honorsbaseinstall>
+ <unusualbaseinstall />
+ <phpfile />
+ <executable>1</executable>
+ <phpextension />
+ <config_vars />
+</role> \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Src.php b/includes/pear/PEAR/Installer/Role/Src.php
new file mode 100644
index 0000000..3d114d4
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Src.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * PEAR_Installer_Role_Src
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Installer_Role_Src extends PEAR_Installer_Role_Common
+{
+ function setup(&$installer, $pkg, $atts, $file)
+ {
+ $installer->source_files++;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Src.xml b/includes/pear/PEAR/Installer/Role/Src.xml
new file mode 100644
index 0000000..1034834
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Src.xml
@@ -0,0 +1,12 @@
+<role version="1.0">
+ <releasetypes>extsrc</releasetypes>
+ <releasetypes>zendextsrc</releasetypes>
+ <installable>1</installable>
+ <locationconfig>temp_dir</locationconfig>
+ <honorsbaseinstall />
+ <unusualbaseinstall />
+ <phpfile />
+ <executable />
+ <phpextension />
+ <config_vars />
+</role> \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Test.php b/includes/pear/PEAR/Installer/Role/Test.php
new file mode 100644
index 0000000..06747f7
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Test.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * PEAR_Installer_Role_Test
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Installer_Role_Test extends PEAR_Installer_Role_Common {}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Test.xml b/includes/pear/PEAR/Installer/Role/Test.xml
new file mode 100644
index 0000000..51d5b89
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Test.xml
@@ -0,0 +1,15 @@
+<role version="1.0">
+ <releasetypes>php</releasetypes>
+ <releasetypes>extsrc</releasetypes>
+ <releasetypes>extbin</releasetypes>
+ <releasetypes>zendextsrc</releasetypes>
+ <releasetypes>zendextbin</releasetypes>
+ <installable>1</installable>
+ <locationconfig>test_dir</locationconfig>
+ <honorsbaseinstall />
+ <unusualbaseinstall />
+ <phpfile />
+ <executable />
+ <phpextension />
+ <config_vars />
+</role> \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Www.php b/includes/pear/PEAR/Installer/Role/Www.php
new file mode 100644
index 0000000..c463c8b
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Www.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * PEAR_Installer_Role_Www
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 2007-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.7.0
+ */
+
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 2007-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.7.0
+ */
+class PEAR_Installer_Role_Www extends PEAR_Installer_Role_Common {}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Installer/Role/Www.xml b/includes/pear/PEAR/Installer/Role/Www.xml
new file mode 100644
index 0000000..7598be3
--- /dev/null
+++ b/includes/pear/PEAR/Installer/Role/Www.xml
@@ -0,0 +1,15 @@
+<role version="1.0">
+ <releasetypes>php</releasetypes>
+ <releasetypes>extsrc</releasetypes>
+ <releasetypes>extbin</releasetypes>
+ <releasetypes>zendextsrc</releasetypes>
+ <releasetypes>zendextbin</releasetypes>
+ <installable>1</installable>
+ <locationconfig>www_dir</locationconfig>
+ <honorsbaseinstall>1</honorsbaseinstall>
+ <unusualbaseinstall />
+ <phpfile />
+ <executable />
+ <phpextension />
+ <config_vars />
+</role> \ No newline at end of file
diff --git a/includes/pear/PEAR/PackageFile.php b/includes/pear/PEAR/PackageFile.php
new file mode 100644
index 0000000..2a29145
--- /dev/null
+++ b/includes/pear/PEAR/PackageFile.php
@@ -0,0 +1,503 @@
+<?php
+/**
+ * PEAR_PackageFile, package.xml parsing utility class
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * needed for PEAR_VALIDATE_* constants
+ */
+require_once 'PEAR/Validate.php';
+/**
+ * Error code if the package.xml <package> tag does not contain a valid version
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION', 1);
+/**
+ * Error code if the package.xml <package> tag version is not supported (version 1.0 and 1.1 are the only supported versions,
+ * currently
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_PACKAGEVERSION', 2);
+/**
+ * Abstraction for the package.xml package description file
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @PEAR-VER@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_PackageFile
+{
+ /**
+ * @var PEAR_Config
+ */
+ var $_config;
+ var $_debug;
+
+ var $_logger = false;
+ /**
+ * @var boolean
+ */
+ var $_rawReturn = false;
+
+ /**
+ * helper for extracting Archive_Tar errors
+ * @var array
+ * @access private
+ */
+ var $_extractErrors = array();
+
+ /**
+ *
+ * @param PEAR_Config $config
+ * @param ? $debug
+ * @param string @tmpdir Optional temporary directory for uncompressing
+ * files
+ */
+ function PEAR_PackageFile(&$config, $debug = false)
+ {
+ $this->_config = $config;
+ $this->_debug = $debug;
+ }
+
+ /**
+ * Turn off validation - return a parsed package.xml without checking it
+ *
+ * This is used by the package-validate command
+ */
+ function rawReturn()
+ {
+ $this->_rawReturn = true;
+ }
+
+ function setLogger(&$l)
+ {
+ $this->_logger = &$l;
+ }
+
+ /**
+ * Create a PEAR_PackageFile_Parser_v* of a given version.
+ * @param int $version
+ * @return PEAR_PackageFile_Parser_v1|PEAR_PackageFile_Parser_v1
+ */
+ function &parserFactory($version)
+ {
+ if (!in_array($version{0}, array('1', '2'))) {
+ $a = false;
+ return $a;
+ }
+
+ include_once 'PEAR/PackageFile/Parser/v' . $version{0} . '.php';
+ $version = $version{0};
+ $class = "PEAR_PackageFile_Parser_v$version";
+ $a = new $class;
+ return $a;
+ }
+
+ /**
+ * For simpler unit-testing
+ * @return string
+ */
+ function getClassPrefix()
+ {
+ return 'PEAR_PackageFile_v';
+ }
+
+ /**
+ * Create a PEAR_PackageFile_v* of a given version.
+ * @param int $version
+ * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|false
+ */
+ function &factory($version)
+ {
+ if (!in_array($version{0}, array('1', '2'))) {
+ $a = false;
+ return $a;
+ }
+
+ include_once 'PEAR/PackageFile/v' . $version{0} . '.php';
+ $version = $version{0};
+ $class = $this->getClassPrefix() . $version;
+ $a = new $class;
+ return $a;
+ }
+
+ /**
+ * Create a PEAR_PackageFile_v* from its toArray() method
+ *
+ * WARNING: no validation is performed, the array is assumed to be valid,
+ * always parse from xml if you want validation.
+ * @param array $arr
+ * @return PEAR_PackageFileManager_v1|PEAR_PackageFileManager_v2|PEAR_Error
+ * @uses factory() to construct the returned object.
+ */
+ function &fromArray($arr)
+ {
+ if (isset($arr['xsdversion'])) {
+ $obj = &$this->factory($arr['xsdversion']);
+ if ($this->_logger) {
+ $obj->setLogger($this->_logger);
+ }
+
+ $obj->setConfig($this->_config);
+ $obj->fromArray($arr);
+ return $obj;
+ }
+
+ // The extensive tests are necessary due to changes in PHP 5.4.
+ if (is_array($arr)
+ && array_key_exists('package', $arr)
+ && is_array($arr['package'])
+ && array_key_exists('attribs', $arr['package'])
+ && is_array($arr['package']['attribs'])
+ && array_key_exists('version', $arr['package']['attribs'])
+ && !empty($arr['package']['attribs']['version']))
+ {
+ $obj = &$this->factory($arr['package']['attribs']['version']);
+ } else {
+ $obj = &$this->factory('1.0');
+ }
+ if (!$obj) {
+ return PEAR::raiseError('Invalid package version.');
+ }
+
+ if ($this->_logger) {
+ $obj->setLogger($this->_logger);
+ }
+
+ $obj->setConfig($this->_config);
+ $obj->fromArray($arr);
+ return $obj;
+ }
+
+ /**
+ * Create a PEAR_PackageFile_v* from an XML string.
+ * @access public
+ * @param string $data contents of package.xml file
+ * @param int $state package state (one of PEAR_VALIDATE_* constants)
+ * @param string $file full path to the package.xml file (and the files
+ * it references)
+ * @param string $archive optional name of the archive that the XML was
+ * extracted from, if any
+ * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * @uses parserFactory() to construct a parser to load the package.
+ */
+ function &fromXmlString($data, $state, $file, $archive = false)
+ {
+ if (preg_match('/<package[^>]+version=[\'"]([0-9]+\.[0-9]+)[\'"]/', $data, $packageversion)) {
+ if (!in_array($packageversion[1], array('1.0', '2.0', '2.1'))) {
+ return PEAR::raiseError('package.xml version "' . $packageversion[1] .
+ '" is not supported, only 1.0, 2.0, and 2.1 are supported.');
+ }
+
+ $object = &$this->parserFactory($packageversion[1]);
+ if ($this->_logger) {
+ $object->setLogger($this->_logger);
+ }
+
+ $object->setConfig($this->_config);
+ $pf = $object->parse($data, $file, $archive);
+ if (PEAR::isError($pf)) {
+ return $pf;
+ }
+
+ if ($this->_rawReturn) {
+ return $pf;
+ }
+
+ if (!$pf->validate($state)) {;
+ if ($this->_config->get('verbose') > 0
+ && $this->_logger && $pf->getValidationWarnings(false)
+ ) {
+ foreach ($pf->getValidationWarnings(false) as $warning) {
+ $this->_logger->log(0, 'ERROR: ' . $warning['message']);
+ }
+ }
+
+ $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed',
+ 2, null, null, $pf->getValidationWarnings());
+ return $a;
+ }
+
+ if ($this->_logger && $pf->getValidationWarnings(false)) {
+ foreach ($pf->getValidationWarnings() as $warning) {
+ $this->_logger->log(0, 'WARNING: ' . $warning['message']);
+ }
+ }
+
+ if (method_exists($pf, 'flattenFilelist')) {
+ $pf->flattenFilelist(); // for v2
+ }
+
+ return $pf;
+ } elseif (preg_match('/<package[^>]+version=[\'"]([^"\']+)[\'"]/', $data, $packageversion)) {
+ $a = PEAR::raiseError('package.xml file "' . $file .
+ '" has unsupported package.xml <package> version "' . $packageversion[1] . '"');
+ return $a;
+ } else {
+ if (!class_exists('PEAR_ErrorStack')) {
+ require_once 'PEAR/ErrorStack.php';
+ }
+
+ PEAR_ErrorStack::staticPush('PEAR_PackageFile',
+ PEAR_PACKAGEFILE_ERROR_NO_PACKAGEVERSION,
+ 'warning', array('xml' => $data), 'package.xml "' . $file .
+ '" has no package.xml <package> version');
+ $object = &$this->parserFactory('1.0');
+ $object->setConfig($this->_config);
+ $pf = $object->parse($data, $file, $archive);
+ if (PEAR::isError($pf)) {
+ return $pf;
+ }
+
+ if ($this->_rawReturn) {
+ return $pf;
+ }
+
+ if (!$pf->validate($state)) {
+ $a = PEAR::raiseError('Parsing of package.xml from file "' . $file . '" failed',
+ 2, null, null, $pf->getValidationWarnings());
+ return $a;
+ }
+
+ if ($this->_logger && $pf->getValidationWarnings(false)) {
+ foreach ($pf->getValidationWarnings() as $warning) {
+ $this->_logger->log(0, 'WARNING: ' . $warning['message']);
+ }
+ }
+
+ if (method_exists($pf, 'flattenFilelist')) {
+ $pf->flattenFilelist(); // for v2
+ }
+
+ return $pf;
+ }
+ }
+
+ /**
+ * Register a temporary file or directory. When the destructor is
+ * executed, all registered temporary files and directories are
+ * removed.
+ *
+ * @param string $file name of file or directory
+ * @return void
+ */
+ function addTempFile($file)
+ {
+ $GLOBALS['_PEAR_Common_tempfiles'][] = $file;
+ }
+
+ /**
+ * Create a PEAR_PackageFile_v* from a compresed Tar or Tgz file.
+ * @access public
+ * @param string contents of package.xml file
+ * @param int package state (one of PEAR_VALIDATE_* constants)
+ * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * @using Archive_Tar to extract the files
+ * @using fromPackageFile() to load the package after the package.xml
+ * file is extracted.
+ */
+ function &fromTgzFile($file, $state)
+ {
+ if (!class_exists('Archive_Tar')) {
+ require_once 'Archive/Tar.php';
+ }
+
+ $tar = new Archive_Tar($file);
+ if ($this->_debug <= 1) {
+ $tar->pushErrorHandling(PEAR_ERROR_RETURN);
+ }
+
+ $content = $tar->listContent();
+ if ($this->_debug <= 1) {
+ $tar->popErrorHandling();
+ }
+
+ if (!is_array($content)) {
+ if (is_string($file) && strlen($file < 255) &&
+ (!file_exists($file) || !@is_file($file))) {
+ $ret = PEAR::raiseError("could not open file \"$file\"");
+ return $ret;
+ }
+
+ $file = realpath($file);
+ $ret = PEAR::raiseError("Could not get contents of package \"$file\"".
+ '. Invalid tgz file.');
+ return $ret;
+ }
+
+ if (!count($content) && !@is_file($file)) {
+ $ret = PEAR::raiseError("could not open file \"$file\"");
+ return $ret;
+ }
+
+ $xml = null;
+ $origfile = $file;
+ foreach ($content as $file) {
+ $name = $file['filename'];
+ if ($name == 'package2.xml') { // allow a .tgz to distribute both versions
+ $xml = $name;
+ break;
+ }
+
+ if ($name == 'package.xml') {
+ $xml = $name;
+ break;
+ } elseif (preg_match('/package.xml$/', $name, $match)) {
+ $xml = $name;
+ break;
+ }
+ }
+
+ $tmpdir = System::mktemp('-t "' . $this->_config->get('temp_dir') . '" -d pear');
+ if ($tmpdir === false) {
+ $ret = PEAR::raiseError("there was a problem with getting the configured temp directory");
+ return $ret;
+ }
+
+ PEAR_PackageFile::addTempFile($tmpdir);
+
+ $this->_extractErrors();
+ PEAR::staticPushErrorHandling(PEAR_ERROR_CALLBACK, array($this, '_extractErrors'));
+
+ if (!$xml || !$tar->extractList(array($xml), $tmpdir)) {
+ $extra = implode("\n", $this->_extractErrors());
+ if ($extra) {
+ $extra = ' ' . $extra;
+ }
+
+ PEAR::staticPopErrorHandling();
+ $ret = PEAR::raiseError('could not extract the package.xml file from "' .
+ $origfile . '"' . $extra);
+ return $ret;
+ }
+
+ PEAR::staticPopErrorHandling();
+ $ret = &PEAR_PackageFile::fromPackageFile("$tmpdir/$xml", $state, $origfile);
+ return $ret;
+ }
+
+ /**
+ * helper callback for extracting Archive_Tar errors
+ *
+ * @param PEAR_Error|null $err
+ * @return array
+ * @access private
+ */
+ function _extractErrors($err = null)
+ {
+ static $errors = array();
+ if ($err === null) {
+ $e = $errors;
+ $errors = array();
+ return $e;
+ }
+ $errors[] = $err->getMessage();
+ }
+
+ /**
+ * Create a PEAR_PackageFile_v* from a package.xml file.
+ *
+ * @access public
+ * @param string $descfile name of package xml file
+ * @param int $state package state (one of PEAR_VALIDATE_* constants)
+ * @param string|false $archive name of the archive this package.xml came
+ * from, if any
+ * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * @uses PEAR_PackageFile::fromXmlString to create the oject after the
+ * XML is loaded from the package.xml file.
+ */
+ function &fromPackageFile($descfile, $state, $archive = false)
+ {
+ $fp = false;
+ if (is_string($descfile) && strlen($descfile) < 255 &&
+ (
+ !file_exists($descfile) || !is_file($descfile) || !is_readable($descfile)
+ || (!$fp = @fopen($descfile, 'r'))
+ )
+ ) {
+ $a = PEAR::raiseError("Unable to open $descfile");
+ return $a;
+ }
+
+ // read the whole thing so we only get one cdata callback
+ // for each block of cdata
+ fclose($fp);
+ $data = file_get_contents($descfile);
+ $ret = &PEAR_PackageFile::fromXmlString($data, $state, $descfile, $archive);
+ return $ret;
+ }
+
+ /**
+ * Create a PEAR_PackageFile_v* from a .tgz archive or package.xml file.
+ *
+ * This method is able to extract information about a package from a .tgz
+ * archive or from a XML package definition file.
+ *
+ * @access public
+ * @param string $info file name
+ * @param int $state package state (one of PEAR_VALIDATE_* constants)
+ * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * @uses fromPackageFile() if the file appears to be XML
+ * @uses fromTgzFile() to load all non-XML files
+ */
+ function &fromAnyFile($info, $state)
+ {
+ if (is_dir($info)) {
+ $dir_name = realpath($info);
+ if (file_exists($dir_name . '/package.xml')) {
+ $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package.xml', $state);
+ } elseif (file_exists($dir_name . '/package2.xml')) {
+ $info = PEAR_PackageFile::fromPackageFile($dir_name . '/package2.xml', $state);
+ } else {
+ $info = PEAR::raiseError("No package definition found in '$info' directory");
+ }
+
+ return $info;
+ }
+
+ $fp = false;
+ if (is_string($info) && strlen($info) < 255 &&
+ (file_exists($info) || ($fp = @fopen($info, 'r')))
+ ) {
+
+ if ($fp) {
+ fclose($fp);
+ }
+
+ $tmp = substr($info, -4);
+ if ($tmp == '.xml') {
+ $info = &PEAR_PackageFile::fromPackageFile($info, $state);
+ } elseif ($tmp == '.tar' || $tmp == '.tgz') {
+ $info = &PEAR_PackageFile::fromTgzFile($info, $state);
+ } else {
+ $fp = fopen($info, 'r');
+ $test = fread($fp, 5);
+ fclose($fp);
+ if ($test == '<?xml') {
+ $info = &PEAR_PackageFile::fromPackageFile($info, $state);
+ } else {
+ $info = &PEAR_PackageFile::fromTgzFile($info, $state);
+ }
+ }
+
+ return $info;
+ }
+
+ $info = PEAR::raiseError("Cannot open '$info' for parsing");
+ return $info;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/PackageFile/Generator/v1.php b/includes/pear/PEAR/PackageFile/Generator/v1.php
new file mode 100644
index 0000000..fdfdecd
--- /dev/null
+++ b/includes/pear/PEAR/PackageFile/Generator/v1.php
@@ -0,0 +1,1284 @@
+<?php
+/**
+ * package.xml generation class, package.xml version 1.0
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+/**
+ * needed for PEAR_VALIDATE_* constants
+ */
+require_once 'PEAR/Validate.php';
+require_once 'System.php';
+require_once 'PEAR/PackageFile/v2.php';
+/**
+ * This class converts a PEAR_PackageFile_v1 object into any output format.
+ *
+ * Supported output formats include array, XML string, and a PEAR_PackageFile_v2
+ * object, for converting package.xml 1.0 into package.xml 2.0 with no sweat.
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @PEAR-VER@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_PackageFile_Generator_v1
+{
+ /**
+ * @var PEAR_PackageFile_v1
+ */
+ var $_packagefile;
+ function PEAR_PackageFile_Generator_v1(&$packagefile)
+ {
+ $this->_packagefile = &$packagefile;
+ }
+
+ function getPackagerVersion()
+ {
+ return '1.9.4';
+ }
+
+ /**
+ * @param PEAR_Packager
+ * @param bool if true, a .tgz is written, otherwise a .tar is written
+ * @param string|null directory in which to save the .tgz
+ * @return string|PEAR_Error location of package or error object
+ */
+ function toTgz(&$packager, $compress = true, $where = null)
+ {
+ require_once 'Archive/Tar.php';
+ if ($where === null) {
+ if (!($where = System::mktemp(array('-d')))) {
+ return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: mktemp failed');
+ }
+ } elseif (!@System::mkDir(array('-p', $where))) {
+ return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: "' . $where . '" could' .
+ ' not be created');
+ }
+ if (file_exists($where . DIRECTORY_SEPARATOR . 'package.xml') &&
+ !is_file($where . DIRECTORY_SEPARATOR . 'package.xml')) {
+ return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: unable to save package.xml as' .
+ ' "' . $where . DIRECTORY_SEPARATOR . 'package.xml"');
+ }
+ if (!$this->_packagefile->validate(PEAR_VALIDATE_PACKAGING)) {
+ return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: invalid package file');
+ }
+ $pkginfo = $this->_packagefile->getArray();
+ $ext = $compress ? '.tgz' : '.tar';
+ $pkgver = $pkginfo['package'] . '-' . $pkginfo['version'];
+ $dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext;
+ if (file_exists(getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext) &&
+ !is_file(getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext)) {
+ return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: cannot create tgz file "' .
+ getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext . '"');
+ }
+ if ($pkgfile = $this->_packagefile->getPackageFile()) {
+ $pkgdir = dirname(realpath($pkgfile));
+ $pkgfile = basename($pkgfile);
+ } else {
+ return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: package file object must ' .
+ 'be created from a real file');
+ }
+ // {{{ Create the package file list
+ $filelist = array();
+ $i = 0;
+
+ foreach ($this->_packagefile->getFilelist() as $fname => $atts) {
+ $file = $pkgdir . DIRECTORY_SEPARATOR . $fname;
+ if (!file_exists($file)) {
+ return PEAR::raiseError("File does not exist: $fname");
+ } else {
+ $filelist[$i++] = $file;
+ if (!isset($atts['md5sum'])) {
+ $this->_packagefile->setFileAttribute($fname, 'md5sum', md5_file($file));
+ }
+ $packager->log(2, "Adding file $fname");
+ }
+ }
+ // }}}
+ $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, 'package.xml', true);
+ if ($packagexml) {
+ $tar =& new Archive_Tar($dest_package, $compress);
+ $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors
+ // ----- Creates with the package.xml file
+ $ok = $tar->createModify(array($packagexml), '', $where);
+ if (PEAR::isError($ok)) {
+ return $ok;
+ } elseif (!$ok) {
+ return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: tarball creation failed');
+ }
+ // ----- Add the content of the package
+ if (!$tar->addModify($filelist, $pkgver, $pkgdir)) {
+ return PEAR::raiseError('PEAR_Packagefile_v1::toTgz: tarball creation failed');
+ }
+ return $dest_package;
+ }
+ }
+
+ /**
+ * @param string|null directory to place the package.xml in, or null for a temporary dir
+ * @param int one of the PEAR_VALIDATE_* constants
+ * @param string name of the generated file
+ * @param bool if true, then no analysis will be performed on role="php" files
+ * @return string|PEAR_Error path to the created file on success
+ */
+ function toPackageFile($where = null, $state = PEAR_VALIDATE_NORMAL, $name = 'package.xml',
+ $nofilechecking = false)
+ {
+ if (!$this->_packagefile->validate($state, $nofilechecking)) {
+ return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: invalid package.xml',
+ null, null, null, $this->_packagefile->getValidationWarnings());
+ }
+ if ($where === null) {
+ if (!($where = System::mktemp(array('-d')))) {
+ return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: mktemp failed');
+ }
+ } elseif (!@System::mkDir(array('-p', $where))) {
+ return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: "' . $where . '" could' .
+ ' not be created');
+ }
+ $newpkgfile = $where . DIRECTORY_SEPARATOR . $name;
+ $np = @fopen($newpkgfile, 'wb');
+ if (!$np) {
+ return PEAR::raiseError('PEAR_Packagefile_v1::toPackageFile: unable to save ' .
+ "$name as $newpkgfile");
+ }
+ fwrite($np, $this->toXml($state, true));
+ fclose($np);
+ return $newpkgfile;
+ }
+
+ /**
+ * fix both XML encoding to be UTF8, and replace standard XML entities < > " & '
+ *
+ * @param string $string
+ * @return string
+ * @access private
+ */
+ function _fixXmlEncoding($string)
+ {
+ if (version_compare(phpversion(), '5.0.0', 'lt')) {
+ $string = utf8_encode($string);
+ }
+ return strtr($string, array(
+ '&' => '&amp;',
+ '>' => '&gt;',
+ '<' => '&lt;',
+ '"' => '&quot;',
+ '\'' => '&apos;' ));
+ }
+
+ /**
+ * Return an XML document based on the package info (as returned
+ * by the PEAR_Common::infoFrom* methods).
+ *
+ * @return string XML data
+ */
+ function toXml($state = PEAR_VALIDATE_NORMAL, $nofilevalidation = false)
+ {
+ $this->_packagefile->setDate(date('Y-m-d'));
+ if (!$this->_packagefile->validate($state, $nofilevalidation)) {
+ return false;
+ }
+ $pkginfo = $this->_packagefile->getArray();
+ static $maint_map = array(
+ "handle" => "user",
+ "name" => "name",
+ "email" => "email",
+ "role" => "role",
+ );
+ $ret = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
+ $ret .= "<!DOCTYPE package SYSTEM \"http://pear.php.net/dtd/package-1.0\">\n";
+ $ret .= "<package version=\"1.0\" packagerversion=\"1.9.4\">\n" .
+" <name>$pkginfo[package]</name>";
+ if (isset($pkginfo['extends'])) {
+ $ret .= "\n<extends>$pkginfo[extends]</extends>";
+ }
+ $ret .=
+ "\n <summary>".$this->_fixXmlEncoding($pkginfo['summary'])."</summary>\n" .
+" <description>".trim($this->_fixXmlEncoding($pkginfo['description']))."\n </description>\n" .
+" <maintainers>\n";
+ foreach ($pkginfo['maintainers'] as $maint) {
+ $ret .= " <maintainer>\n";
+ foreach ($maint_map as $idx => $elm) {
+ $ret .= " <$elm>";
+ $ret .= $this->_fixXmlEncoding($maint[$idx]);
+ $ret .= "</$elm>\n";
+ }
+ $ret .= " </maintainer>\n";
+ }
+ $ret .= " </maintainers>\n";
+ $ret .= $this->_makeReleaseXml($pkginfo, false, $state);
+ if (isset($pkginfo['changelog']) && count($pkginfo['changelog']) > 0) {
+ $ret .= " <changelog>\n";
+ foreach ($pkginfo['changelog'] as $oldrelease) {
+ $ret .= $this->_makeReleaseXml($oldrelease, true);
+ }
+ $ret .= " </changelog>\n";
+ }
+ $ret .= "</package>\n";
+ return $ret;
+ }
+
+ // }}}
+ // {{{ _makeReleaseXml()
+
+ /**
+ * Generate part of an XML description with release information.
+ *
+ * @param array $pkginfo array with release information
+ * @param bool $changelog whether the result will be in a changelog element
+ *
+ * @return string XML data
+ *
+ * @access private
+ */
+ function _makeReleaseXml($pkginfo, $changelog = false, $state = PEAR_VALIDATE_NORMAL)
+ {
+ // XXX QUOTE ENTITIES IN PCDATA, OR EMBED IN CDATA BLOCKS!!
+ $indent = $changelog ? " " : "";
+ $ret = "$indent <release>\n";
+ if (!empty($pkginfo['version'])) {
+ $ret .= "$indent <version>$pkginfo[version]</version>\n";
+ }
+ if (!empty($pkginfo['release_date'])) {
+ $ret .= "$indent <date>$pkginfo[release_date]</date>\n";
+ }
+ if (!empty($pkginfo['release_license'])) {
+ $ret .= "$indent <license>$pkginfo[release_license]</license>\n";
+ }
+ if (!empty($pkginfo['release_state'])) {
+ $ret .= "$indent <state>$pkginfo[release_state]</state>\n";
+ }
+ if (!empty($pkginfo['release_notes'])) {
+ $ret .= "$indent <notes>".trim($this->_fixXmlEncoding($pkginfo['release_notes']))
+ ."\n$indent </notes>\n";
+ }
+ if (!empty($pkginfo['release_warnings'])) {
+ $ret .= "$indent <warnings>".$this->_fixXmlEncoding($pkginfo['release_warnings'])."</warnings>\n";
+ }
+ if (isset($pkginfo['release_deps']) && sizeof($pkginfo['release_deps']) > 0) {
+ $ret .= "$indent <deps>\n";
+ foreach ($pkginfo['release_deps'] as $dep) {
+ $ret .= "$indent <dep type=\"$dep[type]\" rel=\"$dep[rel]\"";
+ if (isset($dep['version'])) {
+ $ret .= " version=\"$dep[version]\"";
+ }
+ if (isset($dep['optional'])) {
+ $ret .= " optional=\"$dep[optional]\"";
+ }
+ if (isset($dep['name'])) {
+ $ret .= ">$dep[name]</dep>\n";
+ } else {
+ $ret .= "/>\n";
+ }
+ }
+ $ret .= "$indent </deps>\n";
+ }
+ if (isset($pkginfo['configure_options'])) {
+ $ret .= "$indent <configureoptions>\n";
+ foreach ($pkginfo['configure_options'] as $c) {
+ $ret .= "$indent <configureoption name=\"".
+ $this->_fixXmlEncoding($c['name']) . "\"";
+ if (isset($c['default'])) {
+ $ret .= " default=\"" . $this->_fixXmlEncoding($c['default']) . "\"";
+ }
+ $ret .= " prompt=\"" . $this->_fixXmlEncoding($c['prompt']) . "\"";
+ $ret .= "/>\n";
+ }
+ $ret .= "$indent </configureoptions>\n";
+ }
+ if (isset($pkginfo['provides'])) {
+ foreach ($pkginfo['provides'] as $key => $what) {
+ $ret .= "$indent <provides type=\"$what[type]\" ";
+ $ret .= "name=\"$what[name]\" ";
+ if (isset($what['extends'])) {
+ $ret .= "extends=\"$what[extends]\" ";
+ }
+ $ret .= "/>\n";
+ }
+ }
+ if (isset($pkginfo['filelist'])) {
+ $ret .= "$indent <filelist>\n";
+ if ($state ^ PEAR_VALIDATE_PACKAGING) {
+ $ret .= $this->recursiveXmlFilelist($pkginfo['filelist']);
+ } else {
+ foreach ($pkginfo['filelist'] as $file => $fa) {
+ if (!isset($fa['role'])) {
+ $fa['role'] = '';
+ }
+ $ret .= "$indent <file role=\"$fa[role]\"";
+ if (isset($fa['baseinstalldir'])) {
+ $ret .= ' baseinstalldir="' .
+ $this->_fixXmlEncoding($fa['baseinstalldir']) . '"';
+ }
+ if (isset($fa['md5sum'])) {
+ $ret .= " md5sum=\"$fa[md5sum]\"";
+ }
+ if (isset($fa['platform'])) {
+ $ret .= " platform=\"$fa[platform]\"";
+ }
+ if (!empty($fa['install-as'])) {
+ $ret .= ' install-as="' .
+ $this->_fixXmlEncoding($fa['install-as']) . '"';
+ }
+ $ret .= ' name="' . $this->_fixXmlEncoding($file) . '"';
+ if (empty($fa['replacements'])) {
+ $ret .= "/>\n";
+ } else {
+ $ret .= ">\n";
+ foreach ($fa['replacements'] as $r) {
+ $ret .= "$indent <replace";
+ foreach ($r as $k => $v) {
+ $ret .= " $k=\"" . $this->_fixXmlEncoding($v) .'"';
+ }
+ $ret .= "/>\n";
+ }
+ $ret .= "$indent </file>\n";
+ }
+ }
+ }
+ $ret .= "$indent </filelist>\n";
+ }
+ $ret .= "$indent </release>\n";
+ return $ret;
+ }
+
+ /**
+ * @param array
+ * @access protected
+ */
+ function recursiveXmlFilelist($list)
+ {
+ $this->_dirs = array();
+ foreach ($list as $file => $attributes) {
+ $this->_addDir($this->_dirs, explode('/', dirname($file)), $file, $attributes);
+ }
+ return $this->_formatDir($this->_dirs);
+ }
+
+ /**
+ * @param array
+ * @param array
+ * @param string|null
+ * @param array|null
+ * @access private
+ */
+ function _addDir(&$dirs, $dir, $file = null, $attributes = null)
+ {
+ if ($dir == array() || $dir == array('.')) {
+ $dirs['files'][basename($file)] = $attributes;
+ return;
+ }
+ $curdir = array_shift($dir);
+ if (!isset($dirs['dirs'][$curdir])) {
+ $dirs['dirs'][$curdir] = array();
+ }
+ $this->_addDir($dirs['dirs'][$curdir], $dir, $file, $attributes);
+ }
+
+ /**
+ * @param array
+ * @param string
+ * @param string
+ * @access private
+ */
+ function _formatDir($dirs, $indent = '', $curdir = '')
+ {
+ $ret = '';
+ if (!count($dirs)) {
+ return '';
+ }
+ if (isset($dirs['dirs'])) {
+ uksort($dirs['dirs'], 'strnatcasecmp');
+ foreach ($dirs['dirs'] as $dir => $contents) {
+ $usedir = "$curdir/$dir";
+ $ret .= "$indent <dir name=\"$dir\">\n";
+ $ret .= $this->_formatDir($contents, "$indent ", $usedir);
+ $ret .= "$indent </dir> <!-- $usedir -->\n";
+ }
+ }
+ if (isset($dirs['files'])) {
+ uksort($dirs['files'], 'strnatcasecmp');
+ foreach ($dirs['files'] as $file => $attribs) {
+ $ret .= $this->_formatFile($file, $attribs, $indent);
+ }
+ }
+ return $ret;
+ }
+
+ /**
+ * @param string
+ * @param array
+ * @param string
+ * @access private
+ */
+ function _formatFile($file, $attributes, $indent)
+ {
+ $ret = "$indent <file role=\"$attributes[role]\"";
+ if (isset($attributes['baseinstalldir'])) {
+ $ret .= ' baseinstalldir="' .
+ $this->_fixXmlEncoding($attributes['baseinstalldir']) . '"';
+ }
+ if (isset($attributes['md5sum'])) {
+ $ret .= " md5sum=\"$attributes[md5sum]\"";
+ }
+ if (isset($attributes['platform'])) {
+ $ret .= " platform=\"$attributes[platform]\"";
+ }
+ if (!empty($attributes['install-as'])) {
+ $ret .= ' install-as="' .
+ $this->_fixXmlEncoding($attributes['install-as']) . '"';
+ }
+ $ret .= ' name="' . $this->_fixXmlEncoding($file) . '"';
+ if (empty($attributes['replacements'])) {
+ $ret .= "/>\n";
+ } else {
+ $ret .= ">\n";
+ foreach ($attributes['replacements'] as $r) {
+ $ret .= "$indent <replace";
+ foreach ($r as $k => $v) {
+ $ret .= " $k=\"" . $this->_fixXmlEncoding($v) .'"';
+ }
+ $ret .= "/>\n";
+ }
+ $ret .= "$indent </file>\n";
+ }
+ return $ret;
+ }
+
+ // {{{ _unIndent()
+
+ /**
+ * Unindent given string (?)
+ *
+ * @param string $str The string that has to be unindented.
+ * @return string
+ * @access private
+ */
+ function _unIndent($str)
+ {
+ // remove leading newlines
+ $str = preg_replace('/^[\r\n]+/', '', $str);
+ // find whitespace at the beginning of the first line
+ $indent_len = strspn($str, " \t");
+ $indent = substr($str, 0, $indent_len);
+ $data = '';
+ // remove the same amount of whitespace from following lines
+ foreach (explode("\n", $str) as $line) {
+ if (substr($line, 0, $indent_len) == $indent) {
+ $data .= substr($line, $indent_len) . "\n";
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * @return array
+ */
+ function dependenciesToV2()
+ {
+ $arr = array();
+ $this->_convertDependencies2_0($arr);
+ return $arr['dependencies'];
+ }
+
+ /**
+ * Convert a package.xml version 1.0 into version 2.0
+ *
+ * Note that this does a basic conversion, to allow more advanced
+ * features like bundles and multiple releases
+ * @param string the classname to instantiate and return. This must be
+ * PEAR_PackageFile_v2 or a descendant
+ * @param boolean if true, only valid, deterministic package.xml 1.0 as defined by the
+ * strictest parameters will be converted
+ * @return PEAR_PackageFile_v2|PEAR_Error
+ */
+ function &toV2($class = 'PEAR_PackageFile_v2', $strict = false)
+ {
+ if ($strict) {
+ if (!$this->_packagefile->validate()) {
+ $a = PEAR::raiseError('invalid package.xml version 1.0 cannot be converted' .
+ ' to version 2.0', null, null, null,
+ $this->_packagefile->getValidationWarnings(true));
+ return $a;
+ }
+ }
+
+ $arr = array(
+ 'attribs' => array(
+ 'version' => '2.0',
+ 'xmlns' => 'http://pear.php.net/dtd/package-2.0',
+ 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0',
+ 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
+ 'xsi:schemaLocation' => "http://pear.php.net/dtd/tasks-1.0\n" .
+"http://pear.php.net/dtd/tasks-1.0.xsd\n" .
+"http://pear.php.net/dtd/package-2.0\n" .
+'http://pear.php.net/dtd/package-2.0.xsd',
+ ),
+ 'name' => $this->_packagefile->getPackage(),
+ 'channel' => 'pear.php.net',
+ );
+ $arr['summary'] = $this->_packagefile->getSummary();
+ $arr['description'] = $this->_packagefile->getDescription();
+ $maintainers = $this->_packagefile->getMaintainers();
+ foreach ($maintainers as $maintainer) {
+ if ($maintainer['role'] != 'lead') {
+ continue;
+ }
+ $new = array(
+ 'name' => $maintainer['name'],
+ 'user' => $maintainer['handle'],
+ 'email' => $maintainer['email'],
+ 'active' => 'yes',
+ );
+ $arr['lead'][] = $new;
+ }
+
+ if (!isset($arr['lead'])) { // some people... you know?
+ $arr['lead'] = array(
+ 'name' => 'unknown',
+ 'user' => 'unknown',
+ 'email' => 'noleadmaintainer@example.com',
+ 'active' => 'no',
+ );
+ }
+
+ if (count($arr['lead']) == 1) {
+ $arr['lead'] = $arr['lead'][0];
+ }
+
+ foreach ($maintainers as $maintainer) {
+ if ($maintainer['role'] == 'lead') {
+ continue;
+ }
+ $new = array(
+ 'name' => $maintainer['name'],
+ 'user' => $maintainer['handle'],
+ 'email' => $maintainer['email'],
+ 'active' => 'yes',
+ );
+ $arr[$maintainer['role']][] = $new;
+ }
+
+ if (isset($arr['developer']) && count($arr['developer']) == 1) {
+ $arr['developer'] = $arr['developer'][0];
+ }
+
+ if (isset($arr['contributor']) && count($arr['contributor']) == 1) {
+ $arr['contributor'] = $arr['contributor'][0];
+ }
+
+ if (isset($arr['helper']) && count($arr['helper']) == 1) {
+ $arr['helper'] = $arr['helper'][0];
+ }
+
+ $arr['date'] = $this->_packagefile->getDate();
+ $arr['version'] =
+ array(
+ 'release' => $this->_packagefile->getVersion(),
+ 'api' => $this->_packagefile->getVersion(),
+ );
+ $arr['stability'] =
+ array(
+ 'release' => $this->_packagefile->getState(),
+ 'api' => $this->_packagefile->getState(),
+ );
+ $licensemap =
+ array(
+ 'php' => 'http://www.php.net/license',
+ 'php license' => 'http://www.php.net/license',
+ 'lgpl' => 'http://www.gnu.org/copyleft/lesser.html',
+ 'bsd' => 'http://www.opensource.org/licenses/bsd-license.php',
+ 'bsd style' => 'http://www.opensource.org/licenses/bsd-license.php',
+ 'bsd-style' => 'http://www.opensource.org/licenses/bsd-license.php',
+ 'mit' => 'http://www.opensource.org/licenses/mit-license.php',
+ 'gpl' => 'http://www.gnu.org/copyleft/gpl.html',
+ 'apache' => 'http://www.opensource.org/licenses/apache2.0.php'
+ );
+
+ if (isset($licensemap[strtolower($this->_packagefile->getLicense())])) {
+ $arr['license'] = array(
+ 'attribs' => array('uri' =>
+ $licensemap[strtolower($this->_packagefile->getLicense())]),
+ '_content' => $this->_packagefile->getLicense()
+ );
+ } else {
+ // don't use bogus uri
+ $arr['license'] = $this->_packagefile->getLicense();
+ }
+
+ $arr['notes'] = $this->_packagefile->getNotes();
+ $temp = array();
+ $arr['contents'] = $this->_convertFilelist2_0($temp);
+ $this->_convertDependencies2_0($arr);
+ $release = ($this->_packagefile->getConfigureOptions() || $this->_isExtension) ?
+ 'extsrcrelease' : 'phprelease';
+ if ($release == 'extsrcrelease') {
+ $arr['channel'] = 'pecl.php.net';
+ $arr['providesextension'] = $arr['name']; // assumption
+ }
+
+ $arr[$release] = array();
+ if ($this->_packagefile->getConfigureOptions()) {
+ $arr[$release]['configureoption'] = $this->_packagefile->getConfigureOptions();
+ foreach ($arr[$release]['configureoption'] as $i => $opt) {
+ $arr[$release]['configureoption'][$i] = array('attribs' => $opt);
+ }
+ if (count($arr[$release]['configureoption']) == 1) {
+ $arr[$release]['configureoption'] = $arr[$release]['configureoption'][0];
+ }
+ }
+
+ $this->_convertRelease2_0($arr[$release], $temp);
+ if ($release == 'extsrcrelease' && count($arr[$release]) > 1) {
+ // multiple extsrcrelease tags added in PEAR 1.4.1
+ $arr['dependencies']['required']['pearinstaller']['min'] = '1.4.1';
+ }
+
+ if ($cl = $this->_packagefile->getChangelog()) {
+ foreach ($cl as $release) {
+ $rel = array();
+ $rel['version'] =
+ array(
+ 'release' => $release['version'],
+ 'api' => $release['version'],
+ );
+ if (!isset($release['release_state'])) {
+ $release['release_state'] = 'stable';
+ }
+
+ $rel['stability'] =
+ array(
+ 'release' => $release['release_state'],
+ 'api' => $release['release_state'],
+ );
+ if (isset($release['release_date'])) {
+ $rel['date'] = $release['release_date'];
+ } else {
+ $rel['date'] = date('Y-m-d');
+ }
+
+ if (isset($release['release_license'])) {
+ if (isset($licensemap[strtolower($release['release_license'])])) {
+ $uri = $licensemap[strtolower($release['release_license'])];
+ } else {
+ $uri = 'http://www.example.com';
+ }
+ $rel['license'] = array(
+ 'attribs' => array('uri' => $uri),
+ '_content' => $release['release_license']
+ );
+ } else {
+ $rel['license'] = $arr['license'];
+ }
+
+ if (!isset($release['release_notes'])) {
+ $release['release_notes'] = 'no release notes';
+ }
+
+ $rel['notes'] = $release['release_notes'];
+ $arr['changelog']['release'][] = $rel;
+ }
+ }
+
+ $ret = new $class;
+ $ret->setConfig($this->_packagefile->_config);
+ if (isset($this->_packagefile->_logger) && is_object($this->_packagefile->_logger)) {
+ $ret->setLogger($this->_packagefile->_logger);
+ }
+
+ $ret->fromArray($arr);
+ return $ret;
+ }
+
+ /**
+ * @param array
+ * @param bool
+ * @access private
+ */
+ function _convertDependencies2_0(&$release, $internal = false)
+ {
+ $peardep = array('pearinstaller' =>
+ array('min' => '1.4.0b1')); // this is a lot safer
+ $required = $optional = array();
+ $release['dependencies'] = array('required' => array());
+ if ($this->_packagefile->hasDeps()) {
+ foreach ($this->_packagefile->getDeps() as $dep) {
+ if (!isset($dep['optional']) || $dep['optional'] == 'no') {
+ $required[] = $dep;
+ } else {
+ $optional[] = $dep;
+ }
+ }
+ foreach (array('required', 'optional') as $arr) {
+ $deps = array();
+ foreach ($$arr as $dep) {
+ // organize deps by dependency type and name
+ if (!isset($deps[$dep['type']])) {
+ $deps[$dep['type']] = array();
+ }
+ if (isset($dep['name'])) {
+ $deps[$dep['type']][$dep['name']][] = $dep;
+ } else {
+ $deps[$dep['type']][] = $dep;
+ }
+ }
+ do {
+ if (isset($deps['php'])) {
+ $php = array();
+ if (count($deps['php']) > 1) {
+ $php = $this->_processPhpDeps($deps['php']);
+ } else {
+ if (!isset($deps['php'][0])) {
+ list($key, $blah) = each ($deps['php']); // stupid buggy versions
+ $deps['php'] = array($blah[0]);
+ }
+ $php = $this->_processDep($deps['php'][0]);
+ if (!$php) {
+ break; // poor mans throw
+ }
+ }
+ $release['dependencies'][$arr]['php'] = $php;
+ }
+ } while (false);
+ do {
+ if (isset($deps['pkg'])) {
+ $pkg = array();
+ $pkg = $this->_processMultipleDepsName($deps['pkg']);
+ if (!$pkg) {
+ break; // poor mans throw
+ }
+ $release['dependencies'][$arr]['package'] = $pkg;
+ }
+ } while (false);
+ do {
+ if (isset($deps['ext'])) {
+ $pkg = array();
+ $pkg = $this->_processMultipleDepsName($deps['ext']);
+ $release['dependencies'][$arr]['extension'] = $pkg;
+ }
+ } while (false);
+ // skip sapi - it's not supported so nobody will have used it
+ // skip os - it's not supported in 1.0
+ }
+ }
+ if (isset($release['dependencies']['required'])) {
+ $release['dependencies']['required'] =
+ array_merge($peardep, $release['dependencies']['required']);
+ } else {
+ $release['dependencies']['required'] = $peardep;
+ }
+ if (!isset($release['dependencies']['required']['php'])) {
+ $release['dependencies']['required']['php'] =
+ array('min' => '4.0.0');
+ }
+ $order = array();
+ $bewm = $release['dependencies']['required'];
+ $order['php'] = $bewm['php'];
+ $order['pearinstaller'] = $bewm['pearinstaller'];
+ isset($bewm['package']) ? $order['package'] = $bewm['package'] :0;
+ isset($bewm['extension']) ? $order['extension'] = $bewm['extension'] :0;
+ $release['dependencies']['required'] = $order;
+ }
+
+ /**
+ * @param array
+ * @access private
+ */
+ function _convertFilelist2_0(&$package)
+ {
+ $ret = array('dir' =>
+ array(
+ 'attribs' => array('name' => '/'),
+ 'file' => array()
+ )
+ );
+ $package['platform'] =
+ $package['install-as'] = array();
+ $this->_isExtension = false;
+ foreach ($this->_packagefile->getFilelist() as $name => $file) {
+ $file['name'] = $name;
+ if (isset($file['role']) && $file['role'] == 'src') {
+ $this->_isExtension = true;
+ }
+ if (isset($file['replacements'])) {
+ $repl = $file['replacements'];
+ unset($file['replacements']);
+ } else {
+ unset($repl);
+ }
+ if (isset($file['install-as'])) {
+ $package['install-as'][$name] = $file['install-as'];
+ unset($file['install-as']);
+ }
+ if (isset($file['platform'])) {
+ $package['platform'][$name] = $file['platform'];
+ unset($file['platform']);
+ }
+ $file = array('attribs' => $file);
+ if (isset($repl)) {
+ foreach ($repl as $replace ) {
+ $file['tasks:replace'][] = array('attribs' => $replace);
+ }
+ if (count($repl) == 1) {
+ $file['tasks:replace'] = $file['tasks:replace'][0];
+ }
+ }
+ $ret['dir']['file'][] = $file;
+ }
+ return $ret;
+ }
+
+ /**
+ * Post-process special files with install-as/platform attributes and
+ * make the release tag.
+ *
+ * This complex method follows this work-flow to create the release tags:
+ *
+ * <pre>
+ * - if any install-as/platform exist, create a generic release and fill it with
+ * o <install as=..> tags for <file name=... install-as=...>
+ * o <install as=..> tags for <file name=... platform=!... install-as=..>
+ * o <ignore> tags for <file name=... platform=...>
+ * o <ignore> tags for <file name=... platform=... install-as=..>
+ * - create a release for each platform encountered and fill with
+ * o <install as..> tags for <file name=... install-as=...>
+ * o <install as..> tags for <file name=... platform=this platform install-as=..>
+ * o <install as..> tags for <file name=... platform=!other platform install-as=..>
+ * o <ignore> tags for <file name=... platform=!this platform>
+ * o <ignore> tags for <file name=... platform=other platform>
+ * o <ignore> tags for <file name=... platform=other platform install-as=..>
+ * o <ignore> tags for <file name=... platform=!this platform install-as=..>
+ * </pre>
+ *
+ * It does this by accessing the $package parameter, which contains an array with
+ * indices:
+ *
+ * - platform: mapping of file => OS the file should be installed on
+ * - install-as: mapping of file => installed name
+ * - osmap: mapping of OS => list of files that should be installed
+ * on that OS
+ * - notosmap: mapping of OS => list of files that should not be
+ * installed on that OS
+ *
+ * @param array
+ * @param array
+ * @access private
+ */
+ function _convertRelease2_0(&$release, $package)
+ {
+ //- if any install-as/platform exist, create a generic release and fill it with
+ if (count($package['platform']) || count($package['install-as'])) {
+ $generic = array();
+ $genericIgnore = array();
+ foreach ($package['install-as'] as $file => $as) {
+ //o <install as=..> tags for <file name=... install-as=...>
+ if (!isset($package['platform'][$file])) {
+ $generic[] = $file;
+ continue;
+ }
+ //o <install as=..> tags for <file name=... platform=!... install-as=..>
+ if (isset($package['platform'][$file]) &&
+ $package['platform'][$file]{0} == '!') {
+ $generic[] = $file;
+ continue;
+ }
+ //o <ignore> tags for <file name=... platform=... install-as=..>
+ if (isset($package['platform'][$file]) &&
+ $package['platform'][$file]{0} != '!') {
+ $genericIgnore[] = $file;
+ continue;
+ }
+ }
+ foreach ($package['platform'] as $file => $platform) {
+ if (isset($package['install-as'][$file])) {
+ continue;
+ }
+ if ($platform{0} != '!') {
+ //o <ignore> tags for <file name=... platform=...>
+ $genericIgnore[] = $file;
+ }
+ }
+ if (count($package['platform'])) {
+ $oses = $notplatform = $platform = array();
+ foreach ($package['platform'] as $file => $os) {
+ // get a list of oses
+ if ($os{0} == '!') {
+ if (isset($oses[substr($os, 1)])) {
+ continue;
+ }
+ $oses[substr($os, 1)] = count($oses);
+ } else {
+ if (isset($oses[$os])) {
+ continue;
+ }
+ $oses[$os] = count($oses);
+ }
+ }
+ //- create a release for each platform encountered and fill with
+ foreach ($oses as $os => $releaseNum) {
+ $release[$releaseNum]['installconditions']['os']['name'] = $os;
+ $release[$releaseNum]['filelist'] = array('install' => array(),
+ 'ignore' => array());
+ foreach ($package['install-as'] as $file => $as) {
+ //o <install as=..> tags for <file name=... install-as=...>
+ if (!isset($package['platform'][$file])) {
+ $release[$releaseNum]['filelist']['install'][] =
+ array(
+ 'attribs' => array(
+ 'name' => $file,
+ 'as' => $as,
+ ),
+ );
+ continue;
+ }
+ //o <install as..> tags for
+ // <file name=... platform=this platform install-as=..>
+ if (isset($package['platform'][$file]) &&
+ $package['platform'][$file] == $os) {
+ $release[$releaseNum]['filelist']['install'][] =
+ array(
+ 'attribs' => array(
+ 'name' => $file,
+ 'as' => $as,
+ ),
+ );
+ continue;
+ }
+ //o <install as..> tags for
+ // <file name=... platform=!other platform install-as=..>
+ if (isset($package['platform'][$file]) &&
+ $package['platform'][$file] != "!$os" &&
+ $package['platform'][$file]{0} == '!') {
+ $release[$releaseNum]['filelist']['install'][] =
+ array(
+ 'attribs' => array(
+ 'name' => $file,
+ 'as' => $as,
+ ),
+ );
+ continue;
+ }
+ //o <ignore> tags for
+ // <file name=... platform=!this platform install-as=..>
+ if (isset($package['platform'][$file]) &&
+ $package['platform'][$file] == "!$os") {
+ $release[$releaseNum]['filelist']['ignore'][] =
+ array(
+ 'attribs' => array(
+ 'name' => $file,
+ ),
+ );
+ continue;
+ }
+ //o <ignore> tags for
+ // <file name=... platform=other platform install-as=..>
+ if (isset($package['platform'][$file]) &&
+ $package['platform'][$file]{0} != '!' &&
+ $package['platform'][$file] != $os) {
+ $release[$releaseNum]['filelist']['ignore'][] =
+ array(
+ 'attribs' => array(
+ 'name' => $file,
+ ),
+ );
+ continue;
+ }
+ }
+ foreach ($package['platform'] as $file => $platform) {
+ if (isset($package['install-as'][$file])) {
+ continue;
+ }
+ //o <ignore> tags for <file name=... platform=!this platform>
+ if ($platform == "!$os") {
+ $release[$releaseNum]['filelist']['ignore'][] =
+ array(
+ 'attribs' => array(
+ 'name' => $file,
+ ),
+ );
+ continue;
+ }
+ //o <ignore> tags for <file name=... platform=other platform>
+ if ($platform{0} != '!' && $platform != $os) {
+ $release[$releaseNum]['filelist']['ignore'][] =
+ array(
+ 'attribs' => array(
+ 'name' => $file,
+ ),
+ );
+ }
+ }
+ if (!count($release[$releaseNum]['filelist']['install'])) {
+ unset($release[$releaseNum]['filelist']['install']);
+ }
+ if (!count($release[$releaseNum]['filelist']['ignore'])) {
+ unset($release[$releaseNum]['filelist']['ignore']);
+ }
+ }
+ if (count($generic) || count($genericIgnore)) {
+ $release[count($oses)] = array();
+ if (count($generic)) {
+ foreach ($generic as $file) {
+ if (isset($package['install-as'][$file])) {
+ $installas = $package['install-as'][$file];
+ } else {
+ $installas = $file;
+ }
+ $release[count($oses)]['filelist']['install'][] =
+ array(
+ 'attribs' => array(
+ 'name' => $file,
+ 'as' => $installas,
+ )
+ );
+ }
+ }
+ if (count($genericIgnore)) {
+ foreach ($genericIgnore as $file) {
+ $release[count($oses)]['filelist']['ignore'][] =
+ array(
+ 'attribs' => array(
+ 'name' => $file,
+ )
+ );
+ }
+ }
+ }
+ // cleanup
+ foreach ($release as $i => $rel) {
+ if (isset($rel['filelist']['install']) &&
+ count($rel['filelist']['install']) == 1) {
+ $release[$i]['filelist']['install'] =
+ $release[$i]['filelist']['install'][0];
+ }
+ if (isset($rel['filelist']['ignore']) &&
+ count($rel['filelist']['ignore']) == 1) {
+ $release[$i]['filelist']['ignore'] =
+ $release[$i]['filelist']['ignore'][0];
+ }
+ }
+ if (count($release) == 1) {
+ $release = $release[0];
+ }
+ } else {
+ // no platform atts, but some install-as atts
+ foreach ($package['install-as'] as $file => $value) {
+ $release['filelist']['install'][] =
+ array(
+ 'attribs' => array(
+ 'name' => $file,
+ 'as' => $value
+ )
+ );
+ }
+ if (count($release['filelist']['install']) == 1) {
+ $release['filelist']['install'] = $release['filelist']['install'][0];
+ }
+ }
+ }
+ }
+
+ /**
+ * @param array
+ * @return array
+ * @access private
+ */
+ function _processDep($dep)
+ {
+ if ($dep['type'] == 'php') {
+ if ($dep['rel'] == 'has') {
+ // come on - everyone has php!
+ return false;
+ }
+ }
+ $php = array();
+ if ($dep['type'] != 'php') {
+ $php['name'] = $dep['name'];
+ if ($dep['type'] == 'pkg') {
+ $php['channel'] = 'pear.php.net';
+ }
+ }
+ switch ($dep['rel']) {
+ case 'gt' :
+ $php['min'] = $dep['version'];
+ $php['exclude'] = $dep['version'];
+ break;
+ case 'ge' :
+ if (!isset($dep['version'])) {
+ if ($dep['type'] == 'php') {
+ if (isset($dep['name'])) {
+ $dep['version'] = $dep['name'];
+ }
+ }
+ }
+ $php['min'] = $dep['version'];
+ break;
+ case 'lt' :
+ $php['max'] = $dep['version'];
+ $php['exclude'] = $dep['version'];
+ break;
+ case 'le' :
+ $php['max'] = $dep['version'];
+ break;
+ case 'eq' :
+ $php['min'] = $dep['version'];
+ $php['max'] = $dep['version'];
+ break;
+ case 'ne' :
+ $php['exclude'] = $dep['version'];
+ break;
+ case 'not' :
+ $php['conflicts'] = 'yes';
+ break;
+ }
+ return $php;
+ }
+
+ /**
+ * @param array
+ * @return array
+ */
+ function _processPhpDeps($deps)
+ {
+ $test = array();
+ foreach ($deps as $dep) {
+ $test[] = $this->_processDep($dep);
+ }
+ $min = array();
+ $max = array();
+ foreach ($test as $dep) {
+ if (!$dep) {
+ continue;
+ }
+ if (isset($dep['min'])) {
+ $min[$dep['min']] = count($min);
+ }
+ if (isset($dep['max'])) {
+ $max[$dep['max']] = count($max);
+ }
+ }
+ if (count($min) > 0) {
+ uksort($min, 'version_compare');
+ }
+ if (count($max) > 0) {
+ uksort($max, 'version_compare');
+ }
+ if (count($min)) {
+ // get the highest minimum
+ $min = array_pop($a = array_flip($min));
+ } else {
+ $min = false;
+ }
+ if (count($max)) {
+ // get the lowest maximum
+ $max = array_shift($a = array_flip($max));
+ } else {
+ $max = false;
+ }
+ if ($min) {
+ $php['min'] = $min;
+ }
+ if ($max) {
+ $php['max'] = $max;
+ }
+ $exclude = array();
+ foreach ($test as $dep) {
+ if (!isset($dep['exclude'])) {
+ continue;
+ }
+ $exclude[] = $dep['exclude'];
+ }
+ if (count($exclude)) {
+ $php['exclude'] = $exclude;
+ }
+ return $php;
+ }
+
+ /**
+ * process multiple dependencies that have a name, like package deps
+ * @param array
+ * @return array
+ * @access private
+ */
+ function _processMultipleDepsName($deps)
+ {
+ $ret = $tests = array();
+ foreach ($deps as $name => $dep) {
+ foreach ($dep as $d) {
+ $tests[$name][] = $this->_processDep($d);
+ }
+ }
+
+ foreach ($tests as $name => $test) {
+ $max = $min = $php = array();
+ $php['name'] = $name;
+ foreach ($test as $dep) {
+ if (!$dep) {
+ continue;
+ }
+ if (isset($dep['channel'])) {
+ $php['channel'] = 'pear.php.net';
+ }
+ if (isset($dep['conflicts']) && $dep['conflicts'] == 'yes') {
+ $php['conflicts'] = 'yes';
+ }
+ if (isset($dep['min'])) {
+ $min[$dep['min']] = count($min);
+ }
+ if (isset($dep['max'])) {
+ $max[$dep['max']] = count($max);
+ }
+ }
+ if (count($min) > 0) {
+ uksort($min, 'version_compare');
+ }
+ if (count($max) > 0) {
+ uksort($max, 'version_compare');
+ }
+ if (count($min)) {
+ // get the highest minimum
+ $min = array_pop($a = array_flip($min));
+ } else {
+ $min = false;
+ }
+ if (count($max)) {
+ // get the lowest maximum
+ $max = array_shift($a = array_flip($max));
+ } else {
+ $max = false;
+ }
+ if ($min) {
+ $php['min'] = $min;
+ }
+ if ($max) {
+ $php['max'] = $max;
+ }
+ $exclude = array();
+ foreach ($test as $dep) {
+ if (!isset($dep['exclude'])) {
+ continue;
+ }
+ $exclude[] = $dep['exclude'];
+ }
+ if (count($exclude)) {
+ $php['exclude'] = $exclude;
+ }
+ $ret[] = $php;
+ }
+ return $ret;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/PackageFile/Generator/v2.php b/includes/pear/PEAR/PackageFile/Generator/v2.php
new file mode 100644
index 0000000..8ff90e2
--- /dev/null
+++ b/includes/pear/PEAR/PackageFile/Generator/v2.php
@@ -0,0 +1,893 @@
+<?php
+/**
+ * package.xml generation class, package.xml version 2.0
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @author Stephan Schmidt (original XML_Serializer code)
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+/**
+ * file/dir manipulation routines
+ */
+require_once 'System.php';
+require_once 'XML/Util.php';
+
+/**
+ * This class converts a PEAR_PackageFile_v2 object into any output format.
+ *
+ * Supported output formats include array, XML string (using S. Schmidt's
+ * XML_Serializer, slightly customized)
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @author Stephan Schmidt (original XML_Serializer code)
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @PEAR-VER@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_PackageFile_Generator_v2
+{
+ /**
+ * default options for the serialization
+ * @access private
+ * @var array $_defaultOptions
+ */
+ var $_defaultOptions = array(
+ 'indent' => ' ', // string used for indentation
+ 'linebreak' => "\n", // string used for newlines
+ 'typeHints' => false, // automatically add type hin attributes
+ 'addDecl' => true, // add an XML declaration
+ 'defaultTagName' => 'XML_Serializer_Tag', // tag used for indexed arrays or invalid names
+ 'classAsTagName' => false, // use classname for objects in indexed arrays
+ 'keyAttribute' => '_originalKey', // attribute where original key is stored
+ 'typeAttribute' => '_type', // attribute for type (only if typeHints => true)
+ 'classAttribute' => '_class', // attribute for class of objects (only if typeHints => true)
+ 'scalarAsAttributes' => false, // scalar values (strings, ints,..) will be serialized as attribute
+ 'prependAttributes' => '', // prepend string for attributes
+ 'indentAttributes' => false, // indent the attributes, if set to '_auto', it will indent attributes so they all start at the same column
+ 'mode' => 'simplexml', // use 'simplexml' to use parent name as tagname if transforming an indexed array
+ 'addDoctype' => false, // add a doctype declaration
+ 'doctype' => null, // supply a string or an array with id and uri ({@see XML_Util::getDoctypeDeclaration()}
+ 'rootName' => 'package', // name of the root tag
+ 'rootAttributes' => array(
+ 'version' => '2.0',
+ 'xmlns' => 'http://pear.php.net/dtd/package-2.0',
+ 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0',
+ 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
+ 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd',
+ ), // attributes of the root tag
+ 'attributesArray' => 'attribs', // all values in this key will be treated as attributes
+ 'contentName' => '_content', // this value will be used directly as content, instead of creating a new tag, may only be used in conjuction with attributesArray
+ 'beautifyFilelist' => false,
+ 'encoding' => 'UTF-8',
+ );
+
+ /**
+ * options for the serialization
+ * @access private
+ * @var array $options
+ */
+ var $options = array();
+
+ /**
+ * current tag depth
+ * @var integer $_tagDepth
+ */
+ var $_tagDepth = 0;
+
+ /**
+ * serilialized representation of the data
+ * @var string $_serializedData
+ */
+ var $_serializedData = null;
+ /**
+ * @var PEAR_PackageFile_v2
+ */
+ var $_packagefile;
+ /**
+ * @param PEAR_PackageFile_v2
+ */
+ function PEAR_PackageFile_Generator_v2(&$packagefile)
+ {
+ $this->_packagefile = &$packagefile;
+ if (isset($this->_packagefile->encoding)) {
+ $this->_defaultOptions['encoding'] = $this->_packagefile->encoding;
+ }
+ }
+
+ /**
+ * @return string
+ */
+ function getPackagerVersion()
+ {
+ return '1.9.4';
+ }
+
+ /**
+ * @param PEAR_Packager
+ * @param bool generate a .tgz or a .tar
+ * @param string|null temporary directory to package in
+ */
+ function toTgz(&$packager, $compress = true, $where = null)
+ {
+ $a = null;
+ return $this->toTgz2($packager, $a, $compress, $where);
+ }
+
+ /**
+ * Package up both a package.xml and package2.xml for the same release
+ * @param PEAR_Packager
+ * @param PEAR_PackageFile_v1
+ * @param bool generate a .tgz or a .tar
+ * @param string|null temporary directory to package in
+ */
+ function toTgz2(&$packager, &$pf1, $compress = true, $where = null)
+ {
+ require_once 'Archive/Tar.php';
+ if (!$this->_packagefile->isEquivalent($pf1)) {
+ return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: "' .
+ basename($pf1->getPackageFile()) .
+ '" is not equivalent to "' . basename($this->_packagefile->getPackageFile())
+ . '"');
+ }
+
+ if ($where === null) {
+ if (!($where = System::mktemp(array('-d')))) {
+ return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: mktemp failed');
+ }
+ } elseif (!@System::mkDir(array('-p', $where))) {
+ return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: "' . $where . '" could' .
+ ' not be created');
+ }
+
+ $file = $where . DIRECTORY_SEPARATOR . 'package.xml';
+ if (file_exists($file) && !is_file($file)) {
+ return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: unable to save package.xml as' .
+ ' "' . $file .'"');
+ }
+
+ if (!$this->_packagefile->validate(PEAR_VALIDATE_PACKAGING)) {
+ return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: invalid package.xml');
+ }
+
+ $ext = $compress ? '.tgz' : '.tar';
+ $pkgver = $this->_packagefile->getPackage() . '-' . $this->_packagefile->getVersion();
+ $dest_package = getcwd() . DIRECTORY_SEPARATOR . $pkgver . $ext;
+ if (file_exists($dest_package) && !is_file($dest_package)) {
+ return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: cannot create tgz file "' .
+ $dest_package . '"');
+ }
+
+ $pkgfile = $this->_packagefile->getPackageFile();
+ if (!$pkgfile) {
+ return PEAR::raiseError('PEAR_Packagefile_v2::toTgz: package file object must ' .
+ 'be created from a real file');
+ }
+
+ $pkgdir = dirname(realpath($pkgfile));
+ $pkgfile = basename($pkgfile);
+
+ // {{{ Create the package file list
+ $filelist = array();
+ $i = 0;
+ $this->_packagefile->flattenFilelist();
+ $contents = $this->_packagefile->getContents();
+ if (isset($contents['bundledpackage'])) { // bundles of packages
+ $contents = $contents['bundledpackage'];
+ if (!isset($contents[0])) {
+ $contents = array($contents);
+ }
+
+ $packageDir = $where;
+ foreach ($contents as $i => $package) {
+ $fname = $package;
+ $file = $pkgdir . DIRECTORY_SEPARATOR . $fname;
+ if (!file_exists($file)) {
+ return $packager->raiseError("File does not exist: $fname");
+ }
+
+ $tfile = $packageDir . DIRECTORY_SEPARATOR . $fname;
+ System::mkdir(array('-p', dirname($tfile)));
+ copy($file, $tfile);
+ $filelist[$i++] = $tfile;
+ $packager->log(2, "Adding package $fname");
+ }
+ } else { // normal packages
+ $contents = $contents['dir']['file'];
+ if (!isset($contents[0])) {
+ $contents = array($contents);
+ }
+
+ $packageDir = $where;
+ foreach ($contents as $i => $file) {
+ $fname = $file['attribs']['name'];
+ $atts = $file['attribs'];
+ $orig = $file;
+ $file = $pkgdir . DIRECTORY_SEPARATOR . $fname;
+ if (!file_exists($file)) {
+ return $packager->raiseError("File does not exist: $fname");
+ }
+
+ $origperms = fileperms($file);
+ $tfile = $packageDir . DIRECTORY_SEPARATOR . $fname;
+ unset($orig['attribs']);
+ if (count($orig)) { // file with tasks
+ // run any package-time tasks
+ $contents = file_get_contents($file);
+ foreach ($orig as $tag => $raw) {
+ $tag = str_replace(
+ array($this->_packagefile->getTasksNs() . ':', '-'),
+ array('', '_'), $tag);
+ $task = "PEAR_Task_$tag";
+ $task = &new $task($this->_packagefile->_config,
+ $this->_packagefile->_logger,
+ PEAR_TASK_PACKAGE);
+ $task->init($raw, $atts, null);
+ $res = $task->startSession($this->_packagefile, $contents, $tfile);
+ if (!$res) {
+ continue; // skip this task
+ }
+
+ if (PEAR::isError($res)) {
+ return $res;
+ }
+
+ $contents = $res; // save changes
+ System::mkdir(array('-p', dirname($tfile)));
+ $wp = fopen($tfile, "wb");
+ fwrite($wp, $contents);
+ fclose($wp);
+ }
+ }
+
+ if (!file_exists($tfile)) {
+ System::mkdir(array('-p', dirname($tfile)));
+ copy($file, $tfile);
+ }
+
+ chmod($tfile, $origperms);
+ $filelist[$i++] = $tfile;
+ $this->_packagefile->setFileAttribute($fname, 'md5sum', md5_file($tfile), $i - 1);
+ $packager->log(2, "Adding file $fname");
+ }
+ }
+ // }}}
+
+ $name = $pf1 !== null ? 'package2.xml' : 'package.xml';
+ $packagexml = $this->toPackageFile($where, PEAR_VALIDATE_PACKAGING, $name);
+ if ($packagexml) {
+ $tar =& new Archive_Tar($dest_package, $compress);
+ $tar->setErrorHandling(PEAR_ERROR_RETURN); // XXX Don't print errors
+ // ----- Creates with the package.xml file
+ $ok = $tar->createModify(array($packagexml), '', $where);
+ if (PEAR::isError($ok)) {
+ return $packager->raiseError($ok);
+ } elseif (!$ok) {
+ return $packager->raiseError('PEAR_Packagefile_v2::toTgz(): adding ' . $name .
+ ' failed');
+ }
+
+ // ----- Add the content of the package
+ if (!$tar->addModify($filelist, $pkgver, $where)) {
+ return $packager->raiseError(
+ 'PEAR_Packagefile_v2::toTgz(): tarball creation failed');
+ }
+
+ // add the package.xml version 1.0
+ if ($pf1 !== null) {
+ $pfgen = &$pf1->getDefaultGenerator();
+ $packagexml1 = $pfgen->toPackageFile($where, PEAR_VALIDATE_PACKAGING, 'package.xml', true);
+ if (!$tar->addModify(array($packagexml1), '', $where)) {
+ return $packager->raiseError(
+ 'PEAR_Packagefile_v2::toTgz(): adding package.xml failed');
+ }
+ }
+
+ return $dest_package;
+ }
+ }
+
+ function toPackageFile($where = null, $state = PEAR_VALIDATE_NORMAL, $name = 'package.xml')
+ {
+ if (!$this->_packagefile->validate($state)) {
+ return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: invalid package.xml',
+ null, null, null, $this->_packagefile->getValidationWarnings());
+ }
+
+ if ($where === null) {
+ if (!($where = System::mktemp(array('-d')))) {
+ return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: mktemp failed');
+ }
+ } elseif (!@System::mkDir(array('-p', $where))) {
+ return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: "' . $where . '" could' .
+ ' not be created');
+ }
+
+ $newpkgfile = $where . DIRECTORY_SEPARATOR . $name;
+ $np = @fopen($newpkgfile, 'wb');
+ if (!$np) {
+ return PEAR::raiseError('PEAR_Packagefile_v2::toPackageFile: unable to save ' .
+ "$name as $newpkgfile");
+ }
+ fwrite($np, $this->toXml($state));
+ fclose($np);
+ return $newpkgfile;
+ }
+
+ function &toV2()
+ {
+ return $this->_packagefile;
+ }
+
+ /**
+ * Return an XML document based on the package info (as returned
+ * by the PEAR_Common::infoFrom* methods).
+ *
+ * @return string XML data
+ */
+ function toXml($state = PEAR_VALIDATE_NORMAL, $options = array())
+ {
+ $this->_packagefile->setDate(date('Y-m-d'));
+ $this->_packagefile->setTime(date('H:i:s'));
+ if (!$this->_packagefile->validate($state)) {
+ return false;
+ }
+
+ if (is_array($options)) {
+ $this->options = array_merge($this->_defaultOptions, $options);
+ } else {
+ $this->options = $this->_defaultOptions;
+ }
+
+ $arr = $this->_packagefile->getArray();
+ if (isset($arr['filelist'])) {
+ unset($arr['filelist']);
+ }
+
+ if (isset($arr['_lastversion'])) {
+ unset($arr['_lastversion']);
+ }
+
+ // Fix the notes a little bit
+ if (isset($arr['notes'])) {
+ // This trims out the indenting, needs fixing
+ $arr['notes'] = "\n" . trim($arr['notes']) . "\n";
+ }
+
+ if (isset($arr['changelog']) && !empty($arr['changelog'])) {
+ // Fix for inconsistency how the array is filled depending on the changelog release amount
+ if (!isset($arr['changelog']['release'][0])) {
+ $release = $arr['changelog']['release'];
+ unset($arr['changelog']['release']);
+
+ $arr['changelog']['release'] = array();
+ $arr['changelog']['release'][0] = $release;
+ }
+
+ foreach (array_keys($arr['changelog']['release']) as $key) {
+ $c =& $arr['changelog']['release'][$key];
+ if (isset($c['notes'])) {
+ // This trims out the indenting, needs fixing
+ $c['notes'] = "\n" . trim($c['notes']) . "\n";
+ }
+ }
+ }
+
+ if ($state ^ PEAR_VALIDATE_PACKAGING && !isset($arr['bundle'])) {
+ $use = $this->_recursiveXmlFilelist($arr['contents']['dir']['file']);
+ unset($arr['contents']['dir']['file']);
+ if (isset($use['dir'])) {
+ $arr['contents']['dir']['dir'] = $use['dir'];
+ }
+ if (isset($use['file'])) {
+ $arr['contents']['dir']['file'] = $use['file'];
+ }
+ $this->options['beautifyFilelist'] = true;
+ }
+
+ $arr['attribs']['packagerversion'] = '1.9.4';
+ if ($this->serialize($arr, $options)) {
+ return $this->_serializedData . "\n";
+ }
+
+ return false;
+ }
+
+
+ function _recursiveXmlFilelist($list)
+ {
+ $dirs = array();
+ if (isset($list['attribs'])) {
+ $file = $list['attribs']['name'];
+ unset($list['attribs']['name']);
+ $attributes = $list['attribs'];
+ $this->_addDir($dirs, explode('/', dirname($file)), $file, $attributes);
+ } else {
+ foreach ($list as $a) {
+ $file = $a['attribs']['name'];
+ $attributes = $a['attribs'];
+ unset($a['attribs']);
+ $this->_addDir($dirs, explode('/', dirname($file)), $file, $attributes, $a);
+ }
+ }
+ $this->_formatDir($dirs);
+ $this->_deFormat($dirs);
+ return $dirs;
+ }
+
+ function _addDir(&$dirs, $dir, $file = null, $attributes = null, $tasks = null)
+ {
+ if (!$tasks) {
+ $tasks = array();
+ }
+ if ($dir == array() || $dir == array('.')) {
+ $dirs['file'][basename($file)] = $tasks;
+ $attributes['name'] = basename($file);
+ $dirs['file'][basename($file)]['attribs'] = $attributes;
+ return;
+ }
+ $curdir = array_shift($dir);
+ if (!isset($dirs['dir'][$curdir])) {
+ $dirs['dir'][$curdir] = array();
+ }
+ $this->_addDir($dirs['dir'][$curdir], $dir, $file, $attributes, $tasks);
+ }
+
+ function _formatDir(&$dirs)
+ {
+ if (!count($dirs)) {
+ return array();
+ }
+ $newdirs = array();
+ if (isset($dirs['dir'])) {
+ $newdirs['dir'] = $dirs['dir'];
+ }
+ if (isset($dirs['file'])) {
+ $newdirs['file'] = $dirs['file'];
+ }
+ $dirs = $newdirs;
+ if (isset($dirs['dir'])) {
+ uksort($dirs['dir'], 'strnatcasecmp');
+ foreach ($dirs['dir'] as $dir => $contents) {
+ $this->_formatDir($dirs['dir'][$dir]);
+ }
+ }
+ if (isset($dirs['file'])) {
+ uksort($dirs['file'], 'strnatcasecmp');
+ };
+ }
+
+ function _deFormat(&$dirs)
+ {
+ if (!count($dirs)) {
+ return array();
+ }
+ $newdirs = array();
+ if (isset($dirs['dir'])) {
+ foreach ($dirs['dir'] as $dir => $contents) {
+ $newdir = array();
+ $newdir['attribs']['name'] = $dir;
+ $this->_deFormat($contents);
+ foreach ($contents as $tag => $val) {
+ $newdir[$tag] = $val;
+ }
+ $newdirs['dir'][] = $newdir;
+ }
+ if (count($newdirs['dir']) == 1) {
+ $newdirs['dir'] = $newdirs['dir'][0];
+ }
+ }
+ if (isset($dirs['file'])) {
+ foreach ($dirs['file'] as $name => $file) {
+ $newdirs['file'][] = $file;
+ }
+ if (count($newdirs['file']) == 1) {
+ $newdirs['file'] = $newdirs['file'][0];
+ }
+ }
+ $dirs = $newdirs;
+ }
+
+ /**
+ * reset all options to default options
+ *
+ * @access public
+ * @see setOption(), XML_Unserializer()
+ */
+ function resetOptions()
+ {
+ $this->options = $this->_defaultOptions;
+ }
+
+ /**
+ * set an option
+ *
+ * You can use this method if you do not want to set all options in the constructor
+ *
+ * @access public
+ * @see resetOption(), XML_Serializer()
+ */
+ function setOption($name, $value)
+ {
+ $this->options[$name] = $value;
+ }
+
+ /**
+ * sets several options at once
+ *
+ * You can use this method if you do not want to set all options in the constructor
+ *
+ * @access public
+ * @see resetOption(), XML_Unserializer(), setOption()
+ */
+ function setOptions($options)
+ {
+ $this->options = array_merge($this->options, $options);
+ }
+
+ /**
+ * serialize data
+ *
+ * @access public
+ * @param mixed $data data to serialize
+ * @return boolean true on success, pear error on failure
+ */
+ function serialize($data, $options = null)
+ {
+ // if options have been specified, use them instead
+ // of the previously defined ones
+ if (is_array($options)) {
+ $optionsBak = $this->options;
+ if (isset($options['overrideOptions']) && $options['overrideOptions'] == true) {
+ $this->options = array_merge($this->_defaultOptions, $options);
+ } else {
+ $this->options = array_merge($this->options, $options);
+ }
+ } else {
+ $optionsBak = null;
+ }
+
+ // start depth is zero
+ $this->_tagDepth = 0;
+ $this->_serializedData = '';
+ // serialize an array
+ if (is_array($data)) {
+ $tagName = isset($this->options['rootName']) ? $this->options['rootName'] : 'array';
+ $this->_serializedData .= $this->_serializeArray($data, $tagName, $this->options['rootAttributes']);
+ }
+
+ // add doctype declaration
+ if ($this->options['addDoctype'] === true) {
+ $this->_serializedData = XML_Util::getDoctypeDeclaration($tagName, $this->options['doctype'])
+ . $this->options['linebreak']
+ . $this->_serializedData;
+ }
+
+ // build xml declaration
+ if ($this->options['addDecl']) {
+ $atts = array();
+ $encoding = isset($this->options['encoding']) ? $this->options['encoding'] : null;
+ $this->_serializedData = XML_Util::getXMLDeclaration('1.0', $encoding)
+ . $this->options['linebreak']
+ . $this->_serializedData;
+ }
+
+
+ if ($optionsBak !== null) {
+ $this->options = $optionsBak;
+ }
+
+ return true;
+ }
+
+ /**
+ * get the result of the serialization
+ *
+ * @access public
+ * @return string serialized XML
+ */
+ function getSerializedData()
+ {
+ if ($this->_serializedData === null) {
+ return $this->raiseError('No serialized data available. Use XML_Serializer::serialize() first.', XML_SERIALIZER_ERROR_NO_SERIALIZATION);
+ }
+ return $this->_serializedData;
+ }
+
+ /**
+ * serialize any value
+ *
+ * This method checks for the type of the value and calls the appropriate method
+ *
+ * @access private
+ * @param mixed $value
+ * @param string $tagName
+ * @param array $attributes
+ * @return string
+ */
+ function _serializeValue($value, $tagName = null, $attributes = array())
+ {
+ if (is_array($value)) {
+ $xml = $this->_serializeArray($value, $tagName, $attributes);
+ } elseif (is_object($value)) {
+ $xml = $this->_serializeObject($value, $tagName);
+ } else {
+ $tag = array(
+ 'qname' => $tagName,
+ 'attributes' => $attributes,
+ 'content' => $value
+ );
+ $xml = $this->_createXMLTag($tag);
+ }
+ return $xml;
+ }
+
+ /**
+ * serialize an array
+ *
+ * @access private
+ * @param array $array array to serialize
+ * @param string $tagName name of the root tag
+ * @param array $attributes attributes for the root tag
+ * @return string $string serialized data
+ * @uses XML_Util::isValidName() to check, whether key has to be substituted
+ */
+ function _serializeArray(&$array, $tagName = null, $attributes = array())
+ {
+ $_content = null;
+
+ /**
+ * check for special attributes
+ */
+ if ($this->options['attributesArray'] !== null) {
+ if (isset($array[$this->options['attributesArray']])) {
+ $attributes = $array[$this->options['attributesArray']];
+ unset($array[$this->options['attributesArray']]);
+ }
+ /**
+ * check for special content
+ */
+ if ($this->options['contentName'] !== null) {
+ if (isset($array[$this->options['contentName']])) {
+ $_content = $array[$this->options['contentName']];
+ unset($array[$this->options['contentName']]);
+ }
+ }
+ }
+
+ /*
+ * if mode is set to simpleXML, check whether
+ * the array is associative or indexed
+ */
+ if (is_array($array) && $this->options['mode'] == 'simplexml') {
+ $indexed = true;
+ if (!count($array)) {
+ $indexed = false;
+ }
+ foreach ($array as $key => $val) {
+ if (!is_int($key)) {
+ $indexed = false;
+ break;
+ }
+ }
+
+ if ($indexed && $this->options['mode'] == 'simplexml') {
+ $string = '';
+ foreach ($array as $key => $val) {
+ if ($this->options['beautifyFilelist'] && $tagName == 'dir') {
+ if (!isset($this->_curdir)) {
+ $this->_curdir = '';
+ }
+ $savedir = $this->_curdir;
+ if (isset($val['attribs'])) {
+ if ($val['attribs']['name'] == '/') {
+ $this->_curdir = '/';
+ } else {
+ if ($this->_curdir == '/') {
+ $this->_curdir = '';
+ }
+ $this->_curdir .= '/' . $val['attribs']['name'];
+ }
+ }
+ }
+ $string .= $this->_serializeValue( $val, $tagName, $attributes);
+ if ($this->options['beautifyFilelist'] && $tagName == 'dir') {
+ $string .= ' <!-- ' . $this->_curdir . ' -->';
+ if (empty($savedir)) {
+ unset($this->_curdir);
+ } else {
+ $this->_curdir = $savedir;
+ }
+ }
+
+ $string .= $this->options['linebreak'];
+ // do indentation
+ if ($this->options['indent'] !== null && $this->_tagDepth > 0) {
+ $string .= str_repeat($this->options['indent'], $this->_tagDepth);
+ }
+ }
+ return rtrim($string);
+ }
+ }
+
+ if ($this->options['scalarAsAttributes'] === true) {
+ foreach ($array as $key => $value) {
+ if (is_scalar($value) && (XML_Util::isValidName($key) === true)) {
+ unset($array[$key]);
+ $attributes[$this->options['prependAttributes'].$key] = $value;
+ }
+ }
+ }
+
+ // check for empty array => create empty tag
+ if (empty($array)) {
+ $tag = array(
+ 'qname' => $tagName,
+ 'content' => $_content,
+ 'attributes' => $attributes
+ );
+
+ } else {
+ $this->_tagDepth++;
+ $tmp = $this->options['linebreak'];
+ foreach ($array as $key => $value) {
+ // do indentation
+ if ($this->options['indent'] !== null && $this->_tagDepth > 0) {
+ $tmp .= str_repeat($this->options['indent'], $this->_tagDepth);
+ }
+
+ // copy key
+ $origKey = $key;
+ // key cannot be used as tagname => use default tag
+ $valid = XML_Util::isValidName($key);
+ if (PEAR::isError($valid)) {
+ if ($this->options['classAsTagName'] && is_object($value)) {
+ $key = get_class($value);
+ } else {
+ $key = $this->options['defaultTagName'];
+ }
+ }
+ $atts = array();
+ if ($this->options['typeHints'] === true) {
+ $atts[$this->options['typeAttribute']] = gettype($value);
+ if ($key !== $origKey) {
+ $atts[$this->options['keyAttribute']] = (string)$origKey;
+ }
+
+ }
+ if ($this->options['beautifyFilelist'] && $key == 'dir') {
+ if (!isset($this->_curdir)) {
+ $this->_curdir = '';
+ }
+ $savedir = $this->_curdir;
+ if (isset($value['attribs'])) {
+ if ($value['attribs']['name'] == '/') {
+ $this->_curdir = '/';
+ } else {
+ $this->_curdir .= '/' . $value['attribs']['name'];
+ }
+ }
+ }
+
+ if (is_string($value) && $value && ($value{strlen($value) - 1} == "\n")) {
+ $value .= str_repeat($this->options['indent'], $this->_tagDepth);
+ }
+ $tmp .= $this->_createXMLTag(array(
+ 'qname' => $key,
+ 'attributes' => $atts,
+ 'content' => $value )
+ );
+ if ($this->options['beautifyFilelist'] && $key == 'dir') {
+ if (isset($value['attribs'])) {
+ $tmp .= ' <!-- ' . $this->_curdir . ' -->';
+ if (empty($savedir)) {
+ unset($this->_curdir);
+ } else {
+ $this->_curdir = $savedir;
+ }
+ }
+ }
+ $tmp .= $this->options['linebreak'];
+ }
+
+ $this->_tagDepth--;
+ if ($this->options['indent']!==null && $this->_tagDepth>0) {
+ $tmp .= str_repeat($this->options['indent'], $this->_tagDepth);
+ }
+
+ if (trim($tmp) === '') {
+ $tmp = null;
+ }
+
+ $tag = array(
+ 'qname' => $tagName,
+ 'content' => $tmp,
+ 'attributes' => $attributes
+ );
+ }
+ if ($this->options['typeHints'] === true) {
+ if (!isset($tag['attributes'][$this->options['typeAttribute']])) {
+ $tag['attributes'][$this->options['typeAttribute']] = 'array';
+ }
+ }
+
+ $string = $this->_createXMLTag($tag, false);
+ return $string;
+ }
+
+ /**
+ * create a tag from an array
+ * this method awaits an array in the following format
+ * array(
+ * 'qname' => $tagName,
+ * 'attributes' => array(),
+ * 'content' => $content, // optional
+ * 'namespace' => $namespace // optional
+ * 'namespaceUri' => $namespaceUri // optional
+ * )
+ *
+ * @access private
+ * @param array $tag tag definition
+ * @param boolean $replaceEntities whether to replace XML entities in content or not
+ * @return string $string XML tag
+ */
+ function _createXMLTag($tag, $replaceEntities = true)
+ {
+ if ($this->options['indentAttributes'] !== false) {
+ $multiline = true;
+ $indent = str_repeat($this->options['indent'], $this->_tagDepth);
+
+ if ($this->options['indentAttributes'] == '_auto') {
+ $indent .= str_repeat(' ', (strlen($tag['qname'])+2));
+
+ } else {
+ $indent .= $this->options['indentAttributes'];
+ }
+ } else {
+ $indent = $multiline = false;
+ }
+
+ if (is_array($tag['content'])) {
+ if (empty($tag['content'])) {
+ $tag['content'] = '';
+ }
+ } elseif(is_scalar($tag['content']) && (string)$tag['content'] == '') {
+ $tag['content'] = '';
+ }
+
+ if (is_scalar($tag['content']) || is_null($tag['content'])) {
+ if ($this->options['encoding'] == 'UTF-8' &&
+ version_compare(phpversion(), '5.0.0', 'lt')
+ ) {
+ $tag['content'] = utf8_encode($tag['content']);
+ }
+
+ if ($replaceEntities === true) {
+ $replaceEntities = XML_UTIL_ENTITIES_XML;
+ }
+
+ $tag = XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, $indent, $this->options['linebreak']);
+ } elseif (is_array($tag['content'])) {
+ $tag = $this->_serializeArray($tag['content'], $tag['qname'], $tag['attributes']);
+ } elseif (is_object($tag['content'])) {
+ $tag = $this->_serializeObject($tag['content'], $tag['qname'], $tag['attributes']);
+ } elseif (is_resource($tag['content'])) {
+ settype($tag['content'], 'string');
+ $tag = XML_Util::createTagFromArray($tag, $replaceEntities);
+ }
+ return $tag;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/PackageFile/Parser/v1.php b/includes/pear/PEAR/PackageFile/Parser/v1.php
new file mode 100644
index 0000000..bbf5989
--- /dev/null
+++ b/includes/pear/PEAR/PackageFile/Parser/v1.php
@@ -0,0 +1,459 @@
+<?php
+/**
+ * package.xml parsing class, package.xml version 1.0
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+/**
+ * package.xml abstraction class
+ */
+require_once 'PEAR/PackageFile/v1.php';
+/**
+ * Parser for package.xml version 1.0
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @PEAR-VER@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_PackageFile_Parser_v1
+{
+ var $_registry;
+ var $_config;
+ var $_logger;
+ /**
+ * BC hack to allow PEAR_Common::infoFromString() to sort of
+ * work with the version 2.0 format - there's no filelist though
+ * @param PEAR_PackageFile_v2
+ */
+ function fromV2($packagefile)
+ {
+ $info = $packagefile->getArray(true);
+ $ret = new PEAR_PackageFile_v1;
+ $ret->fromArray($info['old']);
+ }
+
+ function setConfig(&$c)
+ {
+ $this->_config = &$c;
+ $this->_registry = &$c->getRegistry();
+ }
+
+ function setLogger(&$l)
+ {
+ $this->_logger = &$l;
+ }
+
+ /**
+ * @param string contents of package.xml file, version 1.0
+ * @return bool success of parsing
+ */
+ function &parse($data, $file, $archive = false)
+ {
+ if (!extension_loaded('xml')) {
+ return PEAR::raiseError('Cannot create xml parser for parsing package.xml, no xml extension');
+ }
+ $xp = xml_parser_create();
+ if (!$xp) {
+ $a = &PEAR::raiseError('Cannot create xml parser for parsing package.xml');
+ return $a;
+ }
+ xml_set_object($xp, $this);
+ xml_set_element_handler($xp, '_element_start_1_0', '_element_end_1_0');
+ xml_set_character_data_handler($xp, '_pkginfo_cdata_1_0');
+ xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, false);
+
+ $this->element_stack = array();
+ $this->_packageInfo = array('provides' => array());
+ $this->current_element = false;
+ unset($this->dir_install);
+ $this->_packageInfo['filelist'] = array();
+ $this->filelist =& $this->_packageInfo['filelist'];
+ $this->dir_names = array();
+ $this->in_changelog = false;
+ $this->d_i = 0;
+ $this->cdata = '';
+ $this->_isValid = true;
+
+ if (!xml_parse($xp, $data, 1)) {
+ $code = xml_get_error_code($xp);
+ $line = xml_get_current_line_number($xp);
+ xml_parser_free($xp);
+ $a = &PEAR::raiseError(sprintf("XML error: %s at line %d",
+ $str = xml_error_string($code), $line), 2);
+ return $a;
+ }
+
+ xml_parser_free($xp);
+
+ $pf = new PEAR_PackageFile_v1;
+ $pf->setConfig($this->_config);
+ if (isset($this->_logger)) {
+ $pf->setLogger($this->_logger);
+ }
+ $pf->setPackagefile($file, $archive);
+ $pf->fromArray($this->_packageInfo);
+ return $pf;
+ }
+ // {{{ _unIndent()
+
+ /**
+ * Unindent given string
+ *
+ * @param string $str The string that has to be unindented.
+ * @return string
+ * @access private
+ */
+ function _unIndent($str)
+ {
+ // remove leading newlines
+ $str = preg_replace('/^[\r\n]+/', '', $str);
+ // find whitespace at the beginning of the first line
+ $indent_len = strspn($str, " \t");
+ $indent = substr($str, 0, $indent_len);
+ $data = '';
+ // remove the same amount of whitespace from following lines
+ foreach (explode("\n", $str) as $line) {
+ if (substr($line, 0, $indent_len) == $indent) {
+ $data .= substr($line, $indent_len) . "\n";
+ } elseif (trim(substr($line, 0, $indent_len))) {
+ $data .= ltrim($line);
+ }
+ }
+ return $data;
+ }
+
+ // Support for package DTD v1.0:
+ // {{{ _element_start_1_0()
+
+ /**
+ * XML parser callback for ending elements. Used for version 1.0
+ * packages.
+ *
+ * @param resource $xp XML parser resource
+ * @param string $name name of ending element
+ *
+ * @return void
+ *
+ * @access private
+ */
+ function _element_start_1_0($xp, $name, $attribs)
+ {
+ array_push($this->element_stack, $name);
+ $this->current_element = $name;
+ $spos = sizeof($this->element_stack) - 2;
+ $this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : '';
+ $this->current_attributes = $attribs;
+ $this->cdata = '';
+ switch ($name) {
+ case 'dir':
+ if ($this->in_changelog) {
+ break;
+ }
+ if (array_key_exists('name', $attribs) && $attribs['name'] != '/') {
+ $attribs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'),
+ $attribs['name']);
+ if (strrpos($attribs['name'], '/') === strlen($attribs['name']) - 1) {
+ $attribs['name'] = substr($attribs['name'], 0,
+ strlen($attribs['name']) - 1);
+ }
+ if (strpos($attribs['name'], '/') === 0) {
+ $attribs['name'] = substr($attribs['name'], 1);
+ }
+ $this->dir_names[] = $attribs['name'];
+ }
+ if (isset($attribs['baseinstalldir'])) {
+ $this->dir_install = $attribs['baseinstalldir'];
+ }
+ if (isset($attribs['role'])) {
+ $this->dir_role = $attribs['role'];
+ }
+ break;
+ case 'file':
+ if ($this->in_changelog) {
+ break;
+ }
+ if (isset($attribs['name'])) {
+ $path = '';
+ if (count($this->dir_names)) {
+ foreach ($this->dir_names as $dir) {
+ $path .= $dir . '/';
+ }
+ }
+ $path .= preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'),
+ $attribs['name']);
+ unset($attribs['name']);
+ $this->current_path = $path;
+ $this->filelist[$path] = $attribs;
+ // Set the baseinstalldir only if the file don't have this attrib
+ if (!isset($this->filelist[$path]['baseinstalldir']) &&
+ isset($this->dir_install))
+ {
+ $this->filelist[$path]['baseinstalldir'] = $this->dir_install;
+ }
+ // Set the Role
+ if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) {
+ $this->filelist[$path]['role'] = $this->dir_role;
+ }
+ }
+ break;
+ case 'replace':
+ if (!$this->in_changelog) {
+ $this->filelist[$this->current_path]['replacements'][] = $attribs;
+ }
+ break;
+ case 'maintainers':
+ $this->_packageInfo['maintainers'] = array();
+ $this->m_i = 0; // maintainers array index
+ break;
+ case 'maintainer':
+ // compatibility check
+ if (!isset($this->_packageInfo['maintainers'])) {
+ $this->_packageInfo['maintainers'] = array();
+ $this->m_i = 0;
+ }
+ $this->_packageInfo['maintainers'][$this->m_i] = array();
+ $this->current_maintainer =& $this->_packageInfo['maintainers'][$this->m_i];
+ break;
+ case 'changelog':
+ $this->_packageInfo['changelog'] = array();
+ $this->c_i = 0; // changelog array index
+ $this->in_changelog = true;
+ break;
+ case 'release':
+ if ($this->in_changelog) {
+ $this->_packageInfo['changelog'][$this->c_i] = array();
+ $this->current_release = &$this->_packageInfo['changelog'][$this->c_i];
+ } else {
+ $this->current_release = &$this->_packageInfo;
+ }
+ break;
+ case 'deps':
+ if (!$this->in_changelog) {
+ $this->_packageInfo['release_deps'] = array();
+ }
+ break;
+ case 'dep':
+ // dependencies array index
+ if (!$this->in_changelog) {
+ $this->d_i++;
+ isset($attribs['type']) ? ($attribs['type'] = strtolower($attribs['type'])) : false;
+ $this->_packageInfo['release_deps'][$this->d_i] = $attribs;
+ }
+ break;
+ case 'configureoptions':
+ if (!$this->in_changelog) {
+ $this->_packageInfo['configure_options'] = array();
+ }
+ break;
+ case 'configureoption':
+ if (!$this->in_changelog) {
+ $this->_packageInfo['configure_options'][] = $attribs;
+ }
+ break;
+ case 'provides':
+ if (empty($attribs['type']) || empty($attribs['name'])) {
+ break;
+ }
+ $attribs['explicit'] = true;
+ $this->_packageInfo['provides']["$attribs[type];$attribs[name]"] = $attribs;
+ break;
+ case 'package' :
+ if (isset($attribs['version'])) {
+ $this->_packageInfo['xsdversion'] = trim($attribs['version']);
+ } else {
+ $this->_packageInfo['xsdversion'] = '1.0';
+ }
+ if (isset($attribs['packagerversion'])) {
+ $this->_packageInfo['packagerversion'] = $attribs['packagerversion'];
+ }
+ break;
+ }
+ }
+
+ // }}}
+ // {{{ _element_end_1_0()
+
+ /**
+ * XML parser callback for ending elements. Used for version 1.0
+ * packages.
+ *
+ * @param resource $xp XML parser resource
+ * @param string $name name of ending element
+ *
+ * @return void
+ *
+ * @access private
+ */
+ function _element_end_1_0($xp, $name)
+ {
+ $data = trim($this->cdata);
+ switch ($name) {
+ case 'name':
+ switch ($this->prev_element) {
+ case 'package':
+ $this->_packageInfo['package'] = $data;
+ break;
+ case 'maintainer':
+ $this->current_maintainer['name'] = $data;
+ break;
+ }
+ break;
+ case 'extends' :
+ $this->_packageInfo['extends'] = $data;
+ break;
+ case 'summary':
+ $this->_packageInfo['summary'] = $data;
+ break;
+ case 'description':
+ $data = $this->_unIndent($this->cdata);
+ $this->_packageInfo['description'] = $data;
+ break;
+ case 'user':
+ $this->current_maintainer['handle'] = $data;
+ break;
+ case 'email':
+ $this->current_maintainer['email'] = $data;
+ break;
+ case 'role':
+ $this->current_maintainer['role'] = $data;
+ break;
+ case 'version':
+ if ($this->in_changelog) {
+ $this->current_release['version'] = $data;
+ } else {
+ $this->_packageInfo['version'] = $data;
+ }
+ break;
+ case 'date':
+ if ($this->in_changelog) {
+ $this->current_release['release_date'] = $data;
+ } else {
+ $this->_packageInfo['release_date'] = $data;
+ }
+ break;
+ case 'notes':
+ // try to "de-indent" release notes in case someone
+ // has been over-indenting their xml ;-)
+ // Trim only on the right side
+ $data = rtrim($this->_unIndent($this->cdata));
+ if ($this->in_changelog) {
+ $this->current_release['release_notes'] = $data;
+ } else {
+ $this->_packageInfo['release_notes'] = $data;
+ }
+ break;
+ case 'warnings':
+ if ($this->in_changelog) {
+ $this->current_release['release_warnings'] = $data;
+ } else {
+ $this->_packageInfo['release_warnings'] = $data;
+ }
+ break;
+ case 'state':
+ if ($this->in_changelog) {
+ $this->current_release['release_state'] = $data;
+ } else {
+ $this->_packageInfo['release_state'] = $data;
+ }
+ break;
+ case 'license':
+ if ($this->in_changelog) {
+ $this->current_release['release_license'] = $data;
+ } else {
+ $this->_packageInfo['release_license'] = $data;
+ }
+ break;
+ case 'dep':
+ if ($data && !$this->in_changelog) {
+ $this->_packageInfo['release_deps'][$this->d_i]['name'] = $data;
+ }
+ break;
+ case 'dir':
+ if ($this->in_changelog) {
+ break;
+ }
+ array_pop($this->dir_names);
+ break;
+ case 'file':
+ if ($this->in_changelog) {
+ break;
+ }
+ if ($data) {
+ $path = '';
+ if (count($this->dir_names)) {
+ foreach ($this->dir_names as $dir) {
+ $path .= $dir . '/';
+ }
+ }
+ $path .= $data;
+ $this->filelist[$path] = $this->current_attributes;
+ // Set the baseinstalldir only if the file don't have this attrib
+ if (!isset($this->filelist[$path]['baseinstalldir']) &&
+ isset($this->dir_install))
+ {
+ $this->filelist[$path]['baseinstalldir'] = $this->dir_install;
+ }
+ // Set the Role
+ if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) {
+ $this->filelist[$path]['role'] = $this->dir_role;
+ }
+ }
+ break;
+ case 'maintainer':
+ if (empty($this->_packageInfo['maintainers'][$this->m_i]['role'])) {
+ $this->_packageInfo['maintainers'][$this->m_i]['role'] = 'lead';
+ }
+ $this->m_i++;
+ break;
+ case 'release':
+ if ($this->in_changelog) {
+ $this->c_i++;
+ }
+ break;
+ case 'changelog':
+ $this->in_changelog = false;
+ break;
+ }
+ array_pop($this->element_stack);
+ $spos = sizeof($this->element_stack) - 1;
+ $this->current_element = ($spos > 0) ? $this->element_stack[$spos] : '';
+ $this->cdata = '';
+ }
+
+ // }}}
+ // {{{ _pkginfo_cdata_1_0()
+
+ /**
+ * XML parser callback for character data. Used for version 1.0
+ * packages.
+ *
+ * @param resource $xp XML parser resource
+ * @param string $name character data
+ *
+ * @return void
+ *
+ * @access private
+ */
+ function _pkginfo_cdata_1_0($xp, $data)
+ {
+ if (isset($this->cdata)) {
+ $this->cdata .= $data;
+ }
+ }
+
+ // }}}
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/PackageFile/Parser/v2.php b/includes/pear/PEAR/PackageFile/Parser/v2.php
new file mode 100644
index 0000000..cb81041
--- /dev/null
+++ b/includes/pear/PEAR/PackageFile/Parser/v2.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * package.xml parsing class, package.xml version 2.0
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+/**
+ * base xml parser class
+ */
+require_once 'PEAR/XMLParser.php';
+require_once 'PEAR/PackageFile/v2.php';
+/**
+ * Parser for package.xml version 2.0
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @PEAR-VER@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_PackageFile_Parser_v2 extends PEAR_XMLParser
+{
+ var $_config;
+ var $_logger;
+ var $_registry;
+
+ function setConfig(&$c)
+ {
+ $this->_config = &$c;
+ $this->_registry = &$c->getRegistry();
+ }
+
+ function setLogger(&$l)
+ {
+ $this->_logger = &$l;
+ }
+ /**
+ * Unindent given string
+ *
+ * @param string $str The string that has to be unindented.
+ * @return string
+ * @access private
+ */
+ function _unIndent($str)
+ {
+ // remove leading newlines
+ $str = preg_replace('/^[\r\n]+/', '', $str);
+ // find whitespace at the beginning of the first line
+ $indent_len = strspn($str, " \t");
+ $indent = substr($str, 0, $indent_len);
+ $data = '';
+ // remove the same amount of whitespace from following lines
+ foreach (explode("\n", $str) as $line) {
+ if (substr($line, 0, $indent_len) == $indent) {
+ $data .= substr($line, $indent_len) . "\n";
+ } else {
+ $data .= $line . "\n";
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * post-process data
+ *
+ * @param string $data
+ * @param string $element element name
+ */
+ function postProcess($data, $element)
+ {
+ if ($element == 'notes') {
+ return trim($this->_unIndent($data));
+ }
+ return trim($data);
+ }
+
+ /**
+ * @param string
+ * @param string file name of the package.xml
+ * @param string|false name of the archive this package.xml came from, if any
+ * @param string class name to instantiate and return. This must be PEAR_PackageFile_v2 or
+ * a subclass
+ * @return PEAR_PackageFile_v2
+ */
+ function &parse($data, $file, $archive = false, $class = 'PEAR_PackageFile_v2')
+ {
+ if (PEAR::isError($err = parent::parse($data, $file))) {
+ return $err;
+ }
+
+ $ret = new $class;
+ $ret->encoding = $this->encoding;
+ $ret->setConfig($this->_config);
+ if (isset($this->_logger)) {
+ $ret->setLogger($this->_logger);
+ }
+
+ $ret->fromArray($this->_unserializedData);
+ $ret->setPackagefile($file, $archive);
+ return $ret;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/PackageFile/v1.php b/includes/pear/PEAR/PackageFile/v1.php
new file mode 100644
index 0000000..2884cc4
--- /dev/null
+++ b/includes/pear/PEAR/PackageFile/v1.php
@@ -0,0 +1,1612 @@
+<?php
+/**
+ * PEAR_PackageFile_v1, package.xml version 1.0
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+/**
+ * For error handling
+ */
+require_once 'PEAR/ErrorStack.php';
+
+/**
+ * Error code if parsing is attempted with no xml extension
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_XML_EXT', 3);
+
+/**
+ * Error code if creating the xml parser resource fails
+ */
+define('PEAR_PACKAGEFILE_ERROR_CANT_MAKE_PARSER', 4);
+
+/**
+ * Error code used for all sax xml parsing errors
+ */
+define('PEAR_PACKAGEFILE_ERROR_PARSER_ERROR', 5);
+
+/**
+ * Error code used when there is no name
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_NAME', 6);
+
+/**
+ * Error code when a package name is not valid
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_NAME', 7);
+
+/**
+ * Error code used when no summary is parsed
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_SUMMARY', 8);
+
+/**
+ * Error code for summaries that are more than 1 line
+ */
+define('PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY', 9);
+
+/**
+ * Error code used when no description is present
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION', 10);
+
+/**
+ * Error code used when no license is present
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_LICENSE', 11);
+
+/**
+ * Error code used when a <version> version number is not present
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_VERSION', 12);
+
+/**
+ * Error code used when a <version> version number is invalid
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_VERSION', 13);
+
+/**
+ * Error code when release state is missing
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_STATE', 14);
+
+/**
+ * Error code when release state is invalid
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_STATE', 15);
+
+/**
+ * Error code when release state is missing
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_DATE', 16);
+
+/**
+ * Error code when release state is invalid
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_DATE', 17);
+
+/**
+ * Error code when no release notes are found
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_NOTES', 18);
+
+/**
+ * Error code when no maintainers are found
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS', 19);
+
+/**
+ * Error code when a maintainer has no handle
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE', 20);
+
+/**
+ * Error code when a maintainer has no handle
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE', 21);
+
+/**
+ * Error code when a maintainer has no name
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME', 22);
+
+/**
+ * Error code when a maintainer has no email
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL', 23);
+
+/**
+ * Error code when a maintainer has no handle
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_MAINTROLE', 24);
+
+/**
+ * Error code when a dependency is not a PHP dependency, but has no name
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_DEPNAME', 25);
+
+/**
+ * Error code when a dependency has no type (pkg, php, etc.)
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE', 26);
+
+/**
+ * Error code when a dependency has no relation (lt, ge, has, etc.)
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_DEPREL', 27);
+
+/**
+ * Error code when a dependency is not a 'has' relation, but has no version
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION', 28);
+
+/**
+ * Error code when a dependency has an invalid relation
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPREL', 29);
+
+/**
+ * Error code when a dependency has an invalid type
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPTYPE', 30);
+
+/**
+ * Error code when a dependency has an invalid optional option
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL', 31);
+
+/**
+ * Error code when a dependency is a pkg dependency, and has an invalid package name
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_DEPNAME', 32);
+
+/**
+ * Error code when a dependency has a channel="foo" attribute, and foo is not a registered channel
+ */
+define('PEAR_PACKAGEFILE_ERROR_UNKNOWN_DEPCHANNEL', 33);
+
+/**
+ * Error code when rel="has" and version attribute is present.
+ */
+define('PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED', 34);
+
+/**
+ * Error code when type="php" and dependency name is present
+ */
+define('PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED', 35);
+
+/**
+ * Error code when a configure option has no name
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_CONFNAME', 36);
+
+/**
+ * Error code when a configure option has no name
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT', 37);
+
+/**
+ * Error code when a file in the filelist has an invalid role
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE', 38);
+
+/**
+ * Error code when a file in the filelist has no role
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_FILEROLE', 39);
+
+/**
+ * Error code when analyzing a php source file that has parse errors
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE', 40);
+
+/**
+ * Error code when analyzing a php source file reveals a source element
+ * without a package name prefix
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX', 41);
+
+/**
+ * Error code when an unknown channel is specified
+ */
+define('PEAR_PACKAGEFILE_ERROR_UNKNOWN_CHANNEL', 42);
+
+/**
+ * Error code when no files are found in the filelist
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_FILES', 43);
+
+/**
+ * Error code when a file is not valid php according to _analyzeSourceCode()
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_FILE', 44);
+
+/**
+ * Error code when the channel validator returns an error or warning
+ */
+define('PEAR_PACKAGEFILE_ERROR_CHANNELVAL', 45);
+
+/**
+ * Error code when a php5 package is packaged in php4 (analysis doesn't work)
+ */
+define('PEAR_PACKAGEFILE_ERROR_PHP5', 46);
+
+/**
+ * Error code when a file is listed in package.xml but does not exist
+ */
+define('PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND', 47);
+
+/**
+ * Error code when a <dep type="php" rel="not"... is encountered (use rel="ne")
+ */
+define('PEAR_PACKAGEFILE_PHP_NO_NOT', 48);
+
+/**
+ * Error code when a package.xml contains non-ISO-8859-1 characters
+ */
+define('PEAR_PACKAGEFILE_ERROR_NON_ISO_CHARS', 49);
+
+/**
+ * Error code when a dependency is not a 'has' relation, but has no version
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION', 50);
+
+/**
+ * Error code when a package has no lead developer
+ */
+define('PEAR_PACKAGEFILE_ERROR_NO_LEAD', 51);
+
+/**
+ * Error code when a filename begins with "."
+ */
+define('PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME', 52);
+/**
+ * package.xml encapsulator
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_PackageFile_v1
+{
+ /**
+ * @access private
+ * @var PEAR_ErrorStack
+ * @access private
+ */
+ var $_stack;
+
+ /**
+ * A registry object, used to access the package name validation regex for non-standard channels
+ * @var PEAR_Registry
+ * @access private
+ */
+ var $_registry;
+
+ /**
+ * An object that contains a log method that matches PEAR_Common::log's signature
+ * @var object
+ * @access private
+ */
+ var $_logger;
+
+ /**
+ * Parsed package information
+ * @var array
+ * @access private
+ */
+ var $_packageInfo;
+
+ /**
+ * path to package.xml
+ * @var string
+ * @access private
+ */
+ var $_packageFile;
+
+ /**
+ * path to package .tgz or false if this is a local/extracted package.xml
+ * @var string
+ * @access private
+ */
+ var $_archiveFile;
+
+ /**
+ * @var int
+ * @access private
+ */
+ var $_isValid = 0;
+
+ /**
+ * Determines whether this packagefile was initialized only with partial package info
+ *
+ * If this package file was constructed via parsing REST, it will only contain
+ *
+ * - package name
+ * - channel name
+ * - dependencies
+ * @var boolean
+ * @access private
+ */
+ var $_incomplete = true;
+
+ /**
+ * @param bool determines whether to return a PEAR_Error object, or use the PEAR_ErrorStack
+ * @param string Name of Error Stack class to use.
+ */
+ function PEAR_PackageFile_v1()
+ {
+ $this->_stack = &new PEAR_ErrorStack('PEAR_PackageFile_v1');
+ $this->_stack->setErrorMessageTemplate($this->_getErrorMessage());
+ $this->_isValid = 0;
+ }
+
+ function installBinary($installer)
+ {
+ return false;
+ }
+
+ function isExtension($name)
+ {
+ return false;
+ }
+
+ function setConfig(&$config)
+ {
+ $this->_config = &$config;
+ $this->_registry = &$config->getRegistry();
+ }
+
+ function setRequestedGroup()
+ {
+ // placeholder
+ }
+
+ /**
+ * For saving in the registry.
+ *
+ * Set the last version that was installed
+ * @param string
+ */
+ function setLastInstalledVersion($version)
+ {
+ $this->_packageInfo['_lastversion'] = $version;
+ }
+
+ /**
+ * @return string|false
+ */
+ function getLastInstalledVersion()
+ {
+ if (isset($this->_packageInfo['_lastversion'])) {
+ return $this->_packageInfo['_lastversion'];
+ }
+ return false;
+ }
+
+ function getInstalledBinary()
+ {
+ return false;
+ }
+
+ function listPostinstallScripts()
+ {
+ return false;
+ }
+
+ function initPostinstallScripts()
+ {
+ return false;
+ }
+
+ function setLogger(&$logger)
+ {
+ if ($logger && (!is_object($logger) || !method_exists($logger, 'log'))) {
+ return PEAR::raiseError('Logger must be compatible with PEAR_Common::log');
+ }
+ $this->_logger = &$logger;
+ }
+
+ function setPackagefile($file, $archive = false)
+ {
+ $this->_packageFile = $file;
+ $this->_archiveFile = $archive ? $archive : $file;
+ }
+
+ function getPackageFile()
+ {
+ return isset($this->_packageFile) ? $this->_packageFile : false;
+ }
+
+ function getPackageType()
+ {
+ return 'php';
+ }
+
+ function getArchiveFile()
+ {
+ return $this->_archiveFile;
+ }
+
+ function packageInfo($field)
+ {
+ if (!is_string($field) || empty($field) ||
+ !isset($this->_packageInfo[$field])) {
+ return false;
+ }
+ return $this->_packageInfo[$field];
+ }
+
+ function setDirtree($path)
+ {
+ if (!isset($this->_packageInfo['dirtree'])) {
+ $this->_packageInfo['dirtree'] = array();
+ }
+ $this->_packageInfo['dirtree'][$path] = true;
+ }
+
+ function getDirtree()
+ {
+ if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) {
+ return $this->_packageInfo['dirtree'];
+ }
+ return false;
+ }
+
+ function resetDirtree()
+ {
+ unset($this->_packageInfo['dirtree']);
+ }
+
+ function fromArray($pinfo)
+ {
+ $this->_incomplete = false;
+ $this->_packageInfo = $pinfo;
+ }
+
+ function isIncomplete()
+ {
+ return $this->_incomplete;
+ }
+
+ function getChannel()
+ {
+ return 'pear.php.net';
+ }
+
+ function getUri()
+ {
+ return false;
+ }
+
+ function getTime()
+ {
+ return false;
+ }
+
+ function getExtends()
+ {
+ if (isset($this->_packageInfo['extends'])) {
+ return $this->_packageInfo['extends'];
+ }
+ return false;
+ }
+
+ /**
+ * @return array
+ */
+ function toArray()
+ {
+ if (!$this->validate(PEAR_VALIDATE_NORMAL)) {
+ return false;
+ }
+ return $this->getArray();
+ }
+
+ function getArray()
+ {
+ return $this->_packageInfo;
+ }
+
+ function getName()
+ {
+ return $this->getPackage();
+ }
+
+ function getPackage()
+ {
+ if (isset($this->_packageInfo['package'])) {
+ return $this->_packageInfo['package'];
+ }
+ return false;
+ }
+
+ /**
+ * WARNING - don't use this unless you know what you are doing
+ */
+ function setRawPackage($package)
+ {
+ $this->_packageInfo['package'] = $package;
+ }
+
+ function setPackage($package)
+ {
+ $this->_packageInfo['package'] = $package;
+ $this->_isValid = false;
+ }
+
+ function getVersion()
+ {
+ if (isset($this->_packageInfo['version'])) {
+ return $this->_packageInfo['version'];
+ }
+ return false;
+ }
+
+ function setVersion($version)
+ {
+ $this->_packageInfo['version'] = $version;
+ $this->_isValid = false;
+ }
+
+ function clearMaintainers()
+ {
+ unset($this->_packageInfo['maintainers']);
+ }
+
+ function getMaintainers()
+ {
+ if (isset($this->_packageInfo['maintainers'])) {
+ return $this->_packageInfo['maintainers'];
+ }
+ return false;
+ }
+
+ /**
+ * Adds a new maintainer - no checking of duplicates is performed, use
+ * updatemaintainer for that purpose.
+ */
+ function addMaintainer($role, $handle, $name, $email)
+ {
+ $this->_packageInfo['maintainers'][] =
+ array('handle' => $handle, 'role' => $role, 'email' => $email, 'name' => $name);
+ $this->_isValid = false;
+ }
+
+ function updateMaintainer($role, $handle, $name, $email)
+ {
+ $found = false;
+ if (!isset($this->_packageInfo['maintainers']) ||
+ !is_array($this->_packageInfo['maintainers'])) {
+ return $this->addMaintainer($role, $handle, $name, $email);
+ }
+ foreach ($this->_packageInfo['maintainers'] as $i => $maintainer) {
+ if ($maintainer['handle'] == $handle) {
+ $found = $i;
+ break;
+ }
+ }
+ if ($found !== false) {
+ unset($this->_packageInfo['maintainers'][$found]);
+ $this->_packageInfo['maintainers'] =
+ array_values($this->_packageInfo['maintainers']);
+ }
+ $this->addMaintainer($role, $handle, $name, $email);
+ }
+
+ function deleteMaintainer($handle)
+ {
+ $found = false;
+ foreach ($this->_packageInfo['maintainers'] as $i => $maintainer) {
+ if ($maintainer['handle'] == $handle) {
+ $found = $i;
+ break;
+ }
+ }
+ if ($found !== false) {
+ unset($this->_packageInfo['maintainers'][$found]);
+ $this->_packageInfo['maintainers'] =
+ array_values($this->_packageInfo['maintainers']);
+ return true;
+ }
+ return false;
+ }
+
+ function getState()
+ {
+ if (isset($this->_packageInfo['release_state'])) {
+ return $this->_packageInfo['release_state'];
+ }
+ return false;
+ }
+
+ function setRawState($state)
+ {
+ $this->_packageInfo['release_state'] = $state;
+ }
+
+ function setState($state)
+ {
+ $this->_packageInfo['release_state'] = $state;
+ $this->_isValid = false;
+ }
+
+ function getDate()
+ {
+ if (isset($this->_packageInfo['release_date'])) {
+ return $this->_packageInfo['release_date'];
+ }
+ return false;
+ }
+
+ function setDate($date)
+ {
+ $this->_packageInfo['release_date'] = $date;
+ $this->_isValid = false;
+ }
+
+ function getLicense()
+ {
+ if (isset($this->_packageInfo['release_license'])) {
+ return $this->_packageInfo['release_license'];
+ }
+ return false;
+ }
+
+ function setLicense($date)
+ {
+ $this->_packageInfo['release_license'] = $date;
+ $this->_isValid = false;
+ }
+
+ function getSummary()
+ {
+ if (isset($this->_packageInfo['summary'])) {
+ return $this->_packageInfo['summary'];
+ }
+ return false;
+ }
+
+ function setSummary($summary)
+ {
+ $this->_packageInfo['summary'] = $summary;
+ $this->_isValid = false;
+ }
+
+ function getDescription()
+ {
+ if (isset($this->_packageInfo['description'])) {
+ return $this->_packageInfo['description'];
+ }
+ return false;
+ }
+
+ function setDescription($desc)
+ {
+ $this->_packageInfo['description'] = $desc;
+ $this->_isValid = false;
+ }
+
+ function getNotes()
+ {
+ if (isset($this->_packageInfo['release_notes'])) {
+ return $this->_packageInfo['release_notes'];
+ }
+ return false;
+ }
+
+ function setNotes($notes)
+ {
+ $this->_packageInfo['release_notes'] = $notes;
+ $this->_isValid = false;
+ }
+
+ function getDeps()
+ {
+ if (isset($this->_packageInfo['release_deps'])) {
+ return $this->_packageInfo['release_deps'];
+ }
+ return false;
+ }
+
+ /**
+ * Reset dependencies prior to adding new ones
+ */
+ function clearDeps()
+ {
+ unset($this->_packageInfo['release_deps']);
+ }
+
+ function addPhpDep($version, $rel)
+ {
+ $this->_isValid = false;
+ $this->_packageInfo['release_deps'][] =
+ array('type' => 'php',
+ 'rel' => $rel,
+ 'version' => $version);
+ }
+
+ function addPackageDep($name, $version, $rel, $optional = 'no')
+ {
+ $this->_isValid = false;
+ $dep =
+ array('type' => 'pkg',
+ 'name' => $name,
+ 'rel' => $rel,
+ 'optional' => $optional);
+ if ($rel != 'has' && $rel != 'not') {
+ $dep['version'] = $version;
+ }
+ $this->_packageInfo['release_deps'][] = $dep;
+ }
+
+ function addExtensionDep($name, $version, $rel, $optional = 'no')
+ {
+ $this->_isValid = false;
+ $this->_packageInfo['release_deps'][] =
+ array('type' => 'ext',
+ 'name' => $name,
+ 'rel' => $rel,
+ 'version' => $version,
+ 'optional' => $optional);
+ }
+
+ /**
+ * WARNING - do not use this function directly unless you know what you're doing
+ */
+ function setDeps($deps)
+ {
+ $this->_packageInfo['release_deps'] = $deps;
+ }
+
+ function hasDeps()
+ {
+ return isset($this->_packageInfo['release_deps']) &&
+ count($this->_packageInfo['release_deps']);
+ }
+
+ function getDependencyGroup($group)
+ {
+ return false;
+ }
+
+ function isCompatible($pf)
+ {
+ return false;
+ }
+
+ function isSubpackageOf($p)
+ {
+ return $p->isSubpackage($this);
+ }
+
+ function isSubpackage($p)
+ {
+ return false;
+ }
+
+ function dependsOn($package, $channel)
+ {
+ if (strtolower($channel) != 'pear.php.net') {
+ return false;
+ }
+ if (!($deps = $this->getDeps())) {
+ return false;
+ }
+ foreach ($deps as $dep) {
+ if ($dep['type'] != 'pkg') {
+ continue;
+ }
+ if (strtolower($dep['name']) == strtolower($package)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function getConfigureOptions()
+ {
+ if (isset($this->_packageInfo['configure_options'])) {
+ return $this->_packageInfo['configure_options'];
+ }
+ return false;
+ }
+
+ function hasConfigureOptions()
+ {
+ return isset($this->_packageInfo['configure_options']) &&
+ count($this->_packageInfo['configure_options']);
+ }
+
+ function addConfigureOption($name, $prompt, $default = false)
+ {
+ $o = array('name' => $name, 'prompt' => $prompt);
+ if ($default !== false) {
+ $o['default'] = $default;
+ }
+ if (!isset($this->_packageInfo['configure_options'])) {
+ $this->_packageInfo['configure_options'] = array();
+ }
+ $this->_packageInfo['configure_options'][] = $o;
+ }
+
+ function clearConfigureOptions()
+ {
+ unset($this->_packageInfo['configure_options']);
+ }
+
+ function getProvides()
+ {
+ if (isset($this->_packageInfo['provides'])) {
+ return $this->_packageInfo['provides'];
+ }
+ return false;
+ }
+
+ function getProvidesExtension()
+ {
+ return false;
+ }
+
+ function addFile($dir, $file, $attrs)
+ {
+ $dir = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), $dir);
+ if ($dir == '/' || $dir == '') {
+ $dir = '';
+ } else {
+ $dir .= '/';
+ }
+ $file = $dir . $file;
+ $file = preg_replace('![\\/]+!', '/', $file);
+ $this->_packageInfo['filelist'][$file] = $attrs;
+ }
+
+ function getInstallationFilelist()
+ {
+ return $this->getFilelist();
+ }
+
+ function getFilelist()
+ {
+ if (isset($this->_packageInfo['filelist'])) {
+ return $this->_packageInfo['filelist'];
+ }
+ return false;
+ }
+
+ function setFileAttribute($file, $attr, $value)
+ {
+ $this->_packageInfo['filelist'][$file][$attr] = $value;
+ }
+
+ function resetFilelist()
+ {
+ $this->_packageInfo['filelist'] = array();
+ }
+
+ function setInstalledAs($file, $path)
+ {
+ if ($path) {
+ return $this->_packageInfo['filelist'][$file]['installed_as'] = $path;
+ }
+ unset($this->_packageInfo['filelist'][$file]['installed_as']);
+ }
+
+ function installedFile($file, $atts)
+ {
+ if (isset($this->_packageInfo['filelist'][$file])) {
+ $this->_packageInfo['filelist'][$file] =
+ array_merge($this->_packageInfo['filelist'][$file], $atts);
+ } else {
+ $this->_packageInfo['filelist'][$file] = $atts;
+ }
+ }
+
+ function getChangelog()
+ {
+ if (isset($this->_packageInfo['changelog'])) {
+ return $this->_packageInfo['changelog'];
+ }
+ return false;
+ }
+
+ function getPackagexmlVersion()
+ {
+ return '1.0';
+ }
+
+ /**
+ * Wrapper to {@link PEAR_ErrorStack::getErrors()}
+ * @param boolean determines whether to purge the error stack after retrieving
+ * @return array
+ */
+ function getValidationWarnings($purge = true)
+ {
+ return $this->_stack->getErrors($purge);
+ }
+
+ // }}}
+ /**
+ * Validation error. Also marks the object contents as invalid
+ * @param error code
+ * @param array error information
+ * @access private
+ */
+ function _validateError($code, $params = array())
+ {
+ $this->_stack->push($code, 'error', $params, false, false, debug_backtrace());
+ $this->_isValid = false;
+ }
+
+ /**
+ * Validation warning. Does not mark the object contents invalid.
+ * @param error code
+ * @param array error information
+ * @access private
+ */
+ function _validateWarning($code, $params = array())
+ {
+ $this->_stack->push($code, 'warning', $params, false, false, debug_backtrace());
+ }
+
+ /**
+ * @param integer error code
+ * @access protected
+ */
+ function _getErrorMessage()
+ {
+ return array(
+ PEAR_PACKAGEFILE_ERROR_NO_NAME =>
+ 'Missing Package Name',
+ PEAR_PACKAGEFILE_ERROR_NO_SUMMARY =>
+ 'No summary found',
+ PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY =>
+ 'Summary should be on one line',
+ PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION =>
+ 'Missing description',
+ PEAR_PACKAGEFILE_ERROR_NO_LICENSE =>
+ 'Missing license',
+ PEAR_PACKAGEFILE_ERROR_NO_VERSION =>
+ 'No release version found',
+ PEAR_PACKAGEFILE_ERROR_NO_STATE =>
+ 'No release state found',
+ PEAR_PACKAGEFILE_ERROR_NO_DATE =>
+ 'No release date found',
+ PEAR_PACKAGEFILE_ERROR_NO_NOTES =>
+ 'No release notes found',
+ PEAR_PACKAGEFILE_ERROR_NO_LEAD =>
+ 'Package must have at least one lead maintainer',
+ PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS =>
+ 'No maintainers found, at least one must be defined',
+ PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE =>
+ 'Maintainer %index% has no handle (user ID at channel server)',
+ PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE =>
+ 'Maintainer %index% has no role',
+ PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME =>
+ 'Maintainer %index% has no name',
+ PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL =>
+ 'Maintainer %index% has no email',
+ PEAR_PACKAGEFILE_ERROR_NO_DEPNAME =>
+ 'Dependency %index% is not a php dependency, and has no name',
+ PEAR_PACKAGEFILE_ERROR_NO_DEPREL =>
+ 'Dependency %index% has no relation (rel)',
+ PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE =>
+ 'Dependency %index% has no type',
+ PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED =>
+ 'PHP Dependency %index% has a name attribute of "%name%" which will be' .
+ ' ignored!',
+ PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION =>
+ 'Dependency %index% is not a rel="has" or rel="not" dependency, ' .
+ 'and has no version',
+ PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION =>
+ 'Dependency %index% is a type="php" dependency, ' .
+ 'and has no version',
+ PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED =>
+ 'Dependency %index% is a rel="%rel%" dependency, versioning is ignored',
+ PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL =>
+ 'Dependency %index% has invalid optional value "%opt%", should be yes or no',
+ PEAR_PACKAGEFILE_PHP_NO_NOT =>
+ 'Dependency %index%: php dependencies cannot use "not" rel, use "ne"' .
+ ' to exclude specific versions',
+ PEAR_PACKAGEFILE_ERROR_NO_CONFNAME =>
+ 'Configure Option %index% has no name',
+ PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT =>
+ 'Configure Option %index% has no prompt',
+ PEAR_PACKAGEFILE_ERROR_NO_FILES =>
+ 'No files in <filelist> section of package.xml',
+ PEAR_PACKAGEFILE_ERROR_NO_FILEROLE =>
+ 'File "%file%" has no role, expecting one of "%roles%"',
+ PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE =>
+ 'File "%file%" has invalid role "%role%", expecting one of "%roles%"',
+ PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME =>
+ 'File "%file%" cannot start with ".", cannot package or install',
+ PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE =>
+ 'Parser error: invalid PHP found in file "%file%"',
+ PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX =>
+ 'in %file%: %type% "%name%" not prefixed with package name "%package%"',
+ PEAR_PACKAGEFILE_ERROR_INVALID_FILE =>
+ 'Parser error: invalid PHP file "%file%"',
+ PEAR_PACKAGEFILE_ERROR_CHANNELVAL =>
+ 'Channel validator error: field "%field%" - %reason%',
+ PEAR_PACKAGEFILE_ERROR_PHP5 =>
+ 'Error, PHP5 token encountered in %file%, analysis should be in PHP5',
+ PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND =>
+ 'File "%file%" in package.xml does not exist',
+ PEAR_PACKAGEFILE_ERROR_NON_ISO_CHARS =>
+ 'Package.xml contains non-ISO-8859-1 characters, and may not validate',
+ );
+ }
+
+ /**
+ * Validate XML package definition file.
+ *
+ * @access public
+ * @return boolean
+ */
+ function validate($state = PEAR_VALIDATE_NORMAL, $nofilechecking = false)
+ {
+ if (($this->_isValid & $state) == $state) {
+ return true;
+ }
+ $this->_isValid = true;
+ $info = $this->_packageInfo;
+ if (empty($info['package'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_NAME);
+ $this->_packageName = $pn = 'unknown';
+ } else {
+ $this->_packageName = $pn = $info['package'];
+ }
+
+ if (empty($info['summary'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_SUMMARY);
+ } elseif (strpos(trim($info['summary']), "\n") !== false) {
+ $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_MULTILINE_SUMMARY,
+ array('summary' => $info['summary']));
+ }
+ if (empty($info['description'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DESCRIPTION);
+ }
+ if (empty($info['release_license'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_LICENSE);
+ }
+ if (empty($info['version'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_VERSION);
+ }
+ if (empty($info['release_state'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_STATE);
+ }
+ if (empty($info['release_date'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DATE);
+ }
+ if (empty($info['release_notes'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_NOTES);
+ }
+ if (empty($info['maintainers'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTAINERS);
+ } else {
+ $haslead = false;
+ $i = 1;
+ foreach ($info['maintainers'] as $m) {
+ if (empty($m['handle'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTHANDLE,
+ array('index' => $i));
+ }
+ if (empty($m['role'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTROLE,
+ array('index' => $i, 'roles' => PEAR_Common::getUserRoles()));
+ } elseif ($m['role'] == 'lead') {
+ $haslead = true;
+ }
+ if (empty($m['name'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTNAME,
+ array('index' => $i));
+ }
+ if (empty($m['email'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_MAINTEMAIL,
+ array('index' => $i));
+ }
+ $i++;
+ }
+ if (!$haslead) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_LEAD);
+ }
+ }
+ if (!empty($info['release_deps'])) {
+ $i = 1;
+ foreach ($info['release_deps'] as $d) {
+ if (!isset($d['type']) || empty($d['type'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPTYPE,
+ array('index' => $i, 'types' => PEAR_Common::getDependencyTypes()));
+ continue;
+ }
+ if (!isset($d['rel']) || empty($d['rel'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPREL,
+ array('index' => $i, 'rels' => PEAR_Common::getDependencyRelations()));
+ continue;
+ }
+ if (!empty($d['optional'])) {
+ if (!in_array($d['optional'], array('yes', 'no'))) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_DEPOPTIONAL,
+ array('index' => $i, 'opt' => $d['optional']));
+ }
+ }
+ if ($d['rel'] != 'has' && $d['rel'] != 'not' && empty($d['version'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPVERSION,
+ array('index' => $i));
+ } elseif (($d['rel'] == 'has' || $d['rel'] == 'not') && !empty($d['version'])) {
+ $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_DEPVERSION_IGNORED,
+ array('index' => $i, 'rel' => $d['rel']));
+ }
+ if ($d['type'] == 'php' && !empty($d['name'])) {
+ $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_DEPNAME_IGNORED,
+ array('index' => $i, 'name' => $d['name']));
+ } elseif ($d['type'] != 'php' && empty($d['name'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPNAME,
+ array('index' => $i));
+ }
+ if ($d['type'] == 'php' && empty($d['version'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_DEPPHPVERSION,
+ array('index' => $i));
+ }
+ if (($d['rel'] == 'not') && ($d['type'] == 'php')) {
+ $this->_validateError(PEAR_PACKAGEFILE_PHP_NO_NOT,
+ array('index' => $i));
+ }
+ $i++;
+ }
+ }
+ if (!empty($info['configure_options'])) {
+ $i = 1;
+ foreach ($info['configure_options'] as $c) {
+ if (empty($c['name'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_CONFNAME,
+ array('index' => $i));
+ }
+ if (empty($c['prompt'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_CONFPROMPT,
+ array('index' => $i));
+ }
+ $i++;
+ }
+ }
+ if (empty($info['filelist'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_FILES);
+ $errors[] = 'no files';
+ } else {
+ foreach ($info['filelist'] as $file => $fa) {
+ if (empty($fa['role'])) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_NO_FILEROLE,
+ array('file' => $file, 'roles' => PEAR_Common::getFileRoles()));
+ continue;
+ } elseif (!in_array($fa['role'], PEAR_Common::getFileRoles())) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILEROLE,
+ array('file' => $file, 'role' => $fa['role'], 'roles' => PEAR_Common::getFileRoles()));
+ }
+ if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', str_replace('\\', '/', $file))) {
+ // file contains .. parent directory or . cur directory references
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME,
+ array('file' => $file));
+ }
+ if (isset($fa['install-as']) &&
+ preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
+ str_replace('\\', '/', $fa['install-as']))) {
+ // install-as contains .. parent directory or . cur directory references
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME,
+ array('file' => $file . ' [installed as ' . $fa['install-as'] . ']'));
+ }
+ if (isset($fa['baseinstalldir']) &&
+ preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
+ str_replace('\\', '/', $fa['baseinstalldir']))) {
+ // install-as contains .. parent directory or . cur directory references
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_FILENAME,
+ array('file' => $file . ' [baseinstalldir ' . $fa['baseinstalldir'] . ']'));
+ }
+ }
+ }
+ if (isset($this->_registry) && $this->_isValid) {
+ $chan = $this->_registry->getChannel('pear.php.net');
+ if (PEAR::isError($chan)) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $chan->getMessage());
+ return $this->_isValid = 0;
+ }
+ $validator = $chan->getValidationObject();
+ $validator->setPackageFile($this);
+ $validator->validate($state);
+ $failures = $validator->getFailures();
+ foreach ($failures['errors'] as $error) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $error);
+ }
+ foreach ($failures['warnings'] as $warning) {
+ $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_CHANNELVAL, $warning);
+ }
+ }
+ if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$nofilechecking) {
+ if ($this->_analyzePhpFiles()) {
+ $this->_isValid = true;
+ }
+ }
+ if ($this->_isValid) {
+ return $this->_isValid = $state;
+ }
+ return $this->_isValid = 0;
+ }
+
+ function _analyzePhpFiles()
+ {
+ if (!$this->_isValid) {
+ return false;
+ }
+ if (!isset($this->_packageFile)) {
+ return false;
+ }
+ $dir_prefix = dirname($this->_packageFile);
+ $common = new PEAR_Common;
+ $log = isset($this->_logger) ? array(&$this->_logger, 'log') :
+ array($common, 'log');
+ $info = $this->getFilelist();
+ foreach ($info as $file => $fa) {
+ if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $file)) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_FILE_NOTFOUND,
+ array('file' => realpath($dir_prefix) . DIRECTORY_SEPARATOR . $file));
+ continue;
+ }
+ if ($fa['role'] == 'php' && $dir_prefix) {
+ call_user_func_array($log, array(1, "Analyzing $file"));
+ $srcinfo = $this->_analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file);
+ if ($srcinfo) {
+ $this->_buildProvidesArray($srcinfo);
+ }
+ }
+ }
+ $this->_packageName = $pn = $this->getPackage();
+ $pnl = strlen($pn);
+ if (isset($this->_packageInfo['provides'])) {
+ foreach ((array) $this->_packageInfo['provides'] as $key => $what) {
+ if (isset($what['explicit'])) {
+ // skip conformance checks if the provides entry is
+ // specified in the package.xml file
+ continue;
+ }
+ extract($what);
+ if ($type == 'class') {
+ if (!strncasecmp($name, $pn, $pnl)) {
+ continue;
+ }
+ $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX,
+ array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn));
+ } elseif ($type == 'function') {
+ if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) {
+ continue;
+ }
+ $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_NO_PNAME_PREFIX,
+ array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn));
+ }
+ }
+ }
+ return $this->_isValid;
+ }
+
+ /**
+ * Get the default xml generator object
+ *
+ * @return PEAR_PackageFile_Generator_v1
+ */
+ function &getDefaultGenerator()
+ {
+ if (!class_exists('PEAR_PackageFile_Generator_v1')) {
+ require_once 'PEAR/PackageFile/Generator/v1.php';
+ }
+ $a = &new PEAR_PackageFile_Generator_v1($this);
+ return $a;
+ }
+
+ /**
+ * Get the contents of a file listed within the package.xml
+ * @param string
+ * @return string
+ */
+ function getFileContents($file)
+ {
+ if ($this->_archiveFile == $this->_packageFile) { // unpacked
+ $dir = dirname($this->_packageFile);
+ $file = $dir . DIRECTORY_SEPARATOR . $file;
+ $file = str_replace(array('/', '\\'),
+ array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file);
+ if (file_exists($file) && is_readable($file)) {
+ return implode('', file($file));
+ }
+ } else { // tgz
+ if (!class_exists('Archive_Tar')) {
+ require_once 'Archive/Tar.php';
+ }
+ $tar = &new Archive_Tar($this->_archiveFile);
+ $tar->pushErrorHandling(PEAR_ERROR_RETURN);
+ if ($file != 'package.xml' && $file != 'package2.xml') {
+ $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file;
+ }
+ $file = $tar->extractInString($file);
+ $tar->popErrorHandling();
+ if (PEAR::isError($file)) {
+ return PEAR::raiseError("Cannot locate file '$file' in archive");
+ }
+ return $file;
+ }
+ }
+
+ // {{{ analyzeSourceCode()
+ /**
+ * Analyze the source code of the given PHP file
+ *
+ * @param string Filename of the PHP file
+ * @return mixed
+ * @access private
+ */
+ function _analyzeSourceCode($file)
+ {
+ if (!function_exists("token_get_all")) {
+ return false;
+ }
+ if (!defined('T_DOC_COMMENT')) {
+ define('T_DOC_COMMENT', T_COMMENT);
+ }
+ if (!defined('T_INTERFACE')) {
+ define('T_INTERFACE', -1);
+ }
+ if (!defined('T_IMPLEMENTS')) {
+ define('T_IMPLEMENTS', -1);
+ }
+ if (!$fp = @fopen($file, "r")) {
+ return false;
+ }
+ fclose($fp);
+ $contents = file_get_contents($file);
+ $tokens = token_get_all($contents);
+/*
+ for ($i = 0; $i < sizeof($tokens); $i++) {
+ @list($token, $data) = $tokens[$i];
+ if (is_string($token)) {
+ var_dump($token);
+ } else {
+ print token_name($token) . ' ';
+ var_dump(rtrim($data));
+ }
+ }
+*/
+ $look_for = 0;
+ $paren_level = 0;
+ $bracket_level = 0;
+ $brace_level = 0;
+ $lastphpdoc = '';
+ $current_class = '';
+ $current_interface = '';
+ $current_class_level = -1;
+ $current_function = '';
+ $current_function_level = -1;
+ $declared_classes = array();
+ $declared_interfaces = array();
+ $declared_functions = array();
+ $declared_methods = array();
+ $used_classes = array();
+ $used_functions = array();
+ $extends = array();
+ $implements = array();
+ $nodeps = array();
+ $inquote = false;
+ $interface = false;
+ for ($i = 0; $i < sizeof($tokens); $i++) {
+ if (is_array($tokens[$i])) {
+ list($token, $data) = $tokens[$i];
+ } else {
+ $token = $tokens[$i];
+ $data = '';
+ }
+ if ($inquote) {
+ if ($token != '"' && $token != T_END_HEREDOC) {
+ continue;
+ } else {
+ $inquote = false;
+ continue;
+ }
+ }
+ switch ($token) {
+ case T_WHITESPACE :
+ continue;
+ case ';':
+ if ($interface) {
+ $current_function = '';
+ $current_function_level = -1;
+ }
+ break;
+ case '"':
+ case T_START_HEREDOC:
+ $inquote = true;
+ break;
+ case T_CURLY_OPEN:
+ case T_DOLLAR_OPEN_CURLY_BRACES:
+ case '{': $brace_level++; continue 2;
+ case '}':
+ $brace_level--;
+ if ($current_class_level == $brace_level) {
+ $current_class = '';
+ $current_class_level = -1;
+ }
+ if ($current_function_level == $brace_level) {
+ $current_function = '';
+ $current_function_level = -1;
+ }
+ continue 2;
+ case '[': $bracket_level++; continue 2;
+ case ']': $bracket_level--; continue 2;
+ case '(': $paren_level++; continue 2;
+ case ')': $paren_level--; continue 2;
+ case T_INTERFACE:
+ $interface = true;
+ case T_CLASS:
+ if (($current_class_level != -1) || ($current_function_level != -1)) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE,
+ array('file' => $file));
+ return false;
+ }
+ case T_FUNCTION:
+ case T_NEW:
+ case T_EXTENDS:
+ case T_IMPLEMENTS:
+ $look_for = $token;
+ continue 2;
+ case T_STRING:
+ if (version_compare(zend_version(), '2.0', '<')) {
+ if (in_array(strtolower($data),
+ array('public', 'private', 'protected', 'abstract',
+ 'interface', 'implements', 'throw')
+ )) {
+ $this->_validateWarning(PEAR_PACKAGEFILE_ERROR_PHP5,
+ array($file));
+ }
+ }
+ if ($look_for == T_CLASS) {
+ $current_class = $data;
+ $current_class_level = $brace_level;
+ $declared_classes[] = $current_class;
+ } elseif ($look_for == T_INTERFACE) {
+ $current_interface = $data;
+ $current_class_level = $brace_level;
+ $declared_interfaces[] = $current_interface;
+ } elseif ($look_for == T_IMPLEMENTS) {
+ $implements[$current_class] = $data;
+ } elseif ($look_for == T_EXTENDS) {
+ $extends[$current_class] = $data;
+ } elseif ($look_for == T_FUNCTION) {
+ if ($current_class) {
+ $current_function = "$current_class::$data";
+ $declared_methods[$current_class][] = $data;
+ } elseif ($current_interface) {
+ $current_function = "$current_interface::$data";
+ $declared_methods[$current_interface][] = $data;
+ } else {
+ $current_function = $data;
+ $declared_functions[] = $current_function;
+ }
+ $current_function_level = $brace_level;
+ $m = array();
+ } elseif ($look_for == T_NEW) {
+ $used_classes[$data] = true;
+ }
+ $look_for = 0;
+ continue 2;
+ case T_VARIABLE:
+ $look_for = 0;
+ continue 2;
+ case T_DOC_COMMENT:
+ case T_COMMENT:
+ if (preg_match('!^/\*\*\s!', $data)) {
+ $lastphpdoc = $data;
+ if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) {
+ $nodeps = array_merge($nodeps, $m[1]);
+ }
+ }
+ continue 2;
+ case T_DOUBLE_COLON:
+ if (!($tokens[$i - 1][0] == T_WHITESPACE || $tokens[$i - 1][0] == T_STRING)) {
+ $this->_validateError(PEAR_PACKAGEFILE_ERROR_INVALID_PHPFILE,
+ array('file' => $file));
+ return false;
+ }
+ $class = $tokens[$i - 1][1];
+ if (strtolower($class) != 'parent') {
+ $used_classes[$class] = true;
+ }
+ continue 2;
+ }
+ }
+ return array(
+ "source_file" => $file,
+ "declared_classes" => $declared_classes,
+ "declared_interfaces" => $declared_interfaces,
+ "declared_methods" => $declared_methods,
+ "declared_functions" => $declared_functions,
+ "used_classes" => array_diff(array_keys($used_classes), $nodeps),
+ "inheritance" => $extends,
+ "implements" => $implements,
+ );
+ }
+
+ /**
+ * Build a "provides" array from data returned by
+ * analyzeSourceCode(). The format of the built array is like
+ * this:
+ *
+ * array(
+ * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'),
+ * ...
+ * )
+ *
+ *
+ * @param array $srcinfo array with information about a source file
+ * as returned by the analyzeSourceCode() method.
+ *
+ * @return void
+ *
+ * @access private
+ *
+ */
+ function _buildProvidesArray($srcinfo)
+ {
+ if (!$this->_isValid) {
+ return false;
+ }
+ $file = basename($srcinfo['source_file']);
+ $pn = $this->getPackage();
+ $pnl = strlen($pn);
+ foreach ($srcinfo['declared_classes'] as $class) {
+ $key = "class;$class";
+ if (isset($this->_packageInfo['provides'][$key])) {
+ continue;
+ }
+ $this->_packageInfo['provides'][$key] =
+ array('file'=> $file, 'type' => 'class', 'name' => $class);
+ if (isset($srcinfo['inheritance'][$class])) {
+ $this->_packageInfo['provides'][$key]['extends'] =
+ $srcinfo['inheritance'][$class];
+ }
+ }
+ foreach ($srcinfo['declared_methods'] as $class => $methods) {
+ foreach ($methods as $method) {
+ $function = "$class::$method";
+ $key = "function;$function";
+ if ($method{0} == '_' || !strcasecmp($method, $class) ||
+ isset($this->_packageInfo['provides'][$key])) {
+ continue;
+ }
+ $this->_packageInfo['provides'][$key] =
+ array('file'=> $file, 'type' => 'function', 'name' => $function);
+ }
+ }
+
+ foreach ($srcinfo['declared_functions'] as $function) {
+ $key = "function;$function";
+ if ($function{0} == '_' || isset($this->_packageInfo['provides'][$key])) {
+ continue;
+ }
+ if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) {
+ $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\"";
+ }
+ $this->_packageInfo['provides'][$key] =
+ array('file'=> $file, 'type' => 'function', 'name' => $function);
+ }
+ }
+
+ // }}}
+}
+?>
diff --git a/includes/pear/PEAR/PackageFile/v2.php b/includes/pear/PEAR/PackageFile/v2.php
new file mode 100644
index 0000000..27310d4
--- /dev/null
+++ b/includes/pear/PEAR/PackageFile/v2.php
@@ -0,0 +1,2049 @@
+<?php
+/**
+ * PEAR_PackageFile_v2, package.xml version 2.0
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+/**
+ * For error handling
+ */
+require_once 'PEAR/ErrorStack.php';
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_PackageFile_v2
+{
+
+ /**
+ * Parsed package information
+ * @var array
+ * @access private
+ */
+ var $_packageInfo = array();
+
+ /**
+ * path to package .tgz or false if this is a local/extracted package.xml
+ * @var string|false
+ * @access private
+ */
+ var $_archiveFile;
+
+ /**
+ * path to package .xml or false if this is an abstract parsed-from-string xml
+ * @var string|false
+ * @access private
+ */
+ var $_packageFile;
+
+ /**
+ * This is used by file analysis routines to log progress information
+ * @var PEAR_Common
+ * @access protected
+ */
+ var $_logger;
+
+ /**
+ * This is set to the highest validation level that has been validated
+ *
+ * If the package.xml is invalid or unknown, this is set to 0. If
+ * normal validation has occurred, this is set to PEAR_VALIDATE_NORMAL. If
+ * downloading/installation validation has occurred it is set to PEAR_VALIDATE_DOWNLOADING
+ * or INSTALLING, and so on up to PEAR_VALIDATE_PACKAGING. This allows validation
+ * "caching" to occur, which is particularly important for package validation, so
+ * that PHP files are not validated twice
+ * @var int
+ * @access private
+ */
+ var $_isValid = 0;
+
+ /**
+ * True if the filelist has been validated
+ * @param bool
+ */
+ var $_filesValid = false;
+
+ /**
+ * @var PEAR_Registry
+ * @access protected
+ */
+ var $_registry;
+
+ /**
+ * @var PEAR_Config
+ * @access protected
+ */
+ var $_config;
+
+ /**
+ * Optional Dependency group requested for installation
+ * @var string
+ * @access private
+ */
+ var $_requestedGroup = false;
+
+ /**
+ * @var PEAR_ErrorStack
+ * @access protected
+ */
+ var $_stack;
+
+ /**
+ * Namespace prefix used for tasks in this package.xml - use tasks: whenever possible
+ */
+ var $_tasksNs;
+
+ /**
+ * Determines whether this packagefile was initialized only with partial package info
+ *
+ * If this package file was constructed via parsing REST, it will only contain
+ *
+ * - package name
+ * - channel name
+ * - dependencies
+ * @var boolean
+ * @access private
+ */
+ var $_incomplete = true;
+
+ /**
+ * @var PEAR_PackageFile_v2_Validator
+ */
+ var $_v2Validator;
+
+ /**
+ * The constructor merely sets up the private error stack
+ */
+ function PEAR_PackageFile_v2()
+ {
+ $this->_stack = new PEAR_ErrorStack('PEAR_PackageFile_v2', false, null);
+ $this->_isValid = false;
+ }
+
+ /**
+ * To make unit-testing easier
+ * @param PEAR_Frontend_*
+ * @param array options
+ * @param PEAR_Config
+ * @return PEAR_Downloader
+ * @access protected
+ */
+ function &getPEARDownloader(&$i, $o, &$c)
+ {
+ $z = &new PEAR_Downloader($i, $o, $c);
+ return $z;
+ }
+
+ /**
+ * To make unit-testing easier
+ * @param PEAR_Config
+ * @param array options
+ * @param array package name as returned from {@link PEAR_Registry::parsePackageName()}
+ * @param int PEAR_VALIDATE_* constant
+ * @return PEAR_Dependency2
+ * @access protected
+ */
+ function &getPEARDependency2(&$c, $o, $p, $s = PEAR_VALIDATE_INSTALLING)
+ {
+ if (!class_exists('PEAR_Dependency2')) {
+ require_once 'PEAR/Dependency2.php';
+ }
+ $z = &new PEAR_Dependency2($c, $o, $p, $s);
+ return $z;
+ }
+
+ function getInstalledBinary()
+ {
+ return isset($this->_packageInfo['#binarypackage']) ? $this->_packageInfo['#binarypackage'] :
+ false;
+ }
+
+ /**
+ * Installation of source package has failed, attempt to download and install the
+ * binary version of this package.
+ * @param PEAR_Installer
+ * @return array|false
+ */
+ function installBinary(&$installer)
+ {
+ if (!OS_WINDOWS) {
+ $a = false;
+ return $a;
+ }
+ if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') {
+ $releasetype = $this->getPackageType() . 'release';
+ if (!is_array($installer->getInstallPackages())) {
+ $a = false;
+ return $a;
+ }
+ foreach ($installer->getInstallPackages() as $p) {
+ if ($p->isExtension($this->_packageInfo['providesextension'])) {
+ if ($p->getPackageType() != 'extsrc' && $p->getPackageType() != 'zendextsrc') {
+ $a = false;
+ return $a; // the user probably downloaded it separately
+ }
+ }
+ }
+ if (isset($this->_packageInfo[$releasetype]['binarypackage'])) {
+ $installer->log(0, 'Attempting to download binary version of extension "' .
+ $this->_packageInfo['providesextension'] . '"');
+ $params = $this->_packageInfo[$releasetype]['binarypackage'];
+ if (!is_array($params) || !isset($params[0])) {
+ $params = array($params);
+ }
+ if (isset($this->_packageInfo['channel'])) {
+ foreach ($params as $i => $param) {
+ $params[$i] = array('channel' => $this->_packageInfo['channel'],
+ 'package' => $param, 'version' => $this->getVersion());
+ }
+ }
+ $dl = &$this->getPEARDownloader($installer->ui, $installer->getOptions(),
+ $installer->config);
+ $verbose = $dl->config->get('verbose');
+ $dl->config->set('verbose', -1);
+ foreach ($params as $param) {
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $ret = $dl->download(array($param));
+ PEAR::popErrorHandling();
+ if (is_array($ret) && count($ret)) {
+ break;
+ }
+ }
+ $dl->config->set('verbose', $verbose);
+ if (is_array($ret)) {
+ if (count($ret) == 1) {
+ $pf = $ret[0]->getPackageFile();
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $err = $installer->install($ret[0]);
+ PEAR::popErrorHandling();
+ if (is_array($err)) {
+ $this->_packageInfo['#binarypackage'] = $ret[0]->getPackage();
+ // "install" self, so all dependencies will work transparently
+ $this->_registry->addPackage2($this);
+ $installer->log(0, 'Download and install of binary extension "' .
+ $this->_registry->parsedPackageNameToString(
+ array('channel' => $pf->getChannel(),
+ 'package' => $pf->getPackage()), true) . '" successful');
+ $a = array($ret[0], $err);
+ return $a;
+ }
+ $installer->log(0, 'Download and install of binary extension "' .
+ $this->_registry->parsedPackageNameToString(
+ array('channel' => $pf->getChannel(),
+ 'package' => $pf->getPackage()), true) . '" failed');
+ }
+ }
+ }
+ }
+ $a = false;
+ return $a;
+ }
+
+ /**
+ * @return string|false Extension name
+ */
+ function getProvidesExtension()
+ {
+ if (in_array($this->getPackageType(),
+ array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) {
+ if (isset($this->_packageInfo['providesextension'])) {
+ return $this->_packageInfo['providesextension'];
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param string Extension name
+ * @return bool
+ */
+ function isExtension($extension)
+ {
+ if (in_array($this->getPackageType(),
+ array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) {
+ return $this->_packageInfo['providesextension'] == $extension;
+ }
+ return false;
+ }
+
+ /**
+ * Tests whether every part of the package.xml 1.0 is represented in
+ * this package.xml 2.0
+ * @param PEAR_PackageFile_v1
+ * @return bool
+ */
+ function isEquivalent($pf1)
+ {
+ if (!$pf1) {
+ return true;
+ }
+ if ($this->getPackageType() == 'bundle') {
+ return false;
+ }
+ $this->_stack->getErrors(true);
+ if (!$pf1->validate(PEAR_VALIDATE_NORMAL)) {
+ return false;
+ }
+ $pass = true;
+ if ($pf1->getPackage() != $this->getPackage()) {
+ $this->_differentPackage($pf1->getPackage());
+ $pass = false;
+ }
+ if ($pf1->getVersion() != $this->getVersion()) {
+ $this->_differentVersion($pf1->getVersion());
+ $pass = false;
+ }
+ if (trim($pf1->getSummary()) != $this->getSummary()) {
+ $this->_differentSummary($pf1->getSummary());
+ $pass = false;
+ }
+ if (preg_replace('/\s+/', '', $pf1->getDescription()) !=
+ preg_replace('/\s+/', '', $this->getDescription())) {
+ $this->_differentDescription($pf1->getDescription());
+ $pass = false;
+ }
+ if ($pf1->getState() != $this->getState()) {
+ $this->_differentState($pf1->getState());
+ $pass = false;
+ }
+ if (!strstr(preg_replace('/\s+/', '', $this->getNotes()),
+ preg_replace('/\s+/', '', $pf1->getNotes()))) {
+ $this->_differentNotes($pf1->getNotes());
+ $pass = false;
+ }
+ $mymaintainers = $this->getMaintainers();
+ $yourmaintainers = $pf1->getMaintainers();
+ for ($i1 = 0; $i1 < count($yourmaintainers); $i1++) {
+ $reset = false;
+ for ($i2 = 0; $i2 < count($mymaintainers); $i2++) {
+ if ($mymaintainers[$i2]['handle'] == $yourmaintainers[$i1]['handle']) {
+ if ($mymaintainers[$i2]['role'] != $yourmaintainers[$i1]['role']) {
+ $this->_differentRole($mymaintainers[$i2]['handle'],
+ $yourmaintainers[$i1]['role'], $mymaintainers[$i2]['role']);
+ $pass = false;
+ }
+ if ($mymaintainers[$i2]['email'] != $yourmaintainers[$i1]['email']) {
+ $this->_differentEmail($mymaintainers[$i2]['handle'],
+ $yourmaintainers[$i1]['email'], $mymaintainers[$i2]['email']);
+ $pass = false;
+ }
+ if ($mymaintainers[$i2]['name'] != $yourmaintainers[$i1]['name']) {
+ $this->_differentName($mymaintainers[$i2]['handle'],
+ $yourmaintainers[$i1]['name'], $mymaintainers[$i2]['name']);
+ $pass = false;
+ }
+ unset($mymaintainers[$i2]);
+ $mymaintainers = array_values($mymaintainers);
+ unset($yourmaintainers[$i1]);
+ $yourmaintainers = array_values($yourmaintainers);
+ $reset = true;
+ break;
+ }
+ }
+ if ($reset) {
+ $i1 = -1;
+ }
+ }
+ $this->_unmatchedMaintainers($mymaintainers, $yourmaintainers);
+ $filelist = $this->getFilelist();
+ foreach ($pf1->getFilelist() as $file => $atts) {
+ if (!isset($filelist[$file])) {
+ $this->_missingFile($file);
+ $pass = false;
+ }
+ }
+ return $pass;
+ }
+
+ function _differentPackage($package)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('package' => $package,
+ 'self' => $this->getPackage()),
+ 'package.xml 1.0 package "%package%" does not match "%self%"');
+ }
+
+ function _differentVersion($version)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('version' => $version,
+ 'self' => $this->getVersion()),
+ 'package.xml 1.0 version "%version%" does not match "%self%"');
+ }
+
+ function _differentState($state)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('state' => $state,
+ 'self' => $this->getState()),
+ 'package.xml 1.0 state "%state%" does not match "%self%"');
+ }
+
+ function _differentRole($handle, $role, $selfrole)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle,
+ 'role' => $role, 'self' => $selfrole),
+ 'package.xml 1.0 maintainer "%handle%" role "%role%" does not match "%self%"');
+ }
+
+ function _differentEmail($handle, $email, $selfemail)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle,
+ 'email' => $email, 'self' => $selfemail),
+ 'package.xml 1.0 maintainer "%handle%" email "%email%" does not match "%self%"');
+ }
+
+ function _differentName($handle, $name, $selfname)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('handle' => $handle,
+ 'name' => $name, 'self' => $selfname),
+ 'package.xml 1.0 maintainer "%handle%" name "%name%" does not match "%self%"');
+ }
+
+ function _unmatchedMaintainers($my, $yours)
+ {
+ if ($my) {
+ array_walk($my, create_function('&$i, $k', '$i = $i["handle"];'));
+ $this->_stack->push(__FUNCTION__, 'error', array('handles' => $my),
+ 'package.xml 2.0 has unmatched extra maintainers "%handles%"');
+ }
+ if ($yours) {
+ array_walk($yours, create_function('&$i, $k', '$i = $i["handle"];'));
+ $this->_stack->push(__FUNCTION__, 'error', array('handles' => $yours),
+ 'package.xml 1.0 has unmatched extra maintainers "%handles%"');
+ }
+ }
+
+ function _differentNotes($notes)
+ {
+ $truncnotes = strlen($notes) < 25 ? $notes : substr($notes, 0, 24) . '...';
+ $truncmynotes = strlen($this->getNotes()) < 25 ? $this->getNotes() :
+ substr($this->getNotes(), 0, 24) . '...';
+ $this->_stack->push(__FUNCTION__, 'error', array('notes' => $truncnotes,
+ 'self' => $truncmynotes),
+ 'package.xml 1.0 release notes "%notes%" do not match "%self%"');
+ }
+
+ function _differentSummary($summary)
+ {
+ $truncsummary = strlen($summary) < 25 ? $summary : substr($summary, 0, 24) . '...';
+ $truncmysummary = strlen($this->getsummary()) < 25 ? $this->getSummary() :
+ substr($this->getsummary(), 0, 24) . '...';
+ $this->_stack->push(__FUNCTION__, 'error', array('summary' => $truncsummary,
+ 'self' => $truncmysummary),
+ 'package.xml 1.0 summary "%summary%" does not match "%self%"');
+ }
+
+ function _differentDescription($description)
+ {
+ $truncdescription = trim(strlen($description) < 25 ? $description : substr($description, 0, 24) . '...');
+ $truncmydescription = trim(strlen($this->getDescription()) < 25 ? $this->getDescription() :
+ substr($this->getdescription(), 0, 24) . '...');
+ $this->_stack->push(__FUNCTION__, 'error', array('description' => $truncdescription,
+ 'self' => $truncmydescription),
+ 'package.xml 1.0 description "%description%" does not match "%self%"');
+ }
+
+ function _missingFile($file)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
+ 'package.xml 1.0 file "%file%" is not present in <contents>');
+ }
+
+ /**
+ * WARNING - do not use this function unless you know what you're doing
+ */
+ function setRawState($state)
+ {
+ if (!isset($this->_packageInfo['stability'])) {
+ $this->_packageInfo['stability'] = array();
+ }
+ $this->_packageInfo['stability']['release'] = $state;
+ }
+
+ /**
+ * WARNING - do not use this function unless you know what you're doing
+ */
+ function setRawCompatible($compatible)
+ {
+ $this->_packageInfo['compatible'] = $compatible;
+ }
+
+ /**
+ * WARNING - do not use this function unless you know what you're doing
+ */
+ function setRawPackage($package)
+ {
+ $this->_packageInfo['name'] = $package;
+ }
+
+ /**
+ * WARNING - do not use this function unless you know what you're doing
+ */
+ function setRawChannel($channel)
+ {
+ $this->_packageInfo['channel'] = $channel;
+ }
+
+ function setRequestedGroup($group)
+ {
+ $this->_requestedGroup = $group;
+ }
+
+ function getRequestedGroup()
+ {
+ if (isset($this->_requestedGroup)) {
+ return $this->_requestedGroup;
+ }
+ return false;
+ }
+
+ /**
+ * For saving in the registry.
+ *
+ * Set the last version that was installed
+ * @param string
+ */
+ function setLastInstalledVersion($version)
+ {
+ $this->_packageInfo['_lastversion'] = $version;
+ }
+
+ /**
+ * @return string|false
+ */
+ function getLastInstalledVersion()
+ {
+ if (isset($this->_packageInfo['_lastversion'])) {
+ return $this->_packageInfo['_lastversion'];
+ }
+ return false;
+ }
+
+ /**
+ * Determines whether this package.xml has post-install scripts or not
+ * @return array|false
+ */
+ function listPostinstallScripts()
+ {
+ $filelist = $this->getFilelist();
+ $contents = $this->getContents();
+ $contents = $contents['dir']['file'];
+ if (!is_array($contents) || !isset($contents[0])) {
+ $contents = array($contents);
+ }
+ $taskfiles = array();
+ foreach ($contents as $file) {
+ $atts = $file['attribs'];
+ unset($file['attribs']);
+ if (count($file)) {
+ $taskfiles[$atts['name']] = $file;
+ }
+ }
+ $common = new PEAR_Common;
+ $common->debug = $this->_config->get('verbose');
+ $this->_scripts = array();
+ $ret = array();
+ foreach ($taskfiles as $name => $tasks) {
+ if (!isset($filelist[$name])) {
+ // ignored files will not be in the filelist
+ continue;
+ }
+ $atts = $filelist[$name];
+ foreach ($tasks as $tag => $raw) {
+ $task = $this->getTask($tag);
+ $task = &new $task($this->_config, $common, PEAR_TASK_INSTALL);
+ if ($task->isScript()) {
+ $ret[] = $filelist[$name]['installed_as'];
+ }
+ }
+ }
+ if (count($ret)) {
+ return $ret;
+ }
+ return false;
+ }
+
+ /**
+ * Initialize post-install scripts for running
+ *
+ * This method can be used to detect post-install scripts, as the return value
+ * indicates whether any exist
+ * @return bool
+ */
+ function initPostinstallScripts()
+ {
+ $filelist = $this->getFilelist();
+ $contents = $this->getContents();
+ $contents = $contents['dir']['file'];
+ if (!is_array($contents) || !isset($contents[0])) {
+ $contents = array($contents);
+ }
+ $taskfiles = array();
+ foreach ($contents as $file) {
+ $atts = $file['attribs'];
+ unset($file['attribs']);
+ if (count($file)) {
+ $taskfiles[$atts['name']] = $file;
+ }
+ }
+ $common = new PEAR_Common;
+ $common->debug = $this->_config->get('verbose');
+ $this->_scripts = array();
+ foreach ($taskfiles as $name => $tasks) {
+ if (!isset($filelist[$name])) {
+ // file was not installed due to installconditions
+ continue;
+ }
+ $atts = $filelist[$name];
+ foreach ($tasks as $tag => $raw) {
+ $taskname = $this->getTask($tag);
+ $task = &new $taskname($this->_config, $common, PEAR_TASK_INSTALL);
+ if (!$task->isScript()) {
+ continue; // scripts are only handled after installation
+ }
+ $lastversion = isset($this->_packageInfo['_lastversion']) ?
+ $this->_packageInfo['_lastversion'] : null;
+ $task->init($raw, $atts, $lastversion);
+ $res = $task->startSession($this, $atts['installed_as']);
+ if (!$res) {
+ continue; // skip this file
+ }
+ if (PEAR::isError($res)) {
+ return $res;
+ }
+ $assign = &$task;
+ $this->_scripts[] = &$assign;
+ }
+ }
+ if (count($this->_scripts)) {
+ return true;
+ }
+ return false;
+ }
+
+ function runPostinstallScripts()
+ {
+ if ($this->initPostinstallScripts()) {
+ $ui = &PEAR_Frontend::singleton();
+ if ($ui) {
+ $ui->runPostinstallScripts($this->_scripts, $this);
+ }
+ }
+ }
+
+
+ /**
+ * Convert a recursive set of <dir> and <file> tags into a single <dir> tag with
+ * <file> tags.
+ */
+ function flattenFilelist()
+ {
+ if (isset($this->_packageInfo['bundle'])) {
+ return;
+ }
+ $filelist = array();
+ if (isset($this->_packageInfo['contents']['dir']['dir'])) {
+ $this->_getFlattenedFilelist($filelist, $this->_packageInfo['contents']['dir']);
+ if (!isset($filelist[1])) {
+ $filelist = $filelist[0];
+ }
+ $this->_packageInfo['contents']['dir']['file'] = $filelist;
+ unset($this->_packageInfo['contents']['dir']['dir']);
+ } else {
+ // else already flattened but check for baseinstalldir propagation
+ if (isset($this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'])) {
+ if (isset($this->_packageInfo['contents']['dir']['file'][0])) {
+ foreach ($this->_packageInfo['contents']['dir']['file'] as $i => $file) {
+ if (isset($file['attribs']['baseinstalldir'])) {
+ continue;
+ }
+ $this->_packageInfo['contents']['dir']['file'][$i]['attribs']['baseinstalldir']
+ = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'];
+ }
+ } else {
+ if (!isset($this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir'])) {
+ $this->_packageInfo['contents']['dir']['file']['attribs']['baseinstalldir']
+ = $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'];
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @param array the final flattened file list
+ * @param array the current directory being processed
+ * @param string|false any recursively inherited baeinstalldir attribute
+ * @param string private recursion variable
+ * @return array
+ * @access protected
+ */
+ function _getFlattenedFilelist(&$files, $dir, $baseinstall = false, $path = '')
+ {
+ if (isset($dir['attribs']) && isset($dir['attribs']['baseinstalldir'])) {
+ $baseinstall = $dir['attribs']['baseinstalldir'];
+ }
+ if (isset($dir['dir'])) {
+ if (!isset($dir['dir'][0])) {
+ $dir['dir'] = array($dir['dir']);
+ }
+ foreach ($dir['dir'] as $subdir) {
+ if (!isset($subdir['attribs']) || !isset($subdir['attribs']['name'])) {
+ $name = '*unknown*';
+ } else {
+ $name = $subdir['attribs']['name'];
+ }
+ $newpath = empty($path) ? $name :
+ $path . '/' . $name;
+ $this->_getFlattenedFilelist($files, $subdir,
+ $baseinstall, $newpath);
+ }
+ }
+ if (isset($dir['file'])) {
+ if (!isset($dir['file'][0])) {
+ $dir['file'] = array($dir['file']);
+ }
+ foreach ($dir['file'] as $file) {
+ $attrs = $file['attribs'];
+ $name = $attrs['name'];
+ if ($baseinstall && !isset($attrs['baseinstalldir'])) {
+ $attrs['baseinstalldir'] = $baseinstall;
+ }
+ $attrs['name'] = empty($path) ? $name : $path . '/' . $name;
+ $attrs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'),
+ $attrs['name']);
+ $file['attribs'] = $attrs;
+ $files[] = $file;
+ }
+ }
+ }
+
+ function setConfig(&$config)
+ {
+ $this->_config = &$config;
+ $this->_registry = &$config->getRegistry();
+ }
+
+ function setLogger(&$logger)
+ {
+ if (!is_object($logger) || !method_exists($logger, 'log')) {
+ return PEAR::raiseError('Logger must be compatible with PEAR_Common::log');
+ }
+ $this->_logger = &$logger;
+ }
+
+ /**
+ * WARNING - do not use this function directly unless you know what you're doing
+ */
+ function setDeps($deps)
+ {
+ $this->_packageInfo['dependencies'] = $deps;
+ }
+
+ /**
+ * WARNING - do not use this function directly unless you know what you're doing
+ */
+ function setCompatible($compat)
+ {
+ $this->_packageInfo['compatible'] = $compat;
+ }
+
+ function setPackagefile($file, $archive = false)
+ {
+ $this->_packageFile = $file;
+ $this->_archiveFile = $archive ? $archive : $file;
+ }
+
+ /**
+ * Wrapper to {@link PEAR_ErrorStack::getErrors()}
+ * @param boolean determines whether to purge the error stack after retrieving
+ * @return array
+ */
+ function getValidationWarnings($purge = true)
+ {
+ return $this->_stack->getErrors($purge);
+ }
+
+ function getPackageFile()
+ {
+ return $this->_packageFile;
+ }
+
+ function getArchiveFile()
+ {
+ return $this->_archiveFile;
+ }
+
+
+ /**
+ * Directly set the array that defines this packagefile
+ *
+ * WARNING: no validation. This should only be performed by internal methods
+ * inside PEAR or by inputting an array saved from an existing PEAR_PackageFile_v2
+ * @param array
+ */
+ function fromArray($pinfo)
+ {
+ unset($pinfo['old']);
+ unset($pinfo['xsdversion']);
+ // If the changelog isn't an array then it was passed in as an empty tag
+ if (isset($pinfo['changelog']) && !is_array($pinfo['changelog'])) {
+ unset($pinfo['changelog']);
+ }
+ $this->_incomplete = false;
+ $this->_packageInfo = $pinfo;
+ }
+
+ function isIncomplete()
+ {
+ return $this->_incomplete;
+ }
+
+ /**
+ * @return array
+ */
+ function toArray($forreg = false)
+ {
+ if (!$this->validate(PEAR_VALIDATE_NORMAL)) {
+ return false;
+ }
+ return $this->getArray($forreg);
+ }
+
+ function getArray($forReg = false)
+ {
+ if ($forReg) {
+ $arr = $this->_packageInfo;
+ $arr['old'] = array();
+ $arr['old']['version'] = $this->getVersion();
+ $arr['old']['release_date'] = $this->getDate();
+ $arr['old']['release_state'] = $this->getState();
+ $arr['old']['release_license'] = $this->getLicense();
+ $arr['old']['release_notes'] = $this->getNotes();
+ $arr['old']['release_deps'] = $this->getDeps();
+ $arr['old']['maintainers'] = $this->getMaintainers();
+ $arr['xsdversion'] = '2.0';
+ return $arr;
+ } else {
+ $info = $this->_packageInfo;
+ unset($info['dirtree']);
+ if (isset($info['_lastversion'])) {
+ unset($info['_lastversion']);
+ }
+ if (isset($info['#binarypackage'])) {
+ unset($info['#binarypackage']);
+ }
+ return $info;
+ }
+ }
+
+ function packageInfo($field)
+ {
+ $arr = $this->getArray(true);
+ if ($field == 'state') {
+ return $arr['stability']['release'];
+ }
+ if ($field == 'api-version') {
+ return $arr['version']['api'];
+ }
+ if ($field == 'api-state') {
+ return $arr['stability']['api'];
+ }
+ if (isset($arr['old'][$field])) {
+ if (!is_string($arr['old'][$field])) {
+ return null;
+ }
+ return $arr['old'][$field];
+ }
+ if (isset($arr[$field])) {
+ if (!is_string($arr[$field])) {
+ return null;
+ }
+ return $arr[$field];
+ }
+ return null;
+ }
+
+ function getName()
+ {
+ return $this->getPackage();
+ }
+
+ function getPackage()
+ {
+ if (isset($this->_packageInfo['name'])) {
+ return $this->_packageInfo['name'];
+ }
+ return false;
+ }
+
+ function getChannel()
+ {
+ if (isset($this->_packageInfo['uri'])) {
+ return '__uri';
+ }
+ if (isset($this->_packageInfo['channel'])) {
+ return strtolower($this->_packageInfo['channel']);
+ }
+ return false;
+ }
+
+ function getUri()
+ {
+ if (isset($this->_packageInfo['uri'])) {
+ return $this->_packageInfo['uri'];
+ }
+ return false;
+ }
+
+ function getExtends()
+ {
+ if (isset($this->_packageInfo['extends'])) {
+ return $this->_packageInfo['extends'];
+ }
+ return false;
+ }
+
+ function getSummary()
+ {
+ if (isset($this->_packageInfo['summary'])) {
+ return $this->_packageInfo['summary'];
+ }
+ return false;
+ }
+
+ function getDescription()
+ {
+ if (isset($this->_packageInfo['description'])) {
+ return $this->_packageInfo['description'];
+ }
+ return false;
+ }
+
+ function getMaintainers($raw = false)
+ {
+ if (!isset($this->_packageInfo['lead'])) {
+ return false;
+ }
+ if ($raw) {
+ $ret = array('lead' => $this->_packageInfo['lead']);
+ (isset($this->_packageInfo['developer'])) ?
+ $ret['developer'] = $this->_packageInfo['developer'] :null;
+ (isset($this->_packageInfo['contributor'])) ?
+ $ret['contributor'] = $this->_packageInfo['contributor'] :null;
+ (isset($this->_packageInfo['helper'])) ?
+ $ret['helper'] = $this->_packageInfo['helper'] :null;
+ return $ret;
+ } else {
+ $ret = array();
+ $leads = isset($this->_packageInfo['lead'][0]) ? $this->_packageInfo['lead'] :
+ array($this->_packageInfo['lead']);
+ foreach ($leads as $lead) {
+ $s = $lead;
+ $s['handle'] = $s['user'];
+ unset($s['user']);
+ $s['role'] = 'lead';
+ $ret[] = $s;
+ }
+ if (isset($this->_packageInfo['developer'])) {
+ $leads = isset($this->_packageInfo['developer'][0]) ?
+ $this->_packageInfo['developer'] :
+ array($this->_packageInfo['developer']);
+ foreach ($leads as $maintainer) {
+ $s = $maintainer;
+ $s['handle'] = $s['user'];
+ unset($s['user']);
+ $s['role'] = 'developer';
+ $ret[] = $s;
+ }
+ }
+ if (isset($this->_packageInfo['contributor'])) {
+ $leads = isset($this->_packageInfo['contributor'][0]) ?
+ $this->_packageInfo['contributor'] :
+ array($this->_packageInfo['contributor']);
+ foreach ($leads as $maintainer) {
+ $s = $maintainer;
+ $s['handle'] = $s['user'];
+ unset($s['user']);
+ $s['role'] = 'contributor';
+ $ret[] = $s;
+ }
+ }
+ if (isset($this->_packageInfo['helper'])) {
+ $leads = isset($this->_packageInfo['helper'][0]) ?
+ $this->_packageInfo['helper'] :
+ array($this->_packageInfo['helper']);
+ foreach ($leads as $maintainer) {
+ $s = $maintainer;
+ $s['handle'] = $s['user'];
+ unset($s['user']);
+ $s['role'] = 'helper';
+ $ret[] = $s;
+ }
+ }
+ return $ret;
+ }
+ return false;
+ }
+
+ function getLeads()
+ {
+ if (isset($this->_packageInfo['lead'])) {
+ return $this->_packageInfo['lead'];
+ }
+ return false;
+ }
+
+ function getDevelopers()
+ {
+ if (isset($this->_packageInfo['developer'])) {
+ return $this->_packageInfo['developer'];
+ }
+ return false;
+ }
+
+ function getContributors()
+ {
+ if (isset($this->_packageInfo['contributor'])) {
+ return $this->_packageInfo['contributor'];
+ }
+ return false;
+ }
+
+ function getHelpers()
+ {
+ if (isset($this->_packageInfo['helper'])) {
+ return $this->_packageInfo['helper'];
+ }
+ return false;
+ }
+
+ function setDate($date)
+ {
+ if (!isset($this->_packageInfo['date'])) {
+ // ensure that the extends tag is set up in the right location
+ $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+ array('time', 'version',
+ 'stability', 'license', 'notes', 'contents', 'compatible',
+ 'dependencies', 'providesextension', 'srcpackage', 'srcuri',
+ 'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease',
+ 'zendextbinrelease', 'bundle', 'changelog'), array(), 'date');
+ }
+ $this->_packageInfo['date'] = $date;
+ $this->_isValid = 0;
+ }
+
+ function setTime($time)
+ {
+ $this->_isValid = 0;
+ if (!isset($this->_packageInfo['time'])) {
+ // ensure that the time tag is set up in the right location
+ $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+ array('version',
+ 'stability', 'license', 'notes', 'contents', 'compatible',
+ 'dependencies', 'providesextension', 'srcpackage', 'srcuri',
+ 'phprelease', 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease',
+ 'zendextbinrelease', 'bundle', 'changelog'), $time, 'time');
+ }
+ $this->_packageInfo['time'] = $time;
+ }
+
+ function getDate()
+ {
+ if (isset($this->_packageInfo['date'])) {
+ return $this->_packageInfo['date'];
+ }
+ return false;
+ }
+
+ function getTime()
+ {
+ if (isset($this->_packageInfo['time'])) {
+ return $this->_packageInfo['time'];
+ }
+ return false;
+ }
+
+ /**
+ * @param package|api version category to return
+ */
+ function getVersion($key = 'release')
+ {
+ if (isset($this->_packageInfo['version'][$key])) {
+ return $this->_packageInfo['version'][$key];
+ }
+ return false;
+ }
+
+ function getStability()
+ {
+ if (isset($this->_packageInfo['stability'])) {
+ return $this->_packageInfo['stability'];
+ }
+ return false;
+ }
+
+ function getState($key = 'release')
+ {
+ if (isset($this->_packageInfo['stability'][$key])) {
+ return $this->_packageInfo['stability'][$key];
+ }
+ return false;
+ }
+
+ function getLicense($raw = false)
+ {
+ if (isset($this->_packageInfo['license'])) {
+ if ($raw) {
+ return $this->_packageInfo['license'];
+ }
+ if (is_array($this->_packageInfo['license'])) {
+ return $this->_packageInfo['license']['_content'];
+ } else {
+ return $this->_packageInfo['license'];
+ }
+ }
+ return false;
+ }
+
+ function getLicenseLocation()
+ {
+ if (!isset($this->_packageInfo['license']) || !is_array($this->_packageInfo['license'])) {
+ return false;
+ }
+ return $this->_packageInfo['license']['attribs'];
+ }
+
+ function getNotes()
+ {
+ if (isset($this->_packageInfo['notes'])) {
+ return $this->_packageInfo['notes'];
+ }
+ return false;
+ }
+
+ /**
+ * Return the <usesrole> tag contents, if any
+ * @return array|false
+ */
+ function getUsesrole()
+ {
+ if (isset($this->_packageInfo['usesrole'])) {
+ return $this->_packageInfo['usesrole'];
+ }
+ return false;
+ }
+
+ /**
+ * Return the <usestask> tag contents, if any
+ * @return array|false
+ */
+ function getUsestask()
+ {
+ if (isset($this->_packageInfo['usestask'])) {
+ return $this->_packageInfo['usestask'];
+ }
+ return false;
+ }
+
+ /**
+ * This should only be used to retrieve filenames and install attributes
+ */
+ function getFilelist($preserve = false)
+ {
+ if (isset($this->_packageInfo['filelist']) && !$preserve) {
+ return $this->_packageInfo['filelist'];
+ }
+ $this->flattenFilelist();
+ if ($contents = $this->getContents()) {
+ $ret = array();
+ if (!isset($contents['dir'])) {
+ return false;
+ }
+ if (!isset($contents['dir']['file'][0])) {
+ $contents['dir']['file'] = array($contents['dir']['file']);
+ }
+ foreach ($contents['dir']['file'] as $file) {
+ $name = $file['attribs']['name'];
+ if (!$preserve) {
+ $file = $file['attribs'];
+ }
+ $ret[$name] = $file;
+ }
+ if (!$preserve) {
+ $this->_packageInfo['filelist'] = $ret;
+ }
+ return $ret;
+ }
+ return false;
+ }
+
+ /**
+ * Return configure options array, if any
+ *
+ * @return array|false
+ */
+ function getConfigureOptions()
+ {
+ if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') {
+ return false;
+ }
+
+ $releases = $this->getReleases();
+ if (isset($releases[0])) {
+ $releases = $releases[0];
+ }
+
+ if (isset($releases['configureoption'])) {
+ if (!isset($releases['configureoption'][0])) {
+ $releases['configureoption'] = array($releases['configureoption']);
+ }
+
+ for ($i = 0; $i < count($releases['configureoption']); $i++) {
+ $releases['configureoption'][$i] = $releases['configureoption'][$i]['attribs'];
+ }
+
+ return $releases['configureoption'];
+ }
+
+ return false;
+ }
+
+ /**
+ * This is only used at install-time, after all serialization
+ * is over.
+ */
+ function resetFilelist()
+ {
+ $this->_packageInfo['filelist'] = array();
+ }
+
+ /**
+ * Retrieve a list of files that should be installed on this computer
+ * @return array
+ */
+ function getInstallationFilelist($forfilecheck = false)
+ {
+ $contents = $this->getFilelist(true);
+ if (isset($contents['dir']['attribs']['baseinstalldir'])) {
+ $base = $contents['dir']['attribs']['baseinstalldir'];
+ }
+ if (isset($this->_packageInfo['bundle'])) {
+ return PEAR::raiseError(
+ 'Exception: bundles should be handled in download code only');
+ }
+ $release = $this->getReleases();
+ if ($release) {
+ if (!isset($release[0])) {
+ if (!isset($release['installconditions']) && !isset($release['filelist'])) {
+ if ($forfilecheck) {
+ return $this->getFilelist();
+ }
+ return $contents;
+ }
+ $release = array($release);
+ }
+ $depchecker = &$this->getPEARDependency2($this->_config, array(),
+ array('channel' => $this->getChannel(), 'package' => $this->getPackage()),
+ PEAR_VALIDATE_INSTALLING);
+ foreach ($release as $instance) {
+ if (isset($instance['installconditions'])) {
+ $installconditions = $instance['installconditions'];
+ if (is_array($installconditions)) {
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ foreach ($installconditions as $type => $conditions) {
+ if (!isset($conditions[0])) {
+ $conditions = array($conditions);
+ }
+ foreach ($conditions as $condition) {
+ $ret = $depchecker->{"validate{$type}Dependency"}($condition);
+ if (PEAR::isError($ret)) {
+ PEAR::popErrorHandling();
+ continue 3; // skip this release
+ }
+ }
+ }
+ PEAR::popErrorHandling();
+ }
+ }
+ // this is the release to use
+ if (isset($instance['filelist'])) {
+ // ignore files
+ if (isset($instance['filelist']['ignore'])) {
+ $ignore = isset($instance['filelist']['ignore'][0]) ?
+ $instance['filelist']['ignore'] :
+ array($instance['filelist']['ignore']);
+ foreach ($ignore as $ig) {
+ unset ($contents[$ig['attribs']['name']]);
+ }
+ }
+ // install files as this name
+ if (isset($instance['filelist']['install'])) {
+ $installas = isset($instance['filelist']['install'][0]) ?
+ $instance['filelist']['install'] :
+ array($instance['filelist']['install']);
+ foreach ($installas as $as) {
+ $contents[$as['attribs']['name']]['attribs']['install-as'] =
+ $as['attribs']['as'];
+ }
+ }
+ }
+ if ($forfilecheck) {
+ foreach ($contents as $file => $attrs) {
+ $contents[$file] = $attrs['attribs'];
+ }
+ }
+ return $contents;
+ }
+ } else { // simple release - no installconditions or install-as
+ if ($forfilecheck) {
+ return $this->getFilelist();
+ }
+ return $contents;
+ }
+ // no releases matched
+ return PEAR::raiseError('No releases in package.xml matched the existing operating ' .
+ 'system, extensions installed, or architecture, cannot install');
+ }
+
+ /**
+ * This is only used at install-time, after all serialization
+ * is over.
+ * @param string file name
+ * @param string installed path
+ */
+ function setInstalledAs($file, $path)
+ {
+ if ($path) {
+ return $this->_packageInfo['filelist'][$file]['installed_as'] = $path;
+ }
+ unset($this->_packageInfo['filelist'][$file]['installed_as']);
+ }
+
+ function getInstalledLocation($file)
+ {
+ if (isset($this->_packageInfo['filelist'][$file]['installed_as'])) {
+ return $this->_packageInfo['filelist'][$file]['installed_as'];
+ }
+ return false;
+ }
+
+ /**
+ * This is only used at install-time, after all serialization
+ * is over.
+ */
+ function installedFile($file, $atts)
+ {
+ if (isset($this->_packageInfo['filelist'][$file])) {
+ $this->_packageInfo['filelist'][$file] =
+ array_merge($this->_packageInfo['filelist'][$file], $atts['attribs']);
+ } else {
+ $this->_packageInfo['filelist'][$file] = $atts['attribs'];
+ }
+ }
+
+ /**
+ * Retrieve the contents tag
+ */
+ function getContents()
+ {
+ if (isset($this->_packageInfo['contents'])) {
+ return $this->_packageInfo['contents'];
+ }
+ return false;
+ }
+
+ /**
+ * @param string full path to file
+ * @param string attribute name
+ * @param string attribute value
+ * @param int risky but fast - use this to choose a file based on its position in the list
+ * of files. Index is zero-based like PHP arrays.
+ * @return bool success of operation
+ */
+ function setFileAttribute($filename, $attr, $value, $index = false)
+ {
+ $this->_isValid = 0;
+ if (in_array($attr, array('role', 'name', 'baseinstalldir'))) {
+ $this->_filesValid = false;
+ }
+ if ($index !== false &&
+ isset($this->_packageInfo['contents']['dir']['file'][$index]['attribs'])) {
+ $this->_packageInfo['contents']['dir']['file'][$index]['attribs'][$attr] = $value;
+ return true;
+ }
+ if (!isset($this->_packageInfo['contents']['dir']['file'])) {
+ return false;
+ }
+ $files = $this->_packageInfo['contents']['dir']['file'];
+ if (!isset($files[0])) {
+ $files = array($files);
+ $ind = false;
+ } else {
+ $ind = true;
+ }
+ foreach ($files as $i => $file) {
+ if (isset($file['attribs'])) {
+ if ($file['attribs']['name'] == $filename) {
+ if ($ind) {
+ $this->_packageInfo['contents']['dir']['file'][$i]['attribs'][$attr] = $value;
+ } else {
+ $this->_packageInfo['contents']['dir']['file']['attribs'][$attr] = $value;
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ function setDirtree($path)
+ {
+ if (!isset($this->_packageInfo['dirtree'])) {
+ $this->_packageInfo['dirtree'] = array();
+ }
+ $this->_packageInfo['dirtree'][$path] = true;
+ }
+
+ function getDirtree()
+ {
+ if (isset($this->_packageInfo['dirtree']) && count($this->_packageInfo['dirtree'])) {
+ return $this->_packageInfo['dirtree'];
+ }
+ return false;
+ }
+
+ function resetDirtree()
+ {
+ unset($this->_packageInfo['dirtree']);
+ }
+
+ /**
+ * Determines whether this package claims it is compatible with the version of
+ * the package that has a recommended version dependency
+ * @param PEAR_PackageFile_v2|PEAR_PackageFile_v1|PEAR_Downloader_Package
+ * @return boolean
+ */
+ function isCompatible($pf)
+ {
+ if (!isset($this->_packageInfo['compatible'])) {
+ return false;
+ }
+ if (!isset($this->_packageInfo['channel'])) {
+ return false;
+ }
+ $me = $pf->getVersion();
+ $compatible = $this->_packageInfo['compatible'];
+ if (!isset($compatible[0])) {
+ $compatible = array($compatible);
+ }
+ $found = false;
+ foreach ($compatible as $info) {
+ if (strtolower($info['name']) == strtolower($pf->getPackage())) {
+ if (strtolower($info['channel']) == strtolower($pf->getChannel())) {
+ $found = true;
+ break;
+ }
+ }
+ }
+ if (!$found) {
+ return false;
+ }
+ if (isset($info['exclude'])) {
+ if (!isset($info['exclude'][0])) {
+ $info['exclude'] = array($info['exclude']);
+ }
+ foreach ($info['exclude'] as $exclude) {
+ if (version_compare($me, $exclude, '==')) {
+ return false;
+ }
+ }
+ }
+ if (version_compare($me, $info['min'], '>=') && version_compare($me, $info['max'], '<=')) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return array|false
+ */
+ function getCompatible()
+ {
+ if (isset($this->_packageInfo['compatible'])) {
+ return $this->_packageInfo['compatible'];
+ }
+ return false;
+ }
+
+ function getDependencies()
+ {
+ if (isset($this->_packageInfo['dependencies'])) {
+ return $this->_packageInfo['dependencies'];
+ }
+ return false;
+ }
+
+ function isSubpackageOf($p)
+ {
+ return $p->isSubpackage($this);
+ }
+
+ /**
+ * Determines whether the passed in package is a subpackage of this package.
+ *
+ * No version checking is done, only name verification.
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * @return bool
+ */
+ function isSubpackage($p)
+ {
+ $sub = array();
+ if (isset($this->_packageInfo['dependencies']['required']['subpackage'])) {
+ $sub = $this->_packageInfo['dependencies']['required']['subpackage'];
+ if (!isset($sub[0])) {
+ $sub = array($sub);
+ }
+ }
+ if (isset($this->_packageInfo['dependencies']['optional']['subpackage'])) {
+ $sub1 = $this->_packageInfo['dependencies']['optional']['subpackage'];
+ if (!isset($sub1[0])) {
+ $sub1 = array($sub1);
+ }
+ $sub = array_merge($sub, $sub1);
+ }
+ if (isset($this->_packageInfo['dependencies']['group'])) {
+ $group = $this->_packageInfo['dependencies']['group'];
+ if (!isset($group[0])) {
+ $group = array($group);
+ }
+ foreach ($group as $deps) {
+ if (isset($deps['subpackage'])) {
+ $sub2 = $deps['subpackage'];
+ if (!isset($sub2[0])) {
+ $sub2 = array($sub2);
+ }
+ $sub = array_merge($sub, $sub2);
+ }
+ }
+ }
+ foreach ($sub as $dep) {
+ if (strtolower($dep['name']) == strtolower($p->getPackage())) {
+ if (isset($dep['channel'])) {
+ if (strtolower($dep['channel']) == strtolower($p->getChannel())) {
+ return true;
+ }
+ } else {
+ if ($dep['uri'] == $p->getURI()) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ function dependsOn($package, $channel)
+ {
+ if (!($deps = $this->getDependencies())) {
+ return false;
+ }
+ foreach (array('package', 'subpackage') as $type) {
+ foreach (array('required', 'optional') as $needed) {
+ if (isset($deps[$needed][$type])) {
+ if (!isset($deps[$needed][$type][0])) {
+ $deps[$needed][$type] = array($deps[$needed][$type]);
+ }
+ foreach ($deps[$needed][$type] as $dep) {
+ $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri';
+ if (strtolower($dep['name']) == strtolower($package) &&
+ $depchannel == $channel) {
+ return true;
+ }
+ }
+ }
+ }
+ if (isset($deps['group'])) {
+ if (!isset($deps['group'][0])) {
+ $dep['group'] = array($deps['group']);
+ }
+ foreach ($deps['group'] as $group) {
+ if (isset($group[$type])) {
+ if (!is_array($group[$type])) {
+ $group[$type] = array($group[$type]);
+ }
+ foreach ($group[$type] as $dep) {
+ $depchannel = isset($dep['channel']) ? $dep['channel'] : '__uri';
+ if (strtolower($dep['name']) == strtolower($package) &&
+ $depchannel == $channel) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get the contents of a dependency group
+ * @param string
+ * @return array|false
+ */
+ function getDependencyGroup($name)
+ {
+ $name = strtolower($name);
+ if (!isset($this->_packageInfo['dependencies']['group'])) {
+ return false;
+ }
+ $groups = $this->_packageInfo['dependencies']['group'];
+ if (!isset($groups[0])) {
+ $groups = array($groups);
+ }
+ foreach ($groups as $group) {
+ if (strtolower($group['attribs']['name']) == $name) {
+ return $group;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve a partial package.xml 1.0 representation of dependencies
+ *
+ * a very limited representation of dependencies is returned by this method.
+ * The <exclude> tag for excluding certain versions of a dependency is
+ * completely ignored. In addition, dependency groups are ignored, with the
+ * assumption that all dependencies in dependency groups are also listed in
+ * the optional group that work with all dependency groups
+ * @param boolean return package.xml 2.0 <dependencies> tag
+ * @return array|false
+ */
+ function getDeps($raw = false, $nopearinstaller = false)
+ {
+ if (isset($this->_packageInfo['dependencies'])) {
+ if ($raw) {
+ return $this->_packageInfo['dependencies'];
+ }
+ $ret = array();
+ $map = array(
+ 'php' => 'php',
+ 'package' => 'pkg',
+ 'subpackage' => 'pkg',
+ 'extension' => 'ext',
+ 'os' => 'os',
+ 'pearinstaller' => 'pkg',
+ );
+ foreach (array('required', 'optional') as $type) {
+ $optional = ($type == 'optional') ? 'yes' : 'no';
+ if (!isset($this->_packageInfo['dependencies'][$type])
+ || empty($this->_packageInfo['dependencies'][$type])) {
+ continue;
+ }
+ foreach ($this->_packageInfo['dependencies'][$type] as $dtype => $deps) {
+ if ($dtype == 'pearinstaller' && $nopearinstaller) {
+ continue;
+ }
+ if (!isset($deps[0])) {
+ $deps = array($deps);
+ }
+ foreach ($deps as $dep) {
+ if (!isset($map[$dtype])) {
+ // no support for arch type
+ continue;
+ }
+ if ($dtype == 'pearinstaller') {
+ $dep['name'] = 'PEAR';
+ $dep['channel'] = 'pear.php.net';
+ }
+ $s = array('type' => $map[$dtype]);
+ if (isset($dep['channel'])) {
+ $s['channel'] = $dep['channel'];
+ }
+ if (isset($dep['uri'])) {
+ $s['uri'] = $dep['uri'];
+ }
+ if (isset($dep['name'])) {
+ $s['name'] = $dep['name'];
+ }
+ if (isset($dep['conflicts'])) {
+ $s['rel'] = 'not';
+ } else {
+ if (!isset($dep['min']) &&
+ !isset($dep['max'])) {
+ $s['rel'] = 'has';
+ $s['optional'] = $optional;
+ } elseif (isset($dep['min']) &&
+ isset($dep['max'])) {
+ $s['rel'] = 'ge';
+ $s1 = $s;
+ $s1['rel'] = 'le';
+ $s['version'] = $dep['min'];
+ $s1['version'] = $dep['max'];
+ if (isset($dep['channel'])) {
+ $s1['channel'] = $dep['channel'];
+ }
+ if ($dtype != 'php') {
+ $s['name'] = $dep['name'];
+ $s1['name'] = $dep['name'];
+ }
+ $s['optional'] = $optional;
+ $s1['optional'] = $optional;
+ $ret[] = $s1;
+ } elseif (isset($dep['min'])) {
+ if (isset($dep['exclude']) &&
+ $dep['exclude'] == $dep['min']) {
+ $s['rel'] = 'gt';
+ } else {
+ $s['rel'] = 'ge';
+ }
+ $s['version'] = $dep['min'];
+ $s['optional'] = $optional;
+ if ($dtype != 'php') {
+ $s['name'] = $dep['name'];
+ }
+ } elseif (isset($dep['max'])) {
+ if (isset($dep['exclude']) &&
+ $dep['exclude'] == $dep['max']) {
+ $s['rel'] = 'lt';
+ } else {
+ $s['rel'] = 'le';
+ }
+ $s['version'] = $dep['max'];
+ $s['optional'] = $optional;
+ if ($dtype != 'php') {
+ $s['name'] = $dep['name'];
+ }
+ }
+ }
+ $ret[] = $s;
+ }
+ }
+ }
+ if (count($ret)) {
+ return $ret;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return php|extsrc|extbin|zendextsrc|zendextbin|bundle|false
+ */
+ function getPackageType()
+ {
+ if (isset($this->_packageInfo['phprelease'])) {
+ return 'php';
+ }
+ if (isset($this->_packageInfo['extsrcrelease'])) {
+ return 'extsrc';
+ }
+ if (isset($this->_packageInfo['extbinrelease'])) {
+ return 'extbin';
+ }
+ if (isset($this->_packageInfo['zendextsrcrelease'])) {
+ return 'zendextsrc';
+ }
+ if (isset($this->_packageInfo['zendextbinrelease'])) {
+ return 'zendextbin';
+ }
+ if (isset($this->_packageInfo['bundle'])) {
+ return 'bundle';
+ }
+ return false;
+ }
+
+ /**
+ * @return array|false
+ */
+ function getReleases()
+ {
+ $type = $this->getPackageType();
+ if ($type != 'bundle') {
+ $type .= 'release';
+ }
+ if ($this->getPackageType() && isset($this->_packageInfo[$type])) {
+ return $this->_packageInfo[$type];
+ }
+ return false;
+ }
+
+ /**
+ * @return array
+ */
+ function getChangelog()
+ {
+ if (isset($this->_packageInfo['changelog'])) {
+ return $this->_packageInfo['changelog'];
+ }
+ return false;
+ }
+
+ function hasDeps()
+ {
+ return isset($this->_packageInfo['dependencies']);
+ }
+
+ function getPackagexmlVersion()
+ {
+ if (isset($this->_packageInfo['zendextsrcrelease'])) {
+ return '2.1';
+ }
+ if (isset($this->_packageInfo['zendextbinrelease'])) {
+ return '2.1';
+ }
+ return '2.0';
+ }
+
+ /**
+ * @return array|false
+ */
+ function getSourcePackage()
+ {
+ if (isset($this->_packageInfo['extbinrelease']) ||
+ isset($this->_packageInfo['zendextbinrelease'])) {
+ return array('channel' => $this->_packageInfo['srcchannel'],
+ 'package' => $this->_packageInfo['srcpackage']);
+ }
+ return false;
+ }
+
+ function getBundledPackages()
+ {
+ if (isset($this->_packageInfo['bundle'])) {
+ return $this->_packageInfo['contents']['bundledpackage'];
+ }
+ return false;
+ }
+
+ function getLastModified()
+ {
+ if (isset($this->_packageInfo['_lastmodified'])) {
+ return $this->_packageInfo['_lastmodified'];
+ }
+ return false;
+ }
+
+ /**
+ * Get the contents of a file listed within the package.xml
+ * @param string
+ * @return string
+ */
+ function getFileContents($file)
+ {
+ if ($this->_archiveFile == $this->_packageFile) { // unpacked
+ $dir = dirname($this->_packageFile);
+ $file = $dir . DIRECTORY_SEPARATOR . $file;
+ $file = str_replace(array('/', '\\'),
+ array(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), $file);
+ if (file_exists($file) && is_readable($file)) {
+ return implode('', file($file));
+ }
+ } else { // tgz
+ $tar = &new Archive_Tar($this->_archiveFile);
+ $tar->pushErrorHandling(PEAR_ERROR_RETURN);
+ if ($file != 'package.xml' && $file != 'package2.xml') {
+ $file = $this->getPackage() . '-' . $this->getVersion() . '/' . $file;
+ }
+ $file = $tar->extractInString($file);
+ $tar->popErrorHandling();
+ if (PEAR::isError($file)) {
+ return PEAR::raiseError("Cannot locate file '$file' in archive");
+ }
+ return $file;
+ }
+ }
+
+ function &getRW()
+ {
+ if (!class_exists('PEAR_PackageFile_v2_rw')) {
+ require_once 'PEAR/PackageFile/v2/rw.php';
+ }
+ $a = new PEAR_PackageFile_v2_rw;
+ foreach (get_object_vars($this) as $name => $unused) {
+ if (!isset($this->$name)) {
+ continue;
+ }
+ if ($name == '_config' || $name == '_logger'|| $name == '_registry' ||
+ $name == '_stack') {
+ $a->$name = &$this->$name;
+ } else {
+ $a->$name = $this->$name;
+ }
+ }
+ return $a;
+ }
+
+ function &getDefaultGenerator()
+ {
+ if (!class_exists('PEAR_PackageFile_Generator_v2')) {
+ require_once 'PEAR/PackageFile/Generator/v2.php';
+ }
+ $a = &new PEAR_PackageFile_Generator_v2($this);
+ return $a;
+ }
+
+ function analyzeSourceCode($file, $string = false)
+ {
+ if (!isset($this->_v2Validator) ||
+ !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) {
+ if (!class_exists('PEAR_PackageFile_v2_Validator')) {
+ require_once 'PEAR/PackageFile/v2/Validator.php';
+ }
+ $this->_v2Validator = new PEAR_PackageFile_v2_Validator;
+ }
+ return $this->_v2Validator->analyzeSourceCode($file, $string);
+ }
+
+ function validate($state = PEAR_VALIDATE_NORMAL)
+ {
+ if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) {
+ return false;
+ }
+ if (!isset($this->_v2Validator) ||
+ !is_a($this->_v2Validator, 'PEAR_PackageFile_v2_Validator')) {
+ if (!class_exists('PEAR_PackageFile_v2_Validator')) {
+ require_once 'PEAR/PackageFile/v2/Validator.php';
+ }
+ $this->_v2Validator = new PEAR_PackageFile_v2_Validator;
+ }
+ if (isset($this->_packageInfo['xsdversion'])) {
+ unset($this->_packageInfo['xsdversion']);
+ }
+ return $this->_v2Validator->validate($this, $state);
+ }
+
+ function getTasksNs()
+ {
+ if (!isset($this->_tasksNs)) {
+ if (isset($this->_packageInfo['attribs'])) {
+ foreach ($this->_packageInfo['attribs'] as $name => $value) {
+ if ($value == 'http://pear.php.net/dtd/tasks-1.0') {
+ $this->_tasksNs = str_replace('xmlns:', '', $name);
+ break;
+ }
+ }
+ }
+ }
+ return $this->_tasksNs;
+ }
+
+ /**
+ * Determine whether a task name is a valid task. Custom tasks may be defined
+ * using subdirectories by putting a "-" in the name, as in <tasks:mycustom-task>
+ *
+ * Note that this method will auto-load the task class file and test for the existence
+ * of the name with "-" replaced by "_" as in PEAR/Task/mycustom/task.php makes class
+ * PEAR_Task_mycustom_task
+ * @param string
+ * @return boolean
+ */
+ function getTask($task)
+ {
+ $this->getTasksNs();
+ // transform all '-' to '/' and 'tasks:' to '' so tasks:replace becomes replace
+ $task = str_replace(array($this->_tasksNs . ':', '-'), array('', ' '), $task);
+ $taskfile = str_replace(' ', '/', ucwords($task));
+ $task = str_replace(array(' ', '/'), '_', ucwords($task));
+ if (class_exists("PEAR_Task_$task")) {
+ return "PEAR_Task_$task";
+ }
+ $fp = @fopen("PEAR/Task/$taskfile.php", 'r', true);
+ if ($fp) {
+ fclose($fp);
+ require_once "PEAR/Task/$taskfile.php";
+ return "PEAR_Task_$task";
+ }
+ return false;
+ }
+
+ /**
+ * Key-friendly array_splice
+ * @param tagname to splice a value in before
+ * @param mixed the value to splice in
+ * @param string the new tag name
+ */
+ function _ksplice($array, $key, $value, $newkey)
+ {
+ $offset = array_search($key, array_keys($array));
+ $after = array_slice($array, $offset);
+ $before = array_slice($array, 0, $offset);
+ $before[$newkey] = $value;
+ return array_merge($before, $after);
+ }
+
+ /**
+ * @param array a list of possible keys, in the order they may occur
+ * @param mixed contents of the new package.xml tag
+ * @param string tag name
+ * @access private
+ */
+ function _insertBefore($array, $keys, $contents, $newkey)
+ {
+ foreach ($keys as $key) {
+ if (isset($array[$key])) {
+ return $array = $this->_ksplice($array, $key, $contents, $newkey);
+ }
+ }
+ $array[$newkey] = $contents;
+ return $array;
+ }
+
+ /**
+ * @param subsection of {@link $_packageInfo}
+ * @param array|string tag contents
+ * @param array format:
+ * <pre>
+ * array(
+ * tagname => array(list of tag names that follow this one),
+ * childtagname => array(list of child tag names that follow this one),
+ * )
+ * </pre>
+ *
+ * This allows construction of nested tags
+ * @access private
+ */
+ function _mergeTag($manip, $contents, $order)
+ {
+ if (count($order)) {
+ foreach ($order as $tag => $curorder) {
+ if (!isset($manip[$tag])) {
+ // ensure that the tag is set up
+ $manip = $this->_insertBefore($manip, $curorder, array(), $tag);
+ }
+ if (count($order) > 1) {
+ $manip[$tag] = $this->_mergeTag($manip[$tag], $contents, array_slice($order, 1));
+ return $manip;
+ }
+ }
+ } else {
+ return $manip;
+ }
+ if (is_array($manip[$tag]) && !empty($manip[$tag]) && isset($manip[$tag][0])) {
+ $manip[$tag][] = $contents;
+ } else {
+ if (!count($manip[$tag])) {
+ $manip[$tag] = $contents;
+ } else {
+ $manip[$tag] = array($manip[$tag]);
+ $manip[$tag][] = $contents;
+ }
+ }
+ return $manip;
+ }
+}
+?>
diff --git a/includes/pear/PEAR/PackageFile/v2/Validator.php b/includes/pear/PEAR/PackageFile/v2/Validator.php
new file mode 100644
index 0000000..7ce2fb3
--- /dev/null
+++ b/includes/pear/PEAR/PackageFile/v2/Validator.php
@@ -0,0 +1,2154 @@
+<?php
+/**
+ * PEAR_PackageFile_v2, package.xml version 2.0, read/write version
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a8
+ */
+/**
+ * Private validation class used by PEAR_PackageFile_v2 - do not use directly, its
+ * sole purpose is to split up the PEAR/PackageFile/v2.php file to make it smaller
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a8
+ * @access private
+ */
+class PEAR_PackageFile_v2_Validator
+{
+ /**
+ * @var array
+ */
+ var $_packageInfo;
+ /**
+ * @var PEAR_PackageFile_v2
+ */
+ var $_pf;
+ /**
+ * @var PEAR_ErrorStack
+ */
+ var $_stack;
+ /**
+ * @var int
+ */
+ var $_isValid = 0;
+ /**
+ * @var int
+ */
+ var $_filesValid = 0;
+ /**
+ * @var int
+ */
+ var $_curState = 0;
+ /**
+ * @param PEAR_PackageFile_v2
+ * @param int
+ */
+ function validate(&$pf, $state = PEAR_VALIDATE_NORMAL)
+ {
+ $this->_pf = &$pf;
+ $this->_curState = $state;
+ $this->_packageInfo = $this->_pf->getArray();
+ $this->_isValid = $this->_pf->_isValid;
+ $this->_filesValid = $this->_pf->_filesValid;
+ $this->_stack = &$pf->_stack;
+ $this->_stack->getErrors(true);
+ if (($this->_isValid & $state) == $state) {
+ return true;
+ }
+ if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) {
+ return false;
+ }
+ if (!isset($this->_packageInfo['attribs']['version']) ||
+ ($this->_packageInfo['attribs']['version'] != '2.0' &&
+ $this->_packageInfo['attribs']['version'] != '2.1')
+ ) {
+ $this->_noPackageVersion();
+ }
+ $structure =
+ array(
+ 'name',
+ 'channel|uri',
+ '*extends', // can't be multiple, but this works fine
+ 'summary',
+ 'description',
+ '+lead', // these all need content checks
+ '*developer',
+ '*contributor',
+ '*helper',
+ 'date',
+ '*time',
+ 'version',
+ 'stability',
+ 'license->?uri->?filesource',
+ 'notes',
+ 'contents', //special validation needed
+ '*compatible',
+ 'dependencies', //special validation needed
+ '*usesrole',
+ '*usestask', // reserve these for 1.4.0a1 to implement
+ // this will allow a package.xml to gracefully say it
+ // needs a certain package installed in order to implement a role or task
+ '*providesextension',
+ '*srcpackage|*srcuri',
+ '+phprelease|+extsrcrelease|+extbinrelease|' .
+ '+zendextsrcrelease|+zendextbinrelease|bundle', //special validation needed
+ '*changelog',
+ );
+ $test = $this->_packageInfo;
+ if (isset($test['dependencies']) &&
+ isset($test['dependencies']['required']) &&
+ isset($test['dependencies']['required']['pearinstaller']) &&
+ isset($test['dependencies']['required']['pearinstaller']['min']) &&
+ version_compare('1.9.4',
+ $test['dependencies']['required']['pearinstaller']['min'], '<')
+ ) {
+ $this->_pearVersionTooLow($test['dependencies']['required']['pearinstaller']['min']);
+ return false;
+ }
+ // ignore post-installation array fields
+ if (array_key_exists('filelist', $test)) {
+ unset($test['filelist']);
+ }
+ if (array_key_exists('_lastmodified', $test)) {
+ unset($test['_lastmodified']);
+ }
+ if (array_key_exists('#binarypackage', $test)) {
+ unset($test['#binarypackage']);
+ }
+ if (array_key_exists('old', $test)) {
+ unset($test['old']);
+ }
+ if (array_key_exists('_lastversion', $test)) {
+ unset($test['_lastversion']);
+ }
+ if (!$this->_stupidSchemaValidate($structure, $test, '<package>')) {
+ return false;
+ }
+ if (empty($this->_packageInfo['name'])) {
+ $this->_tagCannotBeEmpty('name');
+ }
+ $test = isset($this->_packageInfo['uri']) ? 'uri' :'channel';
+ if (empty($this->_packageInfo[$test])) {
+ $this->_tagCannotBeEmpty($test);
+ }
+ if (is_array($this->_packageInfo['license']) &&
+ (!isset($this->_packageInfo['license']['_content']) ||
+ empty($this->_packageInfo['license']['_content']))) {
+ $this->_tagCannotBeEmpty('license');
+ } elseif (empty($this->_packageInfo['license'])) {
+ $this->_tagCannotBeEmpty('license');
+ }
+ if (empty($this->_packageInfo['summary'])) {
+ $this->_tagCannotBeEmpty('summary');
+ }
+ if (empty($this->_packageInfo['description'])) {
+ $this->_tagCannotBeEmpty('description');
+ }
+ if (empty($this->_packageInfo['date'])) {
+ $this->_tagCannotBeEmpty('date');
+ }
+ if (empty($this->_packageInfo['notes'])) {
+ $this->_tagCannotBeEmpty('notes');
+ }
+ if (isset($this->_packageInfo['time']) && empty($this->_packageInfo['time'])) {
+ $this->_tagCannotBeEmpty('time');
+ }
+ if (isset($this->_packageInfo['dependencies'])) {
+ $this->_validateDependencies();
+ }
+ if (isset($this->_packageInfo['compatible'])) {
+ $this->_validateCompatible();
+ }
+ if (!isset($this->_packageInfo['bundle'])) {
+ if (empty($this->_packageInfo['contents'])) {
+ $this->_tagCannotBeEmpty('contents');
+ }
+ if (!isset($this->_packageInfo['contents']['dir'])) {
+ $this->_filelistMustContainDir('contents');
+ return false;
+ }
+ if (isset($this->_packageInfo['contents']['file'])) {
+ $this->_filelistCannotContainFile('contents');
+ return false;
+ }
+ }
+ $this->_validateMaintainers();
+ $this->_validateStabilityVersion();
+ $fail = false;
+ if (array_key_exists('usesrole', $this->_packageInfo)) {
+ $roles = $this->_packageInfo['usesrole'];
+ if (!is_array($roles) || !isset($roles[0])) {
+ $roles = array($roles);
+ }
+ foreach ($roles as $role) {
+ if (!isset($role['role'])) {
+ $this->_usesroletaskMustHaveRoleTask('usesrole', 'role');
+ $fail = true;
+ } else {
+ if (!isset($role['channel'])) {
+ if (!isset($role['uri'])) {
+ $this->_usesroletaskMustHaveChannelOrUri($role['role'], 'usesrole');
+ $fail = true;
+ }
+ } elseif (!isset($role['package'])) {
+ $this->_usesroletaskMustHavePackage($role['role'], 'usesrole');
+ $fail = true;
+ }
+ }
+ }
+ }
+ if (array_key_exists('usestask', $this->_packageInfo)) {
+ $roles = $this->_packageInfo['usestask'];
+ if (!is_array($roles) || !isset($roles[0])) {
+ $roles = array($roles);
+ }
+ foreach ($roles as $role) {
+ if (!isset($role['task'])) {
+ $this->_usesroletaskMustHaveRoleTask('usestask', 'task');
+ $fail = true;
+ } else {
+ if (!isset($role['channel'])) {
+ if (!isset($role['uri'])) {
+ $this->_usesroletaskMustHaveChannelOrUri($role['task'], 'usestask');
+ $fail = true;
+ }
+ } elseif (!isset($role['package'])) {
+ $this->_usesroletaskMustHavePackage($role['task'], 'usestask');
+ $fail = true;
+ }
+ }
+ }
+ }
+
+ if ($fail) {
+ return false;
+ }
+
+ $list = $this->_packageInfo['contents'];
+ if (isset($list['dir']) && is_array($list['dir']) && isset($list['dir'][0])) {
+ $this->_multipleToplevelDirNotAllowed();
+ return $this->_isValid = 0;
+ }
+
+ $this->_validateFilelist();
+ $this->_validateRelease();
+ if (!$this->_stack->hasErrors()) {
+ $chan = $this->_pf->_registry->getChannel($this->_pf->getChannel(), true);
+ if (PEAR::isError($chan)) {
+ $this->_unknownChannel($this->_pf->getChannel());
+ } else {
+ $valpack = $chan->getValidationPackage();
+ // for channel validator packages, always use the default PEAR validator.
+ // otherwise, they can't be installed or packaged
+ $validator = $chan->getValidationObject($this->_pf->getPackage());
+ if (!$validator) {
+ $this->_stack->push(__FUNCTION__, 'error',
+ array('channel' => $chan->getName(),
+ 'package' => $this->_pf->getPackage(),
+ 'name' => $valpack['_content'],
+ 'version' => $valpack['attribs']['version']),
+ 'package "%channel%/%package%" cannot be properly validated without ' .
+ 'validation package "%channel%/%name%-%version%"');
+ return $this->_isValid = 0;
+ }
+ $validator->setPackageFile($this->_pf);
+ $validator->validate($state);
+ $failures = $validator->getFailures();
+ foreach ($failures['errors'] as $error) {
+ $this->_stack->push(__FUNCTION__, 'error', $error,
+ 'Channel validator error: field "%field%" - %reason%');
+ }
+ foreach ($failures['warnings'] as $warning) {
+ $this->_stack->push(__FUNCTION__, 'warning', $warning,
+ 'Channel validator warning: field "%field%" - %reason%');
+ }
+ }
+ }
+
+ $this->_pf->_isValid = $this->_isValid = !$this->_stack->hasErrors('error');
+ if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$this->_filesValid) {
+ if ($this->_pf->getPackageType() == 'bundle') {
+ if ($this->_analyzeBundledPackages()) {
+ $this->_filesValid = $this->_pf->_filesValid = true;
+ } else {
+ $this->_pf->_isValid = $this->_isValid = 0;
+ }
+ } else {
+ if (!$this->_analyzePhpFiles()) {
+ $this->_pf->_isValid = $this->_isValid = 0;
+ } else {
+ $this->_filesValid = $this->_pf->_filesValid = true;
+ }
+ }
+ }
+
+ if ($this->_isValid) {
+ return $this->_pf->_isValid = $this->_isValid = $state;
+ }
+
+ return $this->_pf->_isValid = $this->_isValid = 0;
+ }
+
+ function _stupidSchemaValidate($structure, $xml, $root)
+ {
+ if (!is_array($xml)) {
+ $xml = array();
+ }
+ $keys = array_keys($xml);
+ reset($keys);
+ $key = current($keys);
+ while ($key == 'attribs' || $key == '_contents') {
+ $key = next($keys);
+ }
+ $unfoundtags = $optionaltags = array();
+ $ret = true;
+ $mismatch = false;
+ foreach ($structure as $struc) {
+ if ($key) {
+ $tag = $xml[$key];
+ }
+ $test = $this->_processStructure($struc);
+ if (isset($test['choices'])) {
+ $loose = true;
+ foreach ($test['choices'] as $choice) {
+ if ($key == $choice['tag']) {
+ $key = next($keys);
+ while ($key == 'attribs' || $key == '_contents') {
+ $key = next($keys);
+ }
+ $unfoundtags = $optionaltags = array();
+ $mismatch = false;
+ if ($key && $key != $choice['tag'] && isset($choice['multiple'])) {
+ $unfoundtags[] = $choice['tag'];
+ $optionaltags[] = $choice['tag'];
+ if ($key) {
+ $mismatch = true;
+ }
+ }
+ $ret &= $this->_processAttribs($choice, $tag, $root);
+ continue 2;
+ } else {
+ $unfoundtags[] = $choice['tag'];
+ $mismatch = true;
+ }
+ if (!isset($choice['multiple']) || $choice['multiple'] != '*') {
+ $loose = false;
+ } else {
+ $optionaltags[] = $choice['tag'];
+ }
+ }
+ if (!$loose) {
+ $this->_invalidTagOrder($unfoundtags, $key, $root);
+ return false;
+ }
+ } else {
+ if ($key != $test['tag']) {
+ if (isset($test['multiple']) && $test['multiple'] != '*') {
+ $unfoundtags[] = $test['tag'];
+ $this->_invalidTagOrder($unfoundtags, $key, $root);
+ return false;
+ } else {
+ if ($key) {
+ $mismatch = true;
+ }
+ $unfoundtags[] = $test['tag'];
+ $optionaltags[] = $test['tag'];
+ }
+ if (!isset($test['multiple'])) {
+ $this->_invalidTagOrder($unfoundtags, $key, $root);
+ return false;
+ }
+ continue;
+ } else {
+ $unfoundtags = $optionaltags = array();
+ $mismatch = false;
+ }
+ $key = next($keys);
+ while ($key == 'attribs' || $key == '_contents') {
+ $key = next($keys);
+ }
+ if ($key && $key != $test['tag'] && isset($test['multiple'])) {
+ $unfoundtags[] = $test['tag'];
+ $optionaltags[] = $test['tag'];
+ $mismatch = true;
+ }
+ $ret &= $this->_processAttribs($test, $tag, $root);
+ continue;
+ }
+ }
+ if (!$mismatch && count($optionaltags)) {
+ // don't error out on any optional tags
+ $unfoundtags = array_diff($unfoundtags, $optionaltags);
+ }
+ if (count($unfoundtags)) {
+ $this->_invalidTagOrder($unfoundtags, $key, $root);
+ } elseif ($key) {
+ // unknown tags
+ $this->_invalidTagOrder('*no tags allowed here*', $key, $root);
+ while ($key = next($keys)) {
+ $this->_invalidTagOrder('*no tags allowed here*', $key, $root);
+ }
+ }
+ return $ret;
+ }
+
+ function _processAttribs($choice, $tag, $context)
+ {
+ if (isset($choice['attribs'])) {
+ if (!is_array($tag)) {
+ $tag = array($tag);
+ }
+ $tags = $tag;
+ if (!isset($tags[0])) {
+ $tags = array($tags);
+ }
+ $ret = true;
+ foreach ($tags as $i => $tag) {
+ if (!is_array($tag) || !isset($tag['attribs'])) {
+ foreach ($choice['attribs'] as $attrib) {
+ if ($attrib{0} != '?') {
+ $ret &= $this->_tagHasNoAttribs($choice['tag'],
+ $context);
+ continue 2;
+ }
+ }
+ }
+ foreach ($choice['attribs'] as $attrib) {
+ if ($attrib{0} != '?') {
+ if (!isset($tag['attribs'][$attrib])) {
+ $ret &= $this->_tagMissingAttribute($choice['tag'],
+ $attrib, $context);
+ }
+ }
+ }
+ }
+ return $ret;
+ }
+ return true;
+ }
+
+ function _processStructure($key)
+ {
+ $ret = array();
+ if (count($pieces = explode('|', $key)) > 1) {
+ $ret['choices'] = array();
+ foreach ($pieces as $piece) {
+ $ret['choices'][] = $this->_processStructure($piece);
+ }
+ return $ret;
+ }
+ $multi = $key{0};
+ if ($multi == '+' || $multi == '*') {
+ $ret['multiple'] = $key{0};
+ $key = substr($key, 1);
+ }
+ if (count($attrs = explode('->', $key)) > 1) {
+ $ret['tag'] = array_shift($attrs);
+ $ret['attribs'] = $attrs;
+ } else {
+ $ret['tag'] = $key;
+ }
+ return $ret;
+ }
+
+ function _validateStabilityVersion()
+ {
+ $structure = array('release', 'api');
+ $a = $this->_stupidSchemaValidate($structure, $this->_packageInfo['version'], '<version>');
+ $a &= $this->_stupidSchemaValidate($structure, $this->_packageInfo['stability'], '<stability>');
+ if ($a) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $this->_packageInfo['version']['release'])) {
+ $this->_invalidVersion('release', $this->_packageInfo['version']['release']);
+ }
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $this->_packageInfo['version']['api'])) {
+ $this->_invalidVersion('api', $this->_packageInfo['version']['api']);
+ }
+ if (!in_array($this->_packageInfo['stability']['release'],
+ array('snapshot', 'devel', 'alpha', 'beta', 'stable'))) {
+ $this->_invalidState('release', $this->_packageInfo['stability']['release']);
+ }
+ if (!in_array($this->_packageInfo['stability']['api'],
+ array('devel', 'alpha', 'beta', 'stable'))) {
+ $this->_invalidState('api', $this->_packageInfo['stability']['api']);
+ }
+ }
+ }
+
+ function _validateMaintainers()
+ {
+ $structure =
+ array(
+ 'name',
+ 'user',
+ 'email',
+ 'active',
+ );
+ foreach (array('lead', 'developer', 'contributor', 'helper') as $type) {
+ if (!isset($this->_packageInfo[$type])) {
+ continue;
+ }
+ if (isset($this->_packageInfo[$type][0])) {
+ foreach ($this->_packageInfo[$type] as $lead) {
+ $this->_stupidSchemaValidate($structure, $lead, '<' . $type . '>');
+ }
+ } else {
+ $this->_stupidSchemaValidate($structure, $this->_packageInfo[$type],
+ '<' . $type . '>');
+ }
+ }
+ }
+
+ function _validatePhpDep($dep, $installcondition = false)
+ {
+ $structure = array(
+ 'min',
+ '*max',
+ '*exclude',
+ );
+ $type = $installcondition ? '<installcondition><php>' : '<dependencies><required><php>';
+ $this->_stupidSchemaValidate($structure, $dep, $type);
+ if (isset($dep['min'])) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
+ $dep['min'])) {
+ $this->_invalidVersion($type . '<min>', $dep['min']);
+ }
+ }
+ if (isset($dep['max'])) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
+ $dep['max'])) {
+ $this->_invalidVersion($type . '<max>', $dep['max']);
+ }
+ }
+ if (isset($dep['exclude'])) {
+ if (!is_array($dep['exclude'])) {
+ $dep['exclude'] = array($dep['exclude']);
+ }
+ foreach ($dep['exclude'] as $exclude) {
+ if (!preg_match(
+ '/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
+ $exclude)) {
+ $this->_invalidVersion($type . '<exclude>', $exclude);
+ }
+ }
+ }
+ }
+
+ function _validatePearinstallerDep($dep)
+ {
+ $structure = array(
+ 'min',
+ '*max',
+ '*recommended',
+ '*exclude',
+ );
+ $this->_stupidSchemaValidate($structure, $dep, '<dependencies><required><pearinstaller>');
+ if (isset($dep['min'])) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $dep['min'])) {
+ $this->_invalidVersion('<dependencies><required><pearinstaller><min>',
+ $dep['min']);
+ }
+ }
+ if (isset($dep['max'])) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $dep['max'])) {
+ $this->_invalidVersion('<dependencies><required><pearinstaller><max>',
+ $dep['max']);
+ }
+ }
+ if (isset($dep['recommended'])) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $dep['recommended'])) {
+ $this->_invalidVersion('<dependencies><required><pearinstaller><recommended>',
+ $dep['recommended']);
+ }
+ }
+ if (isset($dep['exclude'])) {
+ if (!is_array($dep['exclude'])) {
+ $dep['exclude'] = array($dep['exclude']);
+ }
+ foreach ($dep['exclude'] as $exclude) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $exclude)) {
+ $this->_invalidVersion('<dependencies><required><pearinstaller><exclude>',
+ $exclude);
+ }
+ }
+ }
+ }
+
+ function _validatePackageDep($dep, $group, $type = '<package>')
+ {
+ if (isset($dep['uri'])) {
+ if (isset($dep['conflicts'])) {
+ $structure = array(
+ 'name',
+ 'uri',
+ 'conflicts',
+ '*providesextension',
+ );
+ } else {
+ $structure = array(
+ 'name',
+ 'uri',
+ '*providesextension',
+ );
+ }
+ } else {
+ if (isset($dep['conflicts'])) {
+ $structure = array(
+ 'name',
+ 'channel',
+ '*min',
+ '*max',
+ '*exclude',
+ 'conflicts',
+ '*providesextension',
+ );
+ } else {
+ $structure = array(
+ 'name',
+ 'channel',
+ '*min',
+ '*max',
+ '*recommended',
+ '*exclude',
+ '*nodefault',
+ '*providesextension',
+ );
+ }
+ }
+ if (isset($dep['name'])) {
+ $type .= '<name>' . $dep['name'] . '</name>';
+ }
+ $this->_stupidSchemaValidate($structure, $dep, '<dependencies>' . $group . $type);
+ if (isset($dep['uri']) && (isset($dep['min']) || isset($dep['max']) ||
+ isset($dep['recommended']) || isset($dep['exclude']))) {
+ $this->_uriDepsCannotHaveVersioning('<dependencies>' . $group . $type);
+ }
+ if (isset($dep['channel']) && strtolower($dep['channel']) == '__uri') {
+ $this->_DepchannelCannotBeUri('<dependencies>' . $group . $type);
+ }
+ if (isset($dep['min'])) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $dep['min'])) {
+ $this->_invalidVersion('<dependencies>' . $group . $type . '<min>', $dep['min']);
+ }
+ }
+ if (isset($dep['max'])) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $dep['max'])) {
+ $this->_invalidVersion('<dependencies>' . $group . $type . '<max>', $dep['max']);
+ }
+ }
+ if (isset($dep['recommended'])) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $dep['recommended'])) {
+ $this->_invalidVersion('<dependencies>' . $group . $type . '<recommended>',
+ $dep['recommended']);
+ }
+ }
+ if (isset($dep['exclude'])) {
+ if (!is_array($dep['exclude'])) {
+ $dep['exclude'] = array($dep['exclude']);
+ }
+ foreach ($dep['exclude'] as $exclude) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $exclude)) {
+ $this->_invalidVersion('<dependencies>' . $group . $type . '<exclude>',
+ $exclude);
+ }
+ }
+ }
+ }
+
+ function _validateSubpackageDep($dep, $group)
+ {
+ $this->_validatePackageDep($dep, $group, '<subpackage>');
+ if (isset($dep['providesextension'])) {
+ $this->_subpackageCannotProvideExtension(isset($dep['name']) ? $dep['name'] : '');
+ }
+ if (isset($dep['conflicts'])) {
+ $this->_subpackagesCannotConflict(isset($dep['name']) ? $dep['name'] : '');
+ }
+ }
+
+ function _validateExtensionDep($dep, $group = false, $installcondition = false)
+ {
+ if (isset($dep['conflicts'])) {
+ $structure = array(
+ 'name',
+ '*min',
+ '*max',
+ '*exclude',
+ 'conflicts',
+ );
+ } else {
+ $structure = array(
+ 'name',
+ '*min',
+ '*max',
+ '*recommended',
+ '*exclude',
+ );
+ }
+ if ($installcondition) {
+ $type = '<installcondition><extension>';
+ } else {
+ $type = '<dependencies>' . $group . '<extension>';
+ }
+ if (isset($dep['name'])) {
+ $type .= '<name>' . $dep['name'] . '</name>';
+ }
+ $this->_stupidSchemaValidate($structure, $dep, $type);
+ if (isset($dep['min'])) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $dep['min'])) {
+ $this->_invalidVersion(substr($type, 1) . '<min', $dep['min']);
+ }
+ }
+ if (isset($dep['max'])) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $dep['max'])) {
+ $this->_invalidVersion(substr($type, 1) . '<max', $dep['max']);
+ }
+ }
+ if (isset($dep['recommended'])) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $dep['recommended'])) {
+ $this->_invalidVersion(substr($type, 1) . '<recommended', $dep['recommended']);
+ }
+ }
+ if (isset($dep['exclude'])) {
+ if (!is_array($dep['exclude'])) {
+ $dep['exclude'] = array($dep['exclude']);
+ }
+ foreach ($dep['exclude'] as $exclude) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $exclude)) {
+ $this->_invalidVersion(substr($type, 1) . '<exclude', $exclude);
+ }
+ }
+ }
+ }
+
+ function _validateOsDep($dep, $installcondition = false)
+ {
+ $structure = array(
+ 'name',
+ '*conflicts',
+ );
+ $type = $installcondition ? '<installcondition><os>' : '<dependencies><required><os>';
+ if ($this->_stupidSchemaValidate($structure, $dep, $type)) {
+ if ($dep['name'] == '*') {
+ if (array_key_exists('conflicts', $dep)) {
+ $this->_cannotConflictWithAllOs($type);
+ }
+ }
+ }
+ }
+
+ function _validateArchDep($dep, $installcondition = false)
+ {
+ $structure = array(
+ 'pattern',
+ '*conflicts',
+ );
+ $type = $installcondition ? '<installcondition><arch>' : '<dependencies><required><arch>';
+ $this->_stupidSchemaValidate($structure, $dep, $type);
+ }
+
+ function _validateInstallConditions($cond, $release)
+ {
+ $structure = array(
+ '*php',
+ '*extension',
+ '*os',
+ '*arch',
+ );
+ if (!$this->_stupidSchemaValidate($structure,
+ $cond, $release)) {
+ return false;
+ }
+ foreach (array('php', 'extension', 'os', 'arch') as $type) {
+ if (isset($cond[$type])) {
+ $iter = $cond[$type];
+ if (!is_array($iter) || !isset($iter[0])) {
+ $iter = array($iter);
+ }
+ foreach ($iter as $package) {
+ if ($type == 'extension') {
+ $this->{"_validate{$type}Dep"}($package, false, true);
+ } else {
+ $this->{"_validate{$type}Dep"}($package, true);
+ }
+ }
+ }
+ }
+ }
+
+ function _validateDependencies()
+ {
+ $structure = array(
+ 'required',
+ '*optional',
+ '*group->name->hint'
+ );
+ if (!$this->_stupidSchemaValidate($structure,
+ $this->_packageInfo['dependencies'], '<dependencies>')) {
+ return false;
+ }
+ foreach (array('required', 'optional') as $simpledep) {
+ if (isset($this->_packageInfo['dependencies'][$simpledep])) {
+ if ($simpledep == 'optional') {
+ $structure = array(
+ '*package',
+ '*subpackage',
+ '*extension',
+ );
+ } else {
+ $structure = array(
+ 'php',
+ 'pearinstaller',
+ '*package',
+ '*subpackage',
+ '*extension',
+ '*os',
+ '*arch',
+ );
+ }
+ if ($this->_stupidSchemaValidate($structure,
+ $this->_packageInfo['dependencies'][$simpledep],
+ "<dependencies><$simpledep>")) {
+ foreach (array('package', 'subpackage', 'extension') as $type) {
+ if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) {
+ $iter = $this->_packageInfo['dependencies'][$simpledep][$type];
+ if (!isset($iter[0])) {
+ $iter = array($iter);
+ }
+ foreach ($iter as $package) {
+ if ($type != 'extension') {
+ if (isset($package['uri'])) {
+ if (isset($package['channel'])) {
+ $this->_UrlOrChannel($type,
+ $package['name']);
+ }
+ } else {
+ if (!isset($package['channel'])) {
+ $this->_NoChannel($type, $package['name']);
+ }
+ }
+ }
+ $this->{"_validate{$type}Dep"}($package, "<$simpledep>");
+ }
+ }
+ }
+ if ($simpledep == 'optional') {
+ continue;
+ }
+ foreach (array('php', 'pearinstaller', 'os', 'arch') as $type) {
+ if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) {
+ $iter = $this->_packageInfo['dependencies'][$simpledep][$type];
+ if (!isset($iter[0])) {
+ $iter = array($iter);
+ }
+ foreach ($iter as $package) {
+ $this->{"_validate{$type}Dep"}($package);
+ }
+ }
+ }
+ }
+ }
+ }
+ if (isset($this->_packageInfo['dependencies']['group'])) {
+ $groups = $this->_packageInfo['dependencies']['group'];
+ if (!isset($groups[0])) {
+ $groups = array($groups);
+ }
+ $structure = array(
+ '*package',
+ '*subpackage',
+ '*extension',
+ );
+ foreach ($groups as $group) {
+ if ($this->_stupidSchemaValidate($structure, $group, '<group>')) {
+ if (!PEAR_Validate::validGroupName($group['attribs']['name'])) {
+ $this->_invalidDepGroupName($group['attribs']['name']);
+ }
+ foreach (array('package', 'subpackage', 'extension') as $type) {
+ if (isset($group[$type])) {
+ $iter = $group[$type];
+ if (!isset($iter[0])) {
+ $iter = array($iter);
+ }
+ foreach ($iter as $package) {
+ if ($type != 'extension') {
+ if (isset($package['uri'])) {
+ if (isset($package['channel'])) {
+ $this->_UrlOrChannelGroup($type,
+ $package['name'],
+ $group['name']);
+ }
+ } else {
+ if (!isset($package['channel'])) {
+ $this->_NoChannelGroup($type,
+ $package['name'],
+ $group['name']);
+ }
+ }
+ }
+ $this->{"_validate{$type}Dep"}($package, '<group name="' .
+ $group['attribs']['name'] . '">');
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ function _validateCompatible()
+ {
+ $compat = $this->_packageInfo['compatible'];
+ if (!isset($compat[0])) {
+ $compat = array($compat);
+ }
+ $required = array('name', 'channel', 'min', 'max', '*exclude');
+ foreach ($compat as $package) {
+ $type = '<compatible>';
+ if (is_array($package) && array_key_exists('name', $package)) {
+ $type .= '<name>' . $package['name'] . '</name>';
+ }
+ $this->_stupidSchemaValidate($required, $package, $type);
+ if (is_array($package) && array_key_exists('min', $package)) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $package['min'])) {
+ $this->_invalidVersion(substr($type, 1) . '<min', $package['min']);
+ }
+ }
+ if (is_array($package) && array_key_exists('max', $package)) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $package['max'])) {
+ $this->_invalidVersion(substr($type, 1) . '<max', $package['max']);
+ }
+ }
+ if (is_array($package) && array_key_exists('exclude', $package)) {
+ if (!is_array($package['exclude'])) {
+ $package['exclude'] = array($package['exclude']);
+ }
+ foreach ($package['exclude'] as $exclude) {
+ if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
+ $exclude)) {
+ $this->_invalidVersion(substr($type, 1) . '<exclude', $exclude);
+ }
+ }
+ }
+ }
+ }
+
+ function _validateBundle($list)
+ {
+ if (!is_array($list) || !isset($list['bundledpackage'])) {
+ return $this->_NoBundledPackages();
+ }
+ if (!is_array($list['bundledpackage']) || !isset($list['bundledpackage'][0])) {
+ return $this->_AtLeast2BundledPackages();
+ }
+ foreach ($list['bundledpackage'] as $package) {
+ if (!is_string($package)) {
+ $this->_bundledPackagesMustBeFilename();
+ }
+ }
+ }
+
+ function _validateFilelist($list = false, $allowignore = false, $dirs = '')
+ {
+ $iscontents = false;
+ if (!$list) {
+ $iscontents = true;
+ $list = $this->_packageInfo['contents'];
+ if (isset($this->_packageInfo['bundle'])) {
+ return $this->_validateBundle($list);
+ }
+ }
+ if ($allowignore) {
+ $struc = array(
+ '*install->name->as',
+ '*ignore->name'
+ );
+ } else {
+ $struc = array(
+ '*dir->name->?baseinstalldir',
+ '*file->name->role->?baseinstalldir->?md5sum'
+ );
+ if (isset($list['dir']) && isset($list['file'])) {
+ // stave off validation errors without requiring a set order.
+ $_old = $list;
+ if (isset($list['attribs'])) {
+ $list = array('attribs' => $_old['attribs']);
+ }
+ $list['dir'] = $_old['dir'];
+ $list['file'] = $_old['file'];
+ }
+ }
+ if (!isset($list['attribs']) || !isset($list['attribs']['name'])) {
+ $unknown = $allowignore ? '<filelist>' : '<dir name="*unknown*">';
+ $dirname = $iscontents ? '<contents>' : $unknown;
+ } else {
+ $dirname = '<dir name="' . $list['attribs']['name'] . '">';
+ if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
+ str_replace('\\', '/', $list['attribs']['name']))) {
+ // file contains .. parent directory or . cur directory
+ $this->_invalidDirName($list['attribs']['name']);
+ }
+ }
+ $res = $this->_stupidSchemaValidate($struc, $list, $dirname);
+ if ($allowignore && $res) {
+ $ignored_or_installed = array();
+ $this->_pf->getFilelist();
+ $fcontents = $this->_pf->getContents();
+ $filelist = array();
+ if (!isset($fcontents['dir']['file'][0])) {
+ $fcontents['dir']['file'] = array($fcontents['dir']['file']);
+ }
+ foreach ($fcontents['dir']['file'] as $file) {
+ $filelist[$file['attribs']['name']] = true;
+ }
+ if (isset($list['install'])) {
+ if (!isset($list['install'][0])) {
+ $list['install'] = array($list['install']);
+ }
+ foreach ($list['install'] as $file) {
+ if (!isset($filelist[$file['attribs']['name']])) {
+ $this->_notInContents($file['attribs']['name'], 'install');
+ continue;
+ }
+ if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) {
+ $this->_multipleInstallAs($file['attribs']['name']);
+ }
+ if (!isset($ignored_or_installed[$file['attribs']['name']])) {
+ $ignored_or_installed[$file['attribs']['name']] = array();
+ }
+ $ignored_or_installed[$file['attribs']['name']][] = 1;
+ if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
+ str_replace('\\', '/', $file['attribs']['as']))) {
+ // file contains .. parent directory or . cur directory references
+ $this->_invalidFileInstallAs($file['attribs']['name'],
+ $file['attribs']['as']);
+ }
+ }
+ }
+ if (isset($list['ignore'])) {
+ if (!isset($list['ignore'][0])) {
+ $list['ignore'] = array($list['ignore']);
+ }
+ foreach ($list['ignore'] as $file) {
+ if (!isset($filelist[$file['attribs']['name']])) {
+ $this->_notInContents($file['attribs']['name'], 'ignore');
+ continue;
+ }
+ if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) {
+ $this->_ignoreAndInstallAs($file['attribs']['name']);
+ }
+ }
+ }
+ }
+ if (!$allowignore && isset($list['file'])) {
+ if (is_string($list['file'])) {
+ $this->_oldStyleFileNotAllowed();
+ return false;
+ }
+ if (!isset($list['file'][0])) {
+ // single file
+ $list['file'] = array($list['file']);
+ }
+ foreach ($list['file'] as $i => $file)
+ {
+ if (isset($file['attribs']) && isset($file['attribs']['name'])) {
+ if ($file['attribs']['name']{0} == '.' &&
+ $file['attribs']['name']{1} == '/') {
+ // name is something like "./doc/whatever.txt"
+ $this->_invalidFileName($file['attribs']['name'], $dirname);
+ }
+ if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
+ str_replace('\\', '/', $file['attribs']['name']))) {
+ // file contains .. parent directory or . cur directory
+ $this->_invalidFileName($file['attribs']['name'], $dirname);
+ }
+ }
+ if (isset($file['attribs']) && isset($file['attribs']['role'])) {
+ if (!$this->_validateRole($file['attribs']['role'])) {
+ if (isset($this->_packageInfo['usesrole'])) {
+ $roles = $this->_packageInfo['usesrole'];
+ if (!isset($roles[0])) {
+ $roles = array($roles);
+ }
+ foreach ($roles as $role) {
+ if ($role['role'] = $file['attribs']['role']) {
+ $msg = 'This package contains role "%role%" and requires ' .
+ 'package "%package%" to be used';
+ if (isset($role['uri'])) {
+ $params = array('role' => $role['role'],
+ 'package' => $role['uri']);
+ } else {
+ $params = array('role' => $role['role'],
+ 'package' => $this->_pf->_registry->
+ parsedPackageNameToString(array('package' =>
+ $role['package'], 'channel' => $role['channel']),
+ true));
+ }
+ $this->_stack->push('_mustInstallRole', 'error', $params, $msg);
+ }
+ }
+ }
+ $this->_invalidFileRole($file['attribs']['name'],
+ $dirname, $file['attribs']['role']);
+ }
+ }
+ if (!isset($file['attribs'])) {
+ continue;
+ }
+ $save = $file['attribs'];
+ if ($dirs) {
+ $save['name'] = $dirs . '/' . $save['name'];
+ }
+ unset($file['attribs']);
+ if (count($file) && $this->_curState != PEAR_VALIDATE_DOWNLOADING) { // has tasks
+ foreach ($file as $task => $value) {
+ if ($tagClass = $this->_pf->getTask($task)) {
+ if (!is_array($value) || !isset($value[0])) {
+ $value = array($value);
+ }
+ foreach ($value as $v) {
+ $ret = call_user_func(array($tagClass, 'validateXml'),
+ $this->_pf, $v, $this->_pf->_config, $save);
+ if (is_array($ret)) {
+ $this->_invalidTask($task, $ret, isset($save['name']) ?
+ $save['name'] : '');
+ }
+ }
+ } else {
+ if (isset($this->_packageInfo['usestask'])) {
+ $roles = $this->_packageInfo['usestask'];
+ if (!isset($roles[0])) {
+ $roles = array($roles);
+ }
+ foreach ($roles as $role) {
+ if ($role['task'] = $task) {
+ $msg = 'This package contains task "%task%" and requires ' .
+ 'package "%package%" to be used';
+ if (isset($role['uri'])) {
+ $params = array('task' => $role['task'],
+ 'package' => $role['uri']);
+ } else {
+ $params = array('task' => $role['task'],
+ 'package' => $this->_pf->_registry->
+ parsedPackageNameToString(array('package' =>
+ $role['package'], 'channel' => $role['channel']),
+ true));
+ }
+ $this->_stack->push('_mustInstallTask', 'error',
+ $params, $msg);
+ }
+ }
+ }
+ $this->_unknownTask($task, $save['name']);
+ }
+ }
+ }
+ }
+ }
+ if (isset($list['ignore'])) {
+ if (!$allowignore) {
+ $this->_ignoreNotAllowed('ignore');
+ }
+ }
+ if (isset($list['install'])) {
+ if (!$allowignore) {
+ $this->_ignoreNotAllowed('install');
+ }
+ }
+ if (isset($list['file'])) {
+ if ($allowignore) {
+ $this->_fileNotAllowed('file');
+ }
+ }
+ if (isset($list['dir'])) {
+ if ($allowignore) {
+ $this->_fileNotAllowed('dir');
+ } else {
+ if (!isset($list['dir'][0])) {
+ $list['dir'] = array($list['dir']);
+ }
+ foreach ($list['dir'] as $dir) {
+ if (isset($dir['attribs']) && isset($dir['attribs']['name'])) {
+ if ($dir['attribs']['name'] == '/' ||
+ !isset($this->_packageInfo['contents']['dir']['dir'])) {
+ // always use nothing if the filelist has already been flattened
+ $newdirs = '';
+ } elseif ($dirs == '') {
+ $newdirs = $dir['attribs']['name'];
+ } else {
+ $newdirs = $dirs . '/' . $dir['attribs']['name'];
+ }
+ } else {
+ $newdirs = $dirs;
+ }
+ $this->_validateFilelist($dir, $allowignore, $newdirs);
+ }
+ }
+ }
+ }
+
+ function _validateRelease()
+ {
+ if (isset($this->_packageInfo['phprelease'])) {
+ $release = 'phprelease';
+ if (isset($this->_packageInfo['providesextension'])) {
+ $this->_cannotProvideExtension($release);
+ }
+ if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
+ $this->_cannotHaveSrcpackage($release);
+ }
+ $releases = $this->_packageInfo['phprelease'];
+ if (!is_array($releases)) {
+ return true;
+ }
+ if (!isset($releases[0])) {
+ $releases = array($releases);
+ }
+ foreach ($releases as $rel) {
+ $this->_stupidSchemaValidate(array(
+ '*installconditions',
+ '*filelist',
+ ), $rel, '<phprelease>');
+ }
+ }
+ foreach (array('', 'zend') as $prefix) {
+ $releasetype = $prefix . 'extsrcrelease';
+ if (isset($this->_packageInfo[$releasetype])) {
+ $release = $releasetype;
+ if (!isset($this->_packageInfo['providesextension'])) {
+ $this->_mustProvideExtension($release);
+ }
+ if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
+ $this->_cannotHaveSrcpackage($release);
+ }
+ $releases = $this->_packageInfo[$releasetype];
+ if (!is_array($releases)) {
+ return true;
+ }
+ if (!isset($releases[0])) {
+ $releases = array($releases);
+ }
+ foreach ($releases as $rel) {
+ $this->_stupidSchemaValidate(array(
+ '*installconditions',
+ '*configureoption->name->prompt->?default',
+ '*binarypackage',
+ '*filelist',
+ ), $rel, '<' . $releasetype . '>');
+ if (isset($rel['binarypackage'])) {
+ if (!is_array($rel['binarypackage']) || !isset($rel['binarypackage'][0])) {
+ $rel['binarypackage'] = array($rel['binarypackage']);
+ }
+ foreach ($rel['binarypackage'] as $bin) {
+ if (!is_string($bin)) {
+ $this->_binaryPackageMustBePackagename();
+ }
+ }
+ }
+ }
+ }
+ $releasetype = 'extbinrelease';
+ if (isset($this->_packageInfo[$releasetype])) {
+ $release = $releasetype;
+ if (!isset($this->_packageInfo['providesextension'])) {
+ $this->_mustProvideExtension($release);
+ }
+ if (isset($this->_packageInfo['channel']) &&
+ !isset($this->_packageInfo['srcpackage'])) {
+ $this->_mustSrcPackage($release);
+ }
+ if (isset($this->_packageInfo['uri']) && !isset($this->_packageInfo['srcuri'])) {
+ $this->_mustSrcuri($release);
+ }
+ $releases = $this->_packageInfo[$releasetype];
+ if (!is_array($releases)) {
+ return true;
+ }
+ if (!isset($releases[0])) {
+ $releases = array($releases);
+ }
+ foreach ($releases as $rel) {
+ $this->_stupidSchemaValidate(array(
+ '*installconditions',
+ '*filelist',
+ ), $rel, '<' . $releasetype . '>');
+ }
+ }
+ }
+ if (isset($this->_packageInfo['bundle'])) {
+ $release = 'bundle';
+ if (isset($this->_packageInfo['providesextension'])) {
+ $this->_cannotProvideExtension($release);
+ }
+ if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
+ $this->_cannotHaveSrcpackage($release);
+ }
+ $releases = $this->_packageInfo['bundle'];
+ if (!is_array($releases) || !isset($releases[0])) {
+ $releases = array($releases);
+ }
+ foreach ($releases as $rel) {
+ $this->_stupidSchemaValidate(array(
+ '*installconditions',
+ '*filelist',
+ ), $rel, '<bundle>');
+ }
+ }
+ foreach ($releases as $rel) {
+ if (is_array($rel) && array_key_exists('installconditions', $rel)) {
+ $this->_validateInstallConditions($rel['installconditions'],
+ "<$release><installconditions>");
+ }
+ if (is_array($rel) && array_key_exists('filelist', $rel)) {
+ if ($rel['filelist']) {
+
+ $this->_validateFilelist($rel['filelist'], true);
+ }
+ }
+ }
+ }
+
+ /**
+ * This is here to allow role extension through plugins
+ * @param string
+ */
+ function _validateRole($role)
+ {
+ return in_array($role, PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType()));
+ }
+
+ function _pearVersionTooLow($version)
+ {
+ $this->_stack->push(__FUNCTION__, 'error',
+ array('version' => $version),
+ 'This package.xml requires PEAR version %version% to parse properly, we are ' .
+ 'version 1.9.4');
+ }
+
+ function _invalidTagOrder($oktags, $actual, $root)
+ {
+ $this->_stack->push(__FUNCTION__, 'error',
+ array('oktags' => $oktags, 'actual' => $actual, 'root' => $root),
+ 'Invalid tag order in %root%, found <%actual%> expected one of "%oktags%"');
+ }
+
+ function _ignoreNotAllowed($type)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
+ '<%type%> is not allowed inside global <contents>, only inside ' .
+ '<phprelease>/<extbinrelease>/<zendextbinrelease>, use <dir> and <file> only');
+ }
+
+ function _fileNotAllowed($type)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
+ '<%type%> is not allowed inside release <filelist>, only inside ' .
+ '<contents>, use <ignore> and <install> only');
+ }
+
+ function _oldStyleFileNotAllowed()
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array(),
+ 'Old-style <file>name</file> is not allowed. Use' .
+ '<file name="name" role="role"/>');
+ }
+
+ function _tagMissingAttribute($tag, $attr, $context)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag,
+ 'attribute' => $attr, 'context' => $context),
+ 'tag <%tag%> in context "%context%" has no attribute "%attribute%"');
+ }
+
+ function _tagHasNoAttribs($tag, $context)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag,
+ 'context' => $context),
+ 'tag <%tag%> has no attributes in context "%context%"');
+ }
+
+ function _invalidInternalStructure()
+ {
+ $this->_stack->push(__FUNCTION__, 'exception', array(),
+ 'internal array was not generated by compatible parser, or extreme parser error, cannot continue');
+ }
+
+ function _invalidFileRole($file, $dir, $role)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array(
+ 'file' => $file, 'dir' => $dir, 'role' => $role,
+ 'roles' => PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())),
+ 'File "%file%" in directory "%dir%" has invalid role "%role%", should be one of %roles%');
+ }
+
+ function _invalidFileName($file, $dir)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array(
+ 'file' => $file),
+ 'File "%file%" in directory "%dir%" cannot begin with "./" or contain ".."');
+ }
+
+ function _invalidFileInstallAs($file, $as)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array(
+ 'file' => $file, 'as' => $as),
+ 'File "%file%" <install as="%as%"/> cannot contain "./" or contain ".."');
+ }
+
+ function _invalidDirName($dir)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array(
+ 'dir' => $file),
+ 'Directory "%dir%" cannot begin with "./" or contain ".."');
+ }
+
+ function _filelistCannotContainFile($filelist)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist),
+ '<%tag%> can only contain <dir>, contains <file>. Use ' .
+ '<dir name="/"> as the first dir element');
+ }
+
+ function _filelistMustContainDir($filelist)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist),
+ '<%tag%> must contain <dir>. Use <dir name="/"> as the ' .
+ 'first dir element');
+ }
+
+ function _tagCannotBeEmpty($tag)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag),
+ '<%tag%> cannot be empty (<%tag%/>)');
+ }
+
+ function _UrlOrChannel($type, $name)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
+ 'name' => $name),
+ 'Required dependency <%type%> "%name%" can have either url OR ' .
+ 'channel attributes, and not both');
+ }
+
+ function _NoChannel($type, $name)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
+ 'name' => $name),
+ 'Required dependency <%type%> "%name%" must have either url OR ' .
+ 'channel attributes');
+ }
+
+ function _UrlOrChannelGroup($type, $name, $group)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
+ 'name' => $name, 'group' => $group),
+ 'Group "%group%" dependency <%type%> "%name%" can have either url OR ' .
+ 'channel attributes, and not both');
+ }
+
+ function _NoChannelGroup($type, $name, $group)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
+ 'name' => $name, 'group' => $group),
+ 'Group "%group%" dependency <%type%> "%name%" must have either url OR ' .
+ 'channel attributes');
+ }
+
+ function _unknownChannel($channel)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('channel' => $channel),
+ 'Unknown channel "%channel%"');
+ }
+
+ function _noPackageVersion()
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array(),
+ 'package.xml <package> tag has no version attribute, or version is not 2.0');
+ }
+
+ function _NoBundledPackages()
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array(),
+ 'No <bundledpackage> tag was found in <contents>, required for bundle packages');
+ }
+
+ function _AtLeast2BundledPackages()
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array(),
+ 'At least 2 packages must be bundled in a bundle package');
+ }
+
+ function _ChannelOrUri($name)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
+ 'Bundled package "%name%" can have either a uri or a channel, not both');
+ }
+
+ function _noChildTag($child, $tag)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('child' => $child, 'tag' => $tag),
+ 'Tag <%tag%> is missing child tag <%child%>');
+ }
+
+ function _invalidVersion($type, $value)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value),
+ 'Version type <%type%> is not a valid version (%value%)');
+ }
+
+ function _invalidState($type, $value)
+ {
+ $states = array('stable', 'beta', 'alpha', 'devel');
+ if ($type != 'api') {
+ $states[] = 'snapshot';
+ }
+ if (strtolower($value) == 'rc') {
+ $this->_stack->push(__FUNCTION__, 'error',
+ array('version' => $this->_packageInfo['version']['release']),
+ 'RC is not a state, it is a version postfix, try %version%RC1, stability beta');
+ }
+ $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value,
+ 'types' => $states),
+ 'Stability type <%type%> is not a valid stability (%value%), must be one of ' .
+ '%types%');
+ }
+
+ function _invalidTask($task, $ret, $file)
+ {
+ switch ($ret[0]) {
+ case PEAR_TASK_ERROR_MISSING_ATTRIB :
+ $info = array('attrib' => $ret[1], 'task' => $task, 'file' => $file);
+ $msg = 'task <%task%> is missing attribute "%attrib%" in file %file%';
+ break;
+ case PEAR_TASK_ERROR_NOATTRIBS :
+ $info = array('task' => $task, 'file' => $file);
+ $msg = 'task <%task%> has no attributes in file %file%';
+ break;
+ case PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE :
+ $info = array('attrib' => $ret[1], 'values' => $ret[3],
+ 'was' => $ret[2], 'task' => $task, 'file' => $file);
+ $msg = 'task <%task%> attribute "%attrib%" has the wrong value "%was%" '.
+ 'in file %file%, expecting one of "%values%"';
+ break;
+ case PEAR_TASK_ERROR_INVALID :
+ $info = array('reason' => $ret[1], 'task' => $task, 'file' => $file);
+ $msg = 'task <%task%> in file %file% is invalid because of "%reason%"';
+ break;
+ }
+ $this->_stack->push(__FUNCTION__, 'error', $info, $msg);
+ }
+
+ function _unknownTask($task, $file)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('task' => $task, 'file' => $file),
+ 'Unknown task "%task%" passed in file <file name="%file%">');
+ }
+
+ function _subpackageCannotProvideExtension($name)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
+ 'Subpackage dependency "%name%" cannot use <providesextension>, ' .
+ 'only package dependencies can use this tag');
+ }
+
+ function _subpackagesCannotConflict($name)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
+ 'Subpackage dependency "%name%" cannot use <conflicts/>, ' .
+ 'only package dependencies can use this tag');
+ }
+
+ function _cannotProvideExtension($release)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
+ '<%release%> packages cannot use <providesextension>, only extbinrelease, extsrcrelease, zendextsrcrelease, and zendextbinrelease can provide a PHP extension');
+ }
+
+ function _mustProvideExtension($release)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
+ '<%release%> packages must use <providesextension> to indicate which PHP extension is provided');
+ }
+
+ function _cannotHaveSrcpackage($release)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
+ '<%release%> packages cannot specify a source code package, only extension binaries may use the <srcpackage> tag');
+ }
+
+ function _mustSrcPackage($release)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
+ '<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcpackage>');
+ }
+
+ function _mustSrcuri($release)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
+ '<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcuri>');
+ }
+
+ function _uriDepsCannotHaveVersioning($type)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
+ '%type%: dependencies with a <uri> tag cannot have any versioning information');
+ }
+
+ function _conflictingDepsCannotHaveVersioning($type)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
+ '%type%: conflicting dependencies cannot have versioning info, use <exclude> to ' .
+ 'exclude specific versions of a dependency');
+ }
+
+ function _DepchannelCannotBeUri($type)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
+ '%type%: channel cannot be __uri, this is a pseudo-channel reserved for uri ' .
+ 'dependencies only');
+ }
+
+ function _bundledPackagesMustBeFilename()
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array(),
+ '<bundledpackage> tags must contain only the filename of a package release ' .
+ 'in the bundle');
+ }
+
+ function _binaryPackageMustBePackagename()
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array(),
+ '<binarypackage> tags must contain the name of a package that is ' .
+ 'a compiled version of this extsrc/zendextsrc package');
+ }
+
+ function _fileNotFound($file)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
+ 'File "%file%" in package.xml does not exist');
+ }
+
+ function _notInContents($file, $tag)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('file' => $file, 'tag' => $tag),
+ '<%tag% name="%file%"> is invalid, file is not in <contents>');
+ }
+
+ function _cannotValidateNoPathSet()
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array(),
+ 'Cannot validate files, no path to package file is set (use setPackageFile())');
+ }
+
+ function _usesroletaskMustHaveChannelOrUri($role, $tag)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag),
+ '<%tag%> for role "%role%" must contain either <uri>, or <channel> and <package>');
+ }
+
+ function _usesroletaskMustHavePackage($role, $tag)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag),
+ '<%tag%> for role "%role%" must contain <package>');
+ }
+
+ function _usesroletaskMustHaveRoleTask($tag, $type)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, 'type' => $type),
+ '<%tag%> must contain <%type%> defining the %type% to be used');
+ }
+
+ function _cannotConflictWithAllOs($type)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag),
+ '%tag% cannot conflict with all OSes');
+ }
+
+ function _invalidDepGroupName($name)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
+ 'Invalid dependency group name "%name%"');
+ }
+
+ function _multipleToplevelDirNotAllowed()
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array(),
+ 'Multiple top-level <dir> tags are not allowed. Enclose them ' .
+ 'in a <dir name="/">');
+ }
+
+ function _multipleInstallAs($file)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
+ 'Only one <install> tag is allowed for file "%file%"');
+ }
+
+ function _ignoreAndInstallAs($file)
+ {
+ $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
+ 'Cannot have both <ignore> and <install> tags for file "%file%"');
+ }
+
+ function _analyzeBundledPackages()
+ {
+ if (!$this->_isValid) {
+ return false;
+ }
+ if (!$this->_pf->getPackageType() == 'bundle') {
+ return false;
+ }
+ if (!isset($this->_pf->_packageFile)) {
+ return false;
+ }
+ $dir_prefix = dirname($this->_pf->_packageFile);
+ $common = new PEAR_Common;
+ $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') :
+ array($common, 'log');
+ $info = $this->_pf->getContents();
+ $info = $info['bundledpackage'];
+ if (!is_array($info)) {
+ $info = array($info);
+ }
+ $pkg = &new PEAR_PackageFile($this->_pf->_config);
+ foreach ($info as $package) {
+ if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $package)) {
+ $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $package);
+ $this->_isValid = 0;
+ continue;
+ }
+ call_user_func_array($log, array(1, "Analyzing bundled package $package"));
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $ret = $pkg->fromAnyFile($dir_prefix . DIRECTORY_SEPARATOR . $package,
+ PEAR_VALIDATE_NORMAL);
+ PEAR::popErrorHandling();
+ if (PEAR::isError($ret)) {
+ call_user_func_array($log, array(0, "ERROR: package $package is not a valid " .
+ 'package'));
+ $inf = $ret->getUserInfo();
+ if (is_array($inf)) {
+ foreach ($inf as $err) {
+ call_user_func_array($log, array(1, $err['message']));
+ }
+ }
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function _analyzePhpFiles()
+ {
+ if (!$this->_isValid) {
+ return false;
+ }
+ if (!isset($this->_pf->_packageFile)) {
+ $this->_cannotValidateNoPathSet();
+ return false;
+ }
+ $dir_prefix = dirname($this->_pf->_packageFile);
+ $common = new PEAR_Common;
+ $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') :
+ array(&$common, 'log');
+ $info = $this->_pf->getContents();
+ if (!$info || !isset($info['dir']['file'])) {
+ $this->_tagCannotBeEmpty('contents><dir');
+ return false;
+ }
+ $info = $info['dir']['file'];
+ if (isset($info['attribs'])) {
+ $info = array($info);
+ }
+ $provides = array();
+ foreach ($info as $fa) {
+ $fa = $fa['attribs'];
+ $file = $fa['name'];
+ if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $file)) {
+ $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $file);
+ $this->_isValid = 0;
+ continue;
+ }
+ if (in_array($fa['role'], PEAR_Installer_Role::getPhpRoles()) && $dir_prefix) {
+ call_user_func_array($log, array(1, "Analyzing $file"));
+ $srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file);
+ if ($srcinfo) {
+ $provides = array_merge($provides, $this->_buildProvidesArray($srcinfo));
+ }
+ }
+ }
+ $this->_packageName = $pn = $this->_pf->getPackage();
+ $pnl = strlen($pn);
+ foreach ($provides as $key => $what) {
+ if (isset($what['explicit']) || !$what) {
+ // skip conformance checks if the provides entry is
+ // specified in the package.xml file
+ continue;
+ }
+ extract($what);
+ if ($type == 'class') {
+ if (!strncasecmp($name, $pn, $pnl)) {
+ continue;
+ }
+ $this->_stack->push(__FUNCTION__, 'warning',
+ array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn),
+ 'in %file%: %type% "%name%" not prefixed with package name "%package%"');
+ } elseif ($type == 'function') {
+ if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) {
+ continue;
+ }
+ $this->_stack->push(__FUNCTION__, 'warning',
+ array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn),
+ 'in %file%: %type% "%name%" not prefixed with package name "%package%"');
+ }
+ }
+ return $this->_isValid;
+ }
+
+ /**
+ * Analyze the source code of the given PHP file
+ *
+ * @param string Filename of the PHP file
+ * @param boolean whether to analyze $file as the file contents
+ * @return mixed
+ */
+ function analyzeSourceCode($file, $string = false)
+ {
+ if (!function_exists("token_get_all")) {
+ $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
+ 'Parser error: token_get_all() function must exist to analyze source code, PHP may have been compiled with --disable-tokenizer');
+ return false;
+ }
+
+ if (!defined('T_DOC_COMMENT')) {
+ define('T_DOC_COMMENT', T_COMMENT);
+ }
+
+ if (!defined('T_INTERFACE')) {
+ define('T_INTERFACE', -1);
+ }
+
+ if (!defined('T_IMPLEMENTS')) {
+ define('T_IMPLEMENTS', -1);
+ }
+
+ if ($string) {
+ $contents = $file;
+ } else {
+ if (!$fp = @fopen($file, "r")) {
+ return false;
+ }
+ fclose($fp);
+ $contents = file_get_contents($file);
+ }
+
+ // Silence this function so we can catch PHP Warnings and show our own custom message
+ $tokens = @token_get_all($contents);
+ if (isset($php_errormsg)) {
+ if (isset($this->_stack)) {
+ $pn = $this->_pf->getPackage();
+ $this->_stack->push(__FUNCTION__, 'warning',
+ array('file' => $file, 'package' => $pn),
+ 'in %file%: Could not process file for unkown reasons,' .
+ ' possibly a PHP parse error in %file% from %package%');
+ }
+ }
+/*
+ for ($i = 0; $i < sizeof($tokens); $i++) {
+ @list($token, $data) = $tokens[$i];
+ if (is_string($token)) {
+ var_dump($token);
+ } else {
+ print token_name($token) . ' ';
+ var_dump(rtrim($data));
+ }
+ }
+*/
+ $look_for = 0;
+ $paren_level = 0;
+ $bracket_level = 0;
+ $brace_level = 0;
+ $lastphpdoc = '';
+ $current_class = '';
+ $current_interface = '';
+ $current_class_level = -1;
+ $current_function = '';
+ $current_function_level = -1;
+ $declared_classes = array();
+ $declared_interfaces = array();
+ $declared_functions = array();
+ $declared_methods = array();
+ $used_classes = array();
+ $used_functions = array();
+ $extends = array();
+ $implements = array();
+ $nodeps = array();
+ $inquote = false;
+ $interface = false;
+ for ($i = 0; $i < sizeof($tokens); $i++) {
+ if (is_array($tokens[$i])) {
+ list($token, $data) = $tokens[$i];
+ } else {
+ $token = $tokens[$i];
+ $data = '';
+ }
+
+ if ($inquote) {
+ if ($token != '"' && $token != T_END_HEREDOC) {
+ continue;
+ } else {
+ $inquote = false;
+ continue;
+ }
+ }
+
+ switch ($token) {
+ case T_WHITESPACE :
+ continue;
+ case ';':
+ if ($interface) {
+ $current_function = '';
+ $current_function_level = -1;
+ }
+ break;
+ case '"':
+ case T_START_HEREDOC:
+ $inquote = true;
+ break;
+ case T_CURLY_OPEN:
+ case T_DOLLAR_OPEN_CURLY_BRACES:
+ case '{': $brace_level++; continue 2;
+ case '}':
+ $brace_level--;
+ if ($current_class_level == $brace_level) {
+ $current_class = '';
+ $current_class_level = -1;
+ }
+ if ($current_function_level == $brace_level) {
+ $current_function = '';
+ $current_function_level = -1;
+ }
+ continue 2;
+ case '[': $bracket_level++; continue 2;
+ case ']': $bracket_level--; continue 2;
+ case '(': $paren_level++; continue 2;
+ case ')': $paren_level--; continue 2;
+ case T_INTERFACE:
+ $interface = true;
+ case T_CLASS:
+ if (($current_class_level != -1) || ($current_function_level != -1)) {
+ if (isset($this->_stack)) {
+ $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
+ 'Parser error: invalid PHP found in file "%file%"');
+ } else {
+ PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"",
+ PEAR_COMMON_ERROR_INVALIDPHP);
+ }
+
+ return false;
+ }
+ case T_FUNCTION:
+ case T_NEW:
+ case T_EXTENDS:
+ case T_IMPLEMENTS:
+ $look_for = $token;
+ continue 2;
+ case T_STRING:
+ if (version_compare(zend_version(), '2.0', '<')) {
+ if (in_array(strtolower($data),
+ array('public', 'private', 'protected', 'abstract',
+ 'interface', 'implements', 'throw')
+ )
+ ) {
+ if (isset($this->_stack)) {
+ $this->_stack->push(__FUNCTION__, 'warning', array(
+ 'file' => $file),
+ 'Error, PHP5 token encountered in %file%,' .
+ ' analysis should be in PHP5');
+ } else {
+ PEAR::raiseError('Error: PHP5 token encountered in ' . $file .
+ 'packaging should be done in PHP 5');
+ return false;
+ }
+ }
+ }
+
+ if ($look_for == T_CLASS) {
+ $current_class = $data;
+ $current_class_level = $brace_level;
+ $declared_classes[] = $current_class;
+ } elseif ($look_for == T_INTERFACE) {
+ $current_interface = $data;
+ $current_class_level = $brace_level;
+ $declared_interfaces[] = $current_interface;
+ } elseif ($look_for == T_IMPLEMENTS) {
+ $implements[$current_class] = $data;
+ } elseif ($look_for == T_EXTENDS) {
+ $extends[$current_class] = $data;
+ } elseif ($look_for == T_FUNCTION) {
+ if ($current_class) {
+ $current_function = "$current_class::$data";
+ $declared_methods[$current_class][] = $data;
+ } elseif ($current_interface) {
+ $current_function = "$current_interface::$data";
+ $declared_methods[$current_interface][] = $data;
+ } else {
+ $current_function = $data;
+ $declared_functions[] = $current_function;
+ }
+
+ $current_function_level = $brace_level;
+ $m = array();
+ } elseif ($look_for == T_NEW) {
+ $used_classes[$data] = true;
+ }
+
+ $look_for = 0;
+ continue 2;
+ case T_VARIABLE:
+ $look_for = 0;
+ continue 2;
+ case T_DOC_COMMENT:
+ case T_COMMENT:
+ if (preg_match('!^/\*\*\s!', $data)) {
+ $lastphpdoc = $data;
+ if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) {
+ $nodeps = array_merge($nodeps, $m[1]);
+ }
+ }
+ continue 2;
+ case T_DOUBLE_COLON:
+ $token = $tokens[$i - 1][0];
+ if (!($token == T_WHITESPACE || $token == T_STRING || $token == T_STATIC)) {
+ if (isset($this->_stack)) {
+ $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file),
+ 'Parser error: invalid PHP found in file "%file%"');
+ } else {
+ PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"",
+ PEAR_COMMON_ERROR_INVALIDPHP);
+ }
+
+ return false;
+ }
+
+ $class = $tokens[$i - 1][1];
+ if (strtolower($class) != 'parent') {
+ $used_classes[$class] = true;
+ }
+
+ continue 2;
+ }
+ }
+
+ return array(
+ "source_file" => $file,
+ "declared_classes" => $declared_classes,
+ "declared_interfaces" => $declared_interfaces,
+ "declared_methods" => $declared_methods,
+ "declared_functions" => $declared_functions,
+ "used_classes" => array_diff(array_keys($used_classes), $nodeps),
+ "inheritance" => $extends,
+ "implements" => $implements,
+ );
+ }
+
+ /**
+ * Build a "provides" array from data returned by
+ * analyzeSourceCode(). The format of the built array is like
+ * this:
+ *
+ * array(
+ * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'),
+ * ...
+ * )
+ *
+ *
+ * @param array $srcinfo array with information about a source file
+ * as returned by the analyzeSourceCode() method.
+ *
+ * @return void
+ *
+ * @access private
+ *
+ */
+ function _buildProvidesArray($srcinfo)
+ {
+ if (!$this->_isValid) {
+ return array();
+ }
+
+ $providesret = array();
+ $file = basename($srcinfo['source_file']);
+ $pn = isset($this->_pf) ? $this->_pf->getPackage() : '';
+ $pnl = strlen($pn);
+ foreach ($srcinfo['declared_classes'] as $class) {
+ $key = "class;$class";
+ if (isset($providesret[$key])) {
+ continue;
+ }
+
+ $providesret[$key] =
+ array('file'=> $file, 'type' => 'class', 'name' => $class);
+ if (isset($srcinfo['inheritance'][$class])) {
+ $providesret[$key]['extends'] =
+ $srcinfo['inheritance'][$class];
+ }
+ }
+
+ foreach ($srcinfo['declared_methods'] as $class => $methods) {
+ foreach ($methods as $method) {
+ $function = "$class::$method";
+ $key = "function;$function";
+ if ($method{0} == '_' || !strcasecmp($method, $class) ||
+ isset($providesret[$key])) {
+ continue;
+ }
+
+ $providesret[$key] =
+ array('file'=> $file, 'type' => 'function', 'name' => $function);
+ }
+ }
+
+ foreach ($srcinfo['declared_functions'] as $function) {
+ $key = "function;$function";
+ if ($function{0} == '_' || isset($providesret[$key])) {
+ continue;
+ }
+
+ if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) {
+ $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\"";
+ }
+
+ $providesret[$key] =
+ array('file'=> $file, 'type' => 'function', 'name' => $function);
+ }
+
+ return $providesret;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/PackageFile/v2/rw.php b/includes/pear/PEAR/PackageFile/v2/rw.php
new file mode 100644
index 0000000..4320d33
--- /dev/null
+++ b/includes/pear/PEAR/PackageFile/v2/rw.php
@@ -0,0 +1,1607 @@
+<?php
+/**
+ * PEAR_PackageFile_v2, package.xml version 2.0, read/write version
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a8
+ */
+/**
+ * For base class
+ */
+require_once 'PEAR/PackageFile/v2.php';
+/**
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a8
+ */
+class PEAR_PackageFile_v2_rw extends PEAR_PackageFile_v2
+{
+ /**
+ * @param string Extension name
+ * @return bool success of operation
+ */
+ function setProvidesExtension($extension)
+ {
+ if (in_array($this->getPackageType(),
+ array('extsrc', 'extbin', 'zendextsrc', 'zendextbin'))) {
+ if (!isset($this->_packageInfo['providesextension'])) {
+ // ensure that the channel tag is set up in the right location
+ $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+ array('usesrole', 'usestask', 'srcpackage', 'srcuri', 'phprelease',
+ 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'bundle', 'changelog'),
+ $extension, 'providesextension');
+ }
+ $this->_packageInfo['providesextension'] = $extension;
+ return true;
+ }
+ return false;
+ }
+
+ function setPackage($package)
+ {
+ $this->_isValid = 0;
+ if (!isset($this->_packageInfo['attribs'])) {
+ $this->_packageInfo = array_merge(array('attribs' => array(
+ 'version' => '2.0',
+ 'xmlns' => 'http://pear.php.net/dtd/package-2.0',
+ 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0',
+ 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
+ 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0
+ http://pear.php.net/dtd/tasks-1.0.xsd
+ http://pear.php.net/dtd/package-2.0
+ http://pear.php.net/dtd/package-2.0.xsd',
+ )), $this->_packageInfo);
+ }
+ if (!isset($this->_packageInfo['name'])) {
+ return $this->_packageInfo = array_merge(array('name' => $package),
+ $this->_packageInfo);
+ }
+ $this->_packageInfo['name'] = $package;
+ }
+
+ /**
+ * set this as a package.xml version 2.1
+ * @access private
+ */
+ function _setPackageVersion2_1()
+ {
+ $info = array(
+ 'version' => '2.1',
+ 'xmlns' => 'http://pear.php.net/dtd/package-2.1',
+ 'xmlns:tasks' => 'http://pear.php.net/dtd/tasks-1.0',
+ 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
+ 'xsi:schemaLocation' => 'http://pear.php.net/dtd/tasks-1.0
+ http://pear.php.net/dtd/tasks-1.0.xsd
+ http://pear.php.net/dtd/package-2.1
+ http://pear.php.net/dtd/package-2.1.xsd',
+ );
+ if (!isset($this->_packageInfo['attribs'])) {
+ $this->_packageInfo = array_merge(array('attribs' => $info), $this->_packageInfo);
+ } else {
+ $this->_packageInfo['attribs'] = $info;
+ }
+ }
+
+ function setUri($uri)
+ {
+ unset($this->_packageInfo['channel']);
+ $this->_isValid = 0;
+ if (!isset($this->_packageInfo['uri'])) {
+ // ensure that the uri tag is set up in the right location
+ $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+ array('extends', 'summary', 'description', 'lead',
+ 'developer', 'contributor', 'helper', 'date', 'time', 'version',
+ 'stability', 'license', 'notes', 'contents', 'compatible',
+ 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+ 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'extbinrelease', 'bundle', 'changelog'), $uri, 'uri');
+ }
+ $this->_packageInfo['uri'] = $uri;
+ }
+
+ function setChannel($channel)
+ {
+ unset($this->_packageInfo['uri']);
+ $this->_isValid = 0;
+ if (!isset($this->_packageInfo['channel'])) {
+ // ensure that the channel tag is set up in the right location
+ $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+ array('extends', 'summary', 'description', 'lead',
+ 'developer', 'contributor', 'helper', 'date', 'time', 'version',
+ 'stability', 'license', 'notes', 'contents', 'compatible',
+ 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+ 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'extbinrelease', 'bundle', 'changelog'), $channel, 'channel');
+ }
+ $this->_packageInfo['channel'] = $channel;
+ }
+
+ function setExtends($extends)
+ {
+ $this->_isValid = 0;
+ if (!isset($this->_packageInfo['extends'])) {
+ // ensure that the extends tag is set up in the right location
+ $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+ array('summary', 'description', 'lead',
+ 'developer', 'contributor', 'helper', 'date', 'time', 'version',
+ 'stability', 'license', 'notes', 'contents', 'compatible',
+ 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+ 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'extbinrelease', 'bundle', 'changelog'), $extends, 'extends');
+ }
+ $this->_packageInfo['extends'] = $extends;
+ }
+
+ function setSummary($summary)
+ {
+ $this->_isValid = 0;
+ if (!isset($this->_packageInfo['summary'])) {
+ // ensure that the summary tag is set up in the right location
+ $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+ array('description', 'lead',
+ 'developer', 'contributor', 'helper', 'date', 'time', 'version',
+ 'stability', 'license', 'notes', 'contents', 'compatible',
+ 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+ 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'extbinrelease', 'bundle', 'changelog'), $summary, 'summary');
+ }
+ $this->_packageInfo['summary'] = $summary;
+ }
+
+ function setDescription($desc)
+ {
+ $this->_isValid = 0;
+ if (!isset($this->_packageInfo['description'])) {
+ // ensure that the description tag is set up in the right location
+ $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+ array('lead',
+ 'developer', 'contributor', 'helper', 'date', 'time', 'version',
+ 'stability', 'license', 'notes', 'contents', 'compatible',
+ 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+ 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'extbinrelease', 'bundle', 'changelog'), $desc, 'description');
+ }
+ $this->_packageInfo['description'] = $desc;
+ }
+
+ /**
+ * Adds a new maintainer - no checking of duplicates is performed, use
+ * updatemaintainer for that purpose.
+ */
+ function addMaintainer($role, $handle, $name, $email, $active = 'yes')
+ {
+ if (!in_array($role, array('lead', 'developer', 'contributor', 'helper'))) {
+ return false;
+ }
+ if (isset($this->_packageInfo[$role])) {
+ if (!isset($this->_packageInfo[$role][0])) {
+ $this->_packageInfo[$role] = array($this->_packageInfo[$role]);
+ }
+ $this->_packageInfo[$role][] =
+ array(
+ 'name' => $name,
+ 'user' => $handle,
+ 'email' => $email,
+ 'active' => $active,
+ );
+ } else {
+ $testarr = array('lead',
+ 'developer', 'contributor', 'helper', 'date', 'time', 'version',
+ 'stability', 'license', 'notes', 'contents', 'compatible',
+ 'dependencies', 'providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease',
+ 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog');
+ foreach (array('lead', 'developer', 'contributor', 'helper') as $testrole) {
+ array_shift($testarr);
+ if ($role == $testrole) {
+ break;
+ }
+ }
+ if (!isset($this->_packageInfo[$role])) {
+ // ensure that the extends tag is set up in the right location
+ $this->_packageInfo = $this->_insertBefore($this->_packageInfo, $testarr,
+ array(), $role);
+ }
+ $this->_packageInfo[$role] =
+ array(
+ 'name' => $name,
+ 'user' => $handle,
+ 'email' => $email,
+ 'active' => $active,
+ );
+ }
+ $this->_isValid = 0;
+ }
+
+ function updateMaintainer($newrole, $handle, $name, $email, $active = 'yes')
+ {
+ $found = false;
+ foreach (array('lead', 'developer', 'contributor', 'helper') as $role) {
+ if (!isset($this->_packageInfo[$role])) {
+ continue;
+ }
+ $info = $this->_packageInfo[$role];
+ if (!isset($info[0])) {
+ if (isset($info['user']) && $info['user'] == $handle) {
+ $found = true;
+ break;
+ }
+ }
+ foreach ($info as $i => $maintainer) {
+ if (!isset($maintainer['user'])) {
+ continue;
+ }
+ if ($maintainer['user'] == $handle) {
+ $found = $i;
+ break 2;
+ }
+ }
+ }
+ if ($found === false) {
+ return $this->addMaintainer($newrole, $handle, $name, $email, $active);
+ }
+ if ($found !== false) {
+ if ($found === true) {
+ unset($this->_packageInfo[$role]);
+ } else {
+ unset($this->_packageInfo[$role][$found]);
+ $this->_packageInfo[$role] = array_values($this->_packageInfo[$role]);
+ }
+ }
+ $this->addMaintainer($newrole, $handle, $name, $email, $active);
+ $this->_isValid = 0;
+ }
+
+ function deleteMaintainer($handle)
+ {
+ $found = false;
+ foreach (array('lead', 'developer', 'contributor', 'helper') as $role) {
+ if (!isset($this->_packageInfo[$role])) {
+ continue;
+ }
+ if (!isset($this->_packageInfo[$role][0])) {
+ $this->_packageInfo[$role] = array($this->_packageInfo[$role]);
+ }
+ foreach ($this->_packageInfo[$role] as $i => $maintainer) {
+ if ($maintainer['user'] == $handle) {
+ $found = $i;
+ break;
+ }
+ }
+ if ($found !== false) {
+ unset($this->_packageInfo[$role][$found]);
+ if (!count($this->_packageInfo[$role]) && $role == 'lead') {
+ $this->_isValid = 0;
+ }
+ if (!count($this->_packageInfo[$role])) {
+ unset($this->_packageInfo[$role]);
+ return true;
+ }
+ $this->_packageInfo[$role] =
+ array_values($this->_packageInfo[$role]);
+ if (count($this->_packageInfo[$role]) == 1) {
+ $this->_packageInfo[$role] = $this->_packageInfo[$role][0];
+ }
+ return true;
+ }
+ if (count($this->_packageInfo[$role]) == 1) {
+ $this->_packageInfo[$role] = $this->_packageInfo[$role][0];
+ }
+ }
+ return false;
+ }
+
+ function setReleaseVersion($version)
+ {
+ if (isset($this->_packageInfo['version']) &&
+ isset($this->_packageInfo['version']['release'])) {
+ unset($this->_packageInfo['version']['release']);
+ }
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $version, array(
+ 'version' => array('stability', 'license', 'notes', 'contents', 'compatible',
+ 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+ 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'extbinrelease', 'bundle', 'changelog'),
+ 'release' => array('api')));
+ $this->_isValid = 0;
+ }
+
+ function setAPIVersion($version)
+ {
+ if (isset($this->_packageInfo['version']) &&
+ isset($this->_packageInfo['version']['api'])) {
+ unset($this->_packageInfo['version']['api']);
+ }
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $version, array(
+ 'version' => array('stability', 'license', 'notes', 'contents', 'compatible',
+ 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+ 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'extbinrelease', 'bundle', 'changelog'),
+ 'api' => array()));
+ $this->_isValid = 0;
+ }
+
+ /**
+ * snapshot|devel|alpha|beta|stable
+ */
+ function setReleaseStability($state)
+ {
+ if (isset($this->_packageInfo['stability']) &&
+ isset($this->_packageInfo['stability']['release'])) {
+ unset($this->_packageInfo['stability']['release']);
+ }
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $state, array(
+ 'stability' => array('license', 'notes', 'contents', 'compatible',
+ 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+ 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'extbinrelease', 'bundle', 'changelog'),
+ 'release' => array('api')));
+ $this->_isValid = 0;
+ }
+
+ /**
+ * @param devel|alpha|beta|stable
+ */
+ function setAPIStability($state)
+ {
+ if (isset($this->_packageInfo['stability']) &&
+ isset($this->_packageInfo['stability']['api'])) {
+ unset($this->_packageInfo['stability']['api']);
+ }
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $state, array(
+ 'stability' => array('license', 'notes', 'contents', 'compatible',
+ 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+ 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'extbinrelease', 'bundle', 'changelog'),
+ 'api' => array()));
+ $this->_isValid = 0;
+ }
+
+ function setLicense($license, $uri = false, $filesource = false)
+ {
+ if (!isset($this->_packageInfo['license'])) {
+ // ensure that the license tag is set up in the right location
+ $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+ array('notes', 'contents', 'compatible',
+ 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+ 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'extbinrelease', 'bundle', 'changelog'), 0, 'license');
+ }
+ if ($uri || $filesource) {
+ $attribs = array();
+ if ($uri) {
+ $attribs['uri'] = $uri;
+ }
+ $uri = true; // for test below
+ if ($filesource) {
+ $attribs['filesource'] = $filesource;
+ }
+ }
+ $license = $uri ? array('attribs' => $attribs, '_content' => $license) : $license;
+ $this->_packageInfo['license'] = $license;
+ $this->_isValid = 0;
+ }
+
+ function setNotes($notes)
+ {
+ $this->_isValid = 0;
+ if (!isset($this->_packageInfo['notes'])) {
+ // ensure that the notes tag is set up in the right location
+ $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+ array('contents', 'compatible',
+ 'dependencies', 'providesextension', 'usesrole', 'usestask', 'srcpackage', 'srcuri',
+ 'phprelease', 'extsrcrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'extbinrelease', 'bundle', 'changelog'), $notes, 'notes');
+ }
+ $this->_packageInfo['notes'] = $notes;
+ }
+
+ /**
+ * This is only used at install-time, after all serialization
+ * is over.
+ * @param string file name
+ * @param string installed path
+ */
+ function setInstalledAs($file, $path)
+ {
+ if ($path) {
+ return $this->_packageInfo['filelist'][$file]['installed_as'] = $path;
+ }
+ unset($this->_packageInfo['filelist'][$file]['installed_as']);
+ }
+
+ /**
+ * This is only used at install-time, after all serialization
+ * is over.
+ */
+ function installedFile($file, $atts)
+ {
+ if (isset($this->_packageInfo['filelist'][$file])) {
+ $this->_packageInfo['filelist'][$file] =
+ array_merge($this->_packageInfo['filelist'][$file], $atts['attribs']);
+ } else {
+ $this->_packageInfo['filelist'][$file] = $atts['attribs'];
+ }
+ }
+
+ /**
+ * Reset the listing of package contents
+ * @param string base installation dir for the whole package, if any
+ */
+ function clearContents($baseinstall = false)
+ {
+ $this->_filesValid = false;
+ $this->_isValid = 0;
+ if (!isset($this->_packageInfo['contents'])) {
+ $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+ array('compatible',
+ 'dependencies', 'providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease',
+ 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'bundle', 'changelog'), array(), 'contents');
+ }
+ if ($this->getPackageType() != 'bundle') {
+ $this->_packageInfo['contents'] =
+ array('dir' => array('attribs' => array('name' => '/')));
+ if ($baseinstall) {
+ $this->_packageInfo['contents']['dir']['attribs']['baseinstalldir'] = $baseinstall;
+ }
+ } else {
+ $this->_packageInfo['contents'] = array('bundledpackage' => array());
+ }
+ }
+
+ /**
+ * @param string relative path of the bundled package.
+ */
+ function addBundledPackage($path)
+ {
+ if ($this->getPackageType() != 'bundle') {
+ return false;
+ }
+ $this->_filesValid = false;
+ $this->_isValid = 0;
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $path, array(
+ 'contents' => array('compatible', 'dependencies', 'providesextension',
+ 'usesrole', 'usestask', 'srcpackage', 'srcuri', 'phprelease',
+ 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'bundle', 'changelog'),
+ 'bundledpackage' => array()));
+ }
+
+ /**
+ * @param string file name
+ * @param PEAR_Task_Common a read/write task
+ */
+ function addTaskToFile($filename, $task)
+ {
+ if (!method_exists($task, 'getXml')) {
+ return false;
+ }
+ if (!method_exists($task, 'getName')) {
+ return false;
+ }
+ if (!method_exists($task, 'validate')) {
+ return false;
+ }
+ if (!$task->validate()) {
+ return false;
+ }
+ if (!isset($this->_packageInfo['contents']['dir']['file'])) {
+ return false;
+ }
+ $this->getTasksNs(); // discover the tasks namespace if not done already
+ $files = $this->_packageInfo['contents']['dir']['file'];
+ if (!isset($files[0])) {
+ $files = array($files);
+ $ind = false;
+ } else {
+ $ind = true;
+ }
+ foreach ($files as $i => $file) {
+ if (isset($file['attribs'])) {
+ if ($file['attribs']['name'] == $filename) {
+ if ($ind) {
+ $t = isset($this->_packageInfo['contents']['dir']['file'][$i]
+ ['attribs'][$this->_tasksNs .
+ ':' . $task->getName()]) ?
+ $this->_packageInfo['contents']['dir']['file'][$i]
+ ['attribs'][$this->_tasksNs .
+ ':' . $task->getName()] : false;
+ if ($t && !isset($t[0])) {
+ $this->_packageInfo['contents']['dir']['file'][$i]
+ [$this->_tasksNs . ':' . $task->getName()] = array($t);
+ }
+ $this->_packageInfo['contents']['dir']['file'][$i][$this->_tasksNs .
+ ':' . $task->getName()][] = $task->getXml();
+ } else {
+ $t = isset($this->_packageInfo['contents']['dir']['file']
+ ['attribs'][$this->_tasksNs .
+ ':' . $task->getName()]) ? $this->_packageInfo['contents']['dir']['file']
+ ['attribs'][$this->_tasksNs .
+ ':' . $task->getName()] : false;
+ if ($t && !isset($t[0])) {
+ $this->_packageInfo['contents']['dir']['file']
+ [$this->_tasksNs . ':' . $task->getName()] = array($t);
+ }
+ $this->_packageInfo['contents']['dir']['file'][$this->_tasksNs .
+ ':' . $task->getName()][] = $task->getXml();
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param string path to the file
+ * @param string filename
+ * @param array extra attributes
+ */
+ function addFile($dir, $file, $attrs)
+ {
+ if ($this->getPackageType() == 'bundle') {
+ return false;
+ }
+ $this->_filesValid = false;
+ $this->_isValid = 0;
+ $dir = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'), $dir);
+ if ($dir == '/' || $dir == '') {
+ $dir = '';
+ } else {
+ $dir .= '/';
+ }
+ $attrs['name'] = $dir . $file;
+ if (!isset($this->_packageInfo['contents'])) {
+ // ensure that the contents tag is set up
+ $this->_packageInfo = $this->_insertBefore($this->_packageInfo,
+ array('compatible', 'dependencies', 'providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease',
+ 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'bundle', 'changelog'), array(), 'contents');
+ }
+ if (isset($this->_packageInfo['contents']['dir']['file'])) {
+ if (!isset($this->_packageInfo['contents']['dir']['file'][0])) {
+ $this->_packageInfo['contents']['dir']['file'] =
+ array($this->_packageInfo['contents']['dir']['file']);
+ }
+ $this->_packageInfo['contents']['dir']['file'][]['attribs'] = $attrs;
+ } else {
+ $this->_packageInfo['contents']['dir']['file']['attribs'] = $attrs;
+ }
+ }
+
+ /**
+ * @param string Dependent package name
+ * @param string Dependent package's channel name
+ * @param string minimum version of specified package that this release is guaranteed to be
+ * compatible with
+ * @param string maximum version of specified package that this release is guaranteed to be
+ * compatible with
+ * @param string versions of specified package that this release is not compatible with
+ */
+ function addCompatiblePackage($name, $channel, $min, $max, $exclude = false)
+ {
+ $this->_isValid = 0;
+ $set = array(
+ 'name' => $name,
+ 'channel' => $channel,
+ 'min' => $min,
+ 'max' => $max,
+ );
+ if ($exclude) {
+ $set['exclude'] = $exclude;
+ }
+ $this->_isValid = 0;
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array(
+ 'compatible' => array('dependencies', 'providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog')
+ ));
+ }
+
+ /**
+ * Removes the <usesrole> tag entirely
+ */
+ function resetUsesrole()
+ {
+ if (isset($this->_packageInfo['usesrole'])) {
+ unset($this->_packageInfo['usesrole']);
+ }
+ }
+
+ /**
+ * @param string
+ * @param string package name or uri
+ * @param string channel name if non-uri
+ */
+ function addUsesrole($role, $packageOrUri, $channel = false) {
+ $set = array('role' => $role);
+ if ($channel) {
+ $set['package'] = $packageOrUri;
+ $set['channel'] = $channel;
+ } else {
+ $set['uri'] = $packageOrUri;
+ }
+ $this->_isValid = 0;
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array(
+ 'usesrole' => array('usestask', 'srcpackage', 'srcuri',
+ 'phprelease', 'extsrcrelease', 'extbinrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog')
+ ));
+ }
+
+ /**
+ * Removes the <usestask> tag entirely
+ */
+ function resetUsestask()
+ {
+ if (isset($this->_packageInfo['usestask'])) {
+ unset($this->_packageInfo['usestask']);
+ }
+ }
+
+
+ /**
+ * @param string
+ * @param string package name or uri
+ * @param string channel name if non-uri
+ */
+ function addUsestask($task, $packageOrUri, $channel = false) {
+ $set = array('task' => $task);
+ if ($channel) {
+ $set['package'] = $packageOrUri;
+ $set['channel'] = $channel;
+ } else {
+ $set['uri'] = $packageOrUri;
+ }
+ $this->_isValid = 0;
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $set, array(
+ 'usestask' => array('srcpackage', 'srcuri',
+ 'phprelease', 'extsrcrelease', 'extbinrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog')
+ ));
+ }
+
+ /**
+ * Remove all compatible tags
+ */
+ function clearCompatible()
+ {
+ unset($this->_packageInfo['compatible']);
+ }
+
+ /**
+ * Reset dependencies prior to adding new ones
+ */
+ function clearDeps()
+ {
+ if (!isset($this->_packageInfo['dependencies'])) {
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, array(),
+ array(
+ 'dependencies' => array('providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog')));
+ }
+ $this->_packageInfo['dependencies'] = array();
+ }
+
+ /**
+ * @param string minimum PHP version allowed
+ * @param string maximum PHP version allowed
+ * @param array $exclude incompatible PHP versions
+ */
+ function setPhpDep($min, $max = false, $exclude = false)
+ {
+ $this->_isValid = 0;
+ $dep =
+ array(
+ 'min' => $min,
+ );
+ if ($max) {
+ $dep['max'] = $max;
+ }
+ if ($exclude) {
+ if (count($exclude) == 1) {
+ $exclude = $exclude[0];
+ }
+ $dep['exclude'] = $exclude;
+ }
+ if (isset($this->_packageInfo['dependencies']['required']['php'])) {
+ $this->_stack->push(__FUNCTION__, 'warning', array('dep' =>
+ $this->_packageInfo['dependencies']['required']['php']),
+ 'warning: PHP dependency already exists, overwriting');
+ unset($this->_packageInfo['dependencies']['required']['php']);
+ }
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+ array(
+ 'dependencies' => array('providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+ 'required' => array('optional', 'group'),
+ 'php' => array('pearinstaller', 'package', 'subpackage', 'extension', 'os', 'arch')
+ ));
+ return true;
+ }
+
+ /**
+ * @param string minimum allowed PEAR installer version
+ * @param string maximum allowed PEAR installer version
+ * @param string recommended PEAR installer version
+ * @param array incompatible version of the PEAR installer
+ */
+ function setPearinstallerDep($min, $max = false, $recommended = false, $exclude = false)
+ {
+ $this->_isValid = 0;
+ $dep =
+ array(
+ 'min' => $min,
+ );
+ if ($max) {
+ $dep['max'] = $max;
+ }
+ if ($recommended) {
+ $dep['recommended'] = $recommended;
+ }
+ if ($exclude) {
+ if (count($exclude) == 1) {
+ $exclude = $exclude[0];
+ }
+ $dep['exclude'] = $exclude;
+ }
+ if (isset($this->_packageInfo['dependencies']['required']['pearinstaller'])) {
+ $this->_stack->push(__FUNCTION__, 'warning', array('dep' =>
+ $this->_packageInfo['dependencies']['required']['pearinstaller']),
+ 'warning: PEAR Installer dependency already exists, overwriting');
+ unset($this->_packageInfo['dependencies']['required']['pearinstaller']);
+ }
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+ array(
+ 'dependencies' => array('providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+ 'required' => array('optional', 'group'),
+ 'pearinstaller' => array('package', 'subpackage', 'extension', 'os', 'arch')
+ ));
+ }
+
+ /**
+ * Mark a package as conflicting with this package
+ * @param string package name
+ * @param string package channel
+ * @param string extension this package provides, if any
+ * @param string|false minimum version required
+ * @param string|false maximum version allowed
+ * @param array|false versions to exclude from installation
+ */
+ function addConflictingPackageDepWithChannel($name, $channel,
+ $providesextension = false, $min = false, $max = false, $exclude = false)
+ {
+ $this->_isValid = 0;
+ $dep = $this->_constructDep($name, $channel, false, $min, $max, false,
+ $exclude, $providesextension, false, true);
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+ array(
+ 'dependencies' => array('providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+ 'required' => array('optional', 'group'),
+ 'package' => array('subpackage', 'extension', 'os', 'arch')
+ ));
+ }
+
+ /**
+ * Mark a package as conflicting with this package
+ * @param string package name
+ * @param string package channel
+ * @param string extension this package provides, if any
+ */
+ function addConflictingPackageDepWithUri($name, $uri, $providesextension = false)
+ {
+ $this->_isValid = 0;
+ $dep =
+ array(
+ 'name' => $name,
+ 'uri' => $uri,
+ 'conflicts' => '',
+ );
+ if ($providesextension) {
+ $dep['providesextension'] = $providesextension;
+ }
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+ array(
+ 'dependencies' => array('providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+ 'required' => array('optional', 'group'),
+ 'package' => array('subpackage', 'extension', 'os', 'arch')
+ ));
+ }
+
+ function addDependencyGroup($name, $hint)
+ {
+ $this->_isValid = 0;
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo,
+ array('attribs' => array('name' => $name, 'hint' => $hint)),
+ array(
+ 'dependencies' => array('providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+ 'group' => array(),
+ ));
+ }
+
+ /**
+ * @param string package name
+ * @param string|false channel name, false if this is a uri
+ * @param string|false uri name, false if this is a channel
+ * @param string|false minimum version required
+ * @param string|false maximum version allowed
+ * @param string|false recommended installation version
+ * @param array|false versions to exclude from installation
+ * @param string extension this package provides, if any
+ * @param bool if true, tells the installer to ignore the default optional dependency group
+ * when installing this package
+ * @param bool if true, tells the installer to negate this dependency (conflicts)
+ * @return array
+ * @access private
+ */
+ function _constructDep($name, $channel, $uri, $min, $max, $recommended, $exclude,
+ $providesextension = false, $nodefault = false,
+ $conflicts = false)
+ {
+ $dep =
+ array(
+ 'name' => $name,
+ );
+ if ($channel) {
+ $dep['channel'] = $channel;
+ } elseif ($uri) {
+ $dep['uri'] = $uri;
+ }
+ if ($min) {
+ $dep['min'] = $min;
+ }
+ if ($max) {
+ $dep['max'] = $max;
+ }
+ if ($recommended) {
+ $dep['recommended'] = $recommended;
+ }
+ if ($exclude) {
+ if (is_array($exclude) && count($exclude) == 1) {
+ $exclude = $exclude[0];
+ }
+ $dep['exclude'] = $exclude;
+ }
+ if ($conflicts) {
+ $dep['conflicts'] = '';
+ }
+ if ($nodefault) {
+ $dep['nodefault'] = '';
+ }
+ if ($providesextension) {
+ $dep['providesextension'] = $providesextension;
+ }
+ return $dep;
+ }
+
+ /**
+ * @param package|subpackage
+ * @param string group name
+ * @param string package name
+ * @param string package channel
+ * @param string minimum version
+ * @param string maximum version
+ * @param string recommended version
+ * @param array|false optional excluded versions
+ * @param string extension this package provides, if any
+ * @param bool if true, tells the installer to ignore the default optional dependency group
+ * when installing this package
+ * @return bool false if the dependency group has not been initialized with
+ * {@link addDependencyGroup()}, or a subpackage is added with
+ * a providesextension
+ */
+ function addGroupPackageDepWithChannel($type, $groupname, $name, $channel, $min = false,
+ $max = false, $recommended = false, $exclude = false,
+ $providesextension = false, $nodefault = false)
+ {
+ if ($type == 'subpackage' && $providesextension) {
+ return false; // subpackages must be php packages
+ }
+ $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude,
+ $providesextension, $nodefault);
+ return $this->_addGroupDependency($type, $dep, $groupname);
+ }
+
+ /**
+ * @param package|subpackage
+ * @param string group name
+ * @param string package name
+ * @param string package uri
+ * @param string extension this package provides, if any
+ * @param bool if true, tells the installer to ignore the default optional dependency group
+ * when installing this package
+ * @return bool false if the dependency group has not been initialized with
+ * {@link addDependencyGroup()}
+ */
+ function addGroupPackageDepWithURI($type, $groupname, $name, $uri, $providesextension = false,
+ $nodefault = false)
+ {
+ if ($type == 'subpackage' && $providesextension) {
+ return false; // subpackages must be php packages
+ }
+ $dep = $this->_constructDep($name, false, $uri, false, false, false, false,
+ $providesextension, $nodefault);
+ return $this->_addGroupDependency($type, $dep, $groupname);
+ }
+
+ /**
+ * @param string group name (must be pre-existing)
+ * @param string extension name
+ * @param string minimum version allowed
+ * @param string maximum version allowed
+ * @param string recommended version
+ * @param array incompatible versions
+ */
+ function addGroupExtensionDep($groupname, $name, $min = false, $max = false,
+ $recommended = false, $exclude = false)
+ {
+ $this->_isValid = 0;
+ $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude);
+ return $this->_addGroupDependency('extension', $dep, $groupname);
+ }
+
+ /**
+ * @param package|subpackage|extension
+ * @param array dependency contents
+ * @param string name of the dependency group to add this to
+ * @return boolean
+ * @access private
+ */
+ function _addGroupDependency($type, $dep, $groupname)
+ {
+ $arr = array('subpackage', 'extension');
+ if ($type != 'package') {
+ array_shift($arr);
+ }
+ if ($type == 'extension') {
+ array_shift($arr);
+ }
+ if (!isset($this->_packageInfo['dependencies']['group'])) {
+ return false;
+ } else {
+ if (!isset($this->_packageInfo['dependencies']['group'][0])) {
+ if ($this->_packageInfo['dependencies']['group']['attribs']['name'] == $groupname) {
+ $this->_packageInfo['dependencies']['group'] = $this->_mergeTag(
+ $this->_packageInfo['dependencies']['group'], $dep,
+ array(
+ $type => $arr
+ ));
+ $this->_isValid = 0;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ foreach ($this->_packageInfo['dependencies']['group'] as $i => $group) {
+ if ($group['attribs']['name'] == $groupname) {
+ $this->_packageInfo['dependencies']['group'][$i] = $this->_mergeTag(
+ $this->_packageInfo['dependencies']['group'][$i], $dep,
+ array(
+ $type => $arr
+ ));
+ $this->_isValid = 0;
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+ }
+
+ /**
+ * @param optional|required
+ * @param string package name
+ * @param string package channel
+ * @param string minimum version
+ * @param string maximum version
+ * @param string recommended version
+ * @param string extension this package provides, if any
+ * @param bool if true, tells the installer to ignore the default optional dependency group
+ * when installing this package
+ * @param array|false optional excluded versions
+ */
+ function addPackageDepWithChannel($type, $name, $channel, $min = false, $max = false,
+ $recommended = false, $exclude = false,
+ $providesextension = false, $nodefault = false)
+ {
+ if (!in_array($type, array('optional', 'required'), true)) {
+ $type = 'required';
+ }
+ $this->_isValid = 0;
+ $arr = array('optional', 'group');
+ if ($type != 'required') {
+ array_shift($arr);
+ }
+ $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude,
+ $providesextension, $nodefault);
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+ array(
+ 'dependencies' => array('providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+ $type => $arr,
+ 'package' => array('subpackage', 'extension', 'os', 'arch')
+ ));
+ }
+
+ /**
+ * @param optional|required
+ * @param string name of the package
+ * @param string uri of the package
+ * @param string extension this package provides, if any
+ * @param bool if true, tells the installer to ignore the default optional dependency group
+ * when installing this package
+ */
+ function addPackageDepWithUri($type, $name, $uri, $providesextension = false,
+ $nodefault = false)
+ {
+ $this->_isValid = 0;
+ $arr = array('optional', 'group');
+ if ($type != 'required') {
+ array_shift($arr);
+ }
+ $dep = $this->_constructDep($name, false, $uri, false, false, false, false,
+ $providesextension, $nodefault);
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+ array(
+ 'dependencies' => array('providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+ $type => $arr,
+ 'package' => array('subpackage', 'extension', 'os', 'arch')
+ ));
+ }
+
+ /**
+ * @param optional|required optional, required
+ * @param string package name
+ * @param string package channel
+ * @param string minimum version
+ * @param string maximum version
+ * @param string recommended version
+ * @param array incompatible versions
+ * @param bool if true, tells the installer to ignore the default optional dependency group
+ * when installing this package
+ */
+ function addSubpackageDepWithChannel($type, $name, $channel, $min = false, $max = false,
+ $recommended = false, $exclude = false,
+ $nodefault = false)
+ {
+ $this->_isValid = 0;
+ $arr = array('optional', 'group');
+ if ($type != 'required') {
+ array_shift($arr);
+ }
+ $dep = $this->_constructDep($name, $channel, false, $min, $max, $recommended, $exclude,
+ $nodefault);
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+ array(
+ 'dependencies' => array('providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+ $type => $arr,
+ 'subpackage' => array('extension', 'os', 'arch')
+ ));
+ }
+
+ /**
+ * @param optional|required optional, required
+ * @param string package name
+ * @param string package uri for download
+ * @param bool if true, tells the installer to ignore the default optional dependency group
+ * when installing this package
+ */
+ function addSubpackageDepWithUri($type, $name, $uri, $nodefault = false)
+ {
+ $this->_isValid = 0;
+ $arr = array('optional', 'group');
+ if ($type != 'required') {
+ array_shift($arr);
+ }
+ $dep = $this->_constructDep($name, false, $uri, false, false, false, false, $nodefault);
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+ array(
+ 'dependencies' => array('providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+ $type => $arr,
+ 'subpackage' => array('extension', 'os', 'arch')
+ ));
+ }
+
+ /**
+ * @param optional|required optional, required
+ * @param string extension name
+ * @param string minimum version
+ * @param string maximum version
+ * @param string recommended version
+ * @param array incompatible versions
+ */
+ function addExtensionDep($type, $name, $min = false, $max = false, $recommended = false,
+ $exclude = false)
+ {
+ $this->_isValid = 0;
+ $arr = array('optional', 'group');
+ if ($type != 'required') {
+ array_shift($arr);
+ }
+ $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude);
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+ array(
+ 'dependencies' => array('providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+ $type => $arr,
+ 'extension' => array('os', 'arch')
+ ));
+ }
+
+ /**
+ * @param string Operating system name
+ * @param boolean true if this package cannot be installed on this OS
+ */
+ function addOsDep($name, $conflicts = false)
+ {
+ $this->_isValid = 0;
+ $dep = array('name' => $name);
+ if ($conflicts) {
+ $dep['conflicts'] = '';
+ }
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+ array(
+ 'dependencies' => array('providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+ 'required' => array('optional', 'group'),
+ 'os' => array('arch')
+ ));
+ }
+
+ /**
+ * @param string Architecture matching pattern
+ * @param boolean true if this package cannot be installed on this architecture
+ */
+ function addArchDep($pattern, $conflicts = false)
+ {
+ $this->_isValid = 0;
+ $dep = array('pattern' => $pattern);
+ if ($conflicts) {
+ $dep['conflicts'] = '';
+ }
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, $dep,
+ array(
+ 'dependencies' => array('providesextension', 'usesrole', 'usestask',
+ 'srcpackage', 'srcuri', 'phprelease', 'extsrcrelease', 'extbinrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle', 'changelog'),
+ 'required' => array('optional', 'group'),
+ 'arch' => array()
+ ));
+ }
+
+ /**
+ * Set the kind of package, and erase all release tags
+ *
+ * - a php package is a PEAR-style package
+ * - an extbin package is a PECL-style extension binary
+ * - an extsrc package is a PECL-style source for a binary
+ * - an zendextbin package is a PECL-style zend extension binary
+ * - an zendextsrc package is a PECL-style source for a zend extension binary
+ * - a bundle package is a collection of other pre-packaged packages
+ * @param php|extbin|extsrc|zendextsrc|zendextbin|bundle
+ * @return bool success
+ */
+ function setPackageType($type)
+ {
+ $this->_isValid = 0;
+ if (!in_array($type, array('php', 'extbin', 'extsrc', 'zendextsrc',
+ 'zendextbin', 'bundle'))) {
+ return false;
+ }
+
+ if (in_array($type, array('zendextsrc', 'zendextbin'))) {
+ $this->_setPackageVersion2_1();
+ }
+
+ if ($type != 'bundle') {
+ $type .= 'release';
+ }
+
+ foreach (array('phprelease', 'extbinrelease', 'extsrcrelease',
+ 'zendextsrcrelease', 'zendextbinrelease', 'bundle') as $test) {
+ unset($this->_packageInfo[$test]);
+ }
+
+ if (!isset($this->_packageInfo[$type])) {
+ // ensure that the release tag is set up
+ $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('changelog'),
+ array(), $type);
+ }
+
+ $this->_packageInfo[$type] = array();
+ return true;
+ }
+
+ /**
+ * @return bool true if package type is set up
+ */
+ function addRelease()
+ {
+ if ($type = $this->getPackageType()) {
+ if ($type != 'bundle') {
+ $type .= 'release';
+ }
+ $this->_packageInfo = $this->_mergeTag($this->_packageInfo, array(),
+ array($type => array('changelog')));
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the current release tag in order to add to it
+ * @param bool returns only releases that have installcondition if true
+ * @return array|null
+ */
+ function &_getCurrentRelease($strict = true)
+ {
+ if ($p = $this->getPackageType()) {
+ if ($strict) {
+ if ($p == 'extsrc' || $p == 'zendextsrc') {
+ $a = null;
+ return $a;
+ }
+ }
+ if ($p != 'bundle') {
+ $p .= 'release';
+ }
+ if (isset($this->_packageInfo[$p][0])) {
+ return $this->_packageInfo[$p][count($this->_packageInfo[$p]) - 1];
+ } else {
+ return $this->_packageInfo[$p];
+ }
+ } else {
+ $a = null;
+ return $a;
+ }
+ }
+
+ /**
+ * Add a file to the current release that should be installed under a different name
+ * @param string <contents> path to file
+ * @param string name the file should be installed as
+ */
+ function addInstallAs($path, $as)
+ {
+ $r = &$this->_getCurrentRelease();
+ if ($r === null) {
+ return false;
+ }
+ $this->_isValid = 0;
+ $r = $this->_mergeTag($r, array('attribs' => array('name' => $path, 'as' => $as)),
+ array(
+ 'filelist' => array(),
+ 'install' => array('ignore')
+ ));
+ }
+
+ /**
+ * Add a file to the current release that should be ignored
+ * @param string <contents> path to file
+ * @return bool success of operation
+ */
+ function addIgnore($path)
+ {
+ $r = &$this->_getCurrentRelease();
+ if ($r === null) {
+ return false;
+ }
+ $this->_isValid = 0;
+ $r = $this->_mergeTag($r, array('attribs' => array('name' => $path)),
+ array(
+ 'filelist' => array(),
+ 'ignore' => array()
+ ));
+ }
+
+ /**
+ * Add an extension binary package for this extension source code release
+ *
+ * Note that the package must be from the same channel as the extension source package
+ * @param string
+ */
+ function addBinarypackage($package)
+ {
+ if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') {
+ return false;
+ }
+ $r = &$this->_getCurrentRelease(false);
+ if ($r === null) {
+ return false;
+ }
+ $this->_isValid = 0;
+ $r = $this->_mergeTag($r, $package,
+ array(
+ 'binarypackage' => array('filelist'),
+ ));
+ }
+
+ /**
+ * Add a configureoption to an extension source package
+ * @param string
+ * @param string
+ * @param string
+ */
+ function addConfigureOption($name, $prompt, $default = null)
+ {
+ if ($this->getPackageType() != 'extsrc' && $this->getPackageType() != 'zendextsrc') {
+ return false;
+ }
+
+ $r = &$this->_getCurrentRelease(false);
+ if ($r === null) {
+ return false;
+ }
+
+ $opt = array('attribs' => array('name' => $name, 'prompt' => $prompt));
+ if ($default !== null) {
+ $opt['attribs']['default'] = $default;
+ }
+
+ $this->_isValid = 0;
+ $r = $this->_mergeTag($r, $opt,
+ array(
+ 'configureoption' => array('binarypackage', 'filelist'),
+ ));
+ }
+
+ /**
+ * Set an installation condition based on php version for the current release set
+ * @param string minimum version
+ * @param string maximum version
+ * @param false|array incompatible versions of PHP
+ */
+ function setPhpInstallCondition($min, $max, $exclude = false)
+ {
+ $r = &$this->_getCurrentRelease();
+ if ($r === null) {
+ return false;
+ }
+ $this->_isValid = 0;
+ if (isset($r['installconditions']['php'])) {
+ unset($r['installconditions']['php']);
+ }
+ $dep = array('min' => $min, 'max' => $max);
+ if ($exclude) {
+ if (is_array($exclude) && count($exclude) == 1) {
+ $exclude = $exclude[0];
+ }
+ $dep['exclude'] = $exclude;
+ }
+ if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') {
+ $r = $this->_mergeTag($r, $dep,
+ array(
+ 'installconditions' => array('configureoption', 'binarypackage',
+ 'filelist'),
+ 'php' => array('extension', 'os', 'arch')
+ ));
+ } else {
+ $r = $this->_mergeTag($r, $dep,
+ array(
+ 'installconditions' => array('filelist'),
+ 'php' => array('extension', 'os', 'arch')
+ ));
+ }
+ }
+
+ /**
+ * @param optional|required optional, required
+ * @param string extension name
+ * @param string minimum version
+ * @param string maximum version
+ * @param string recommended version
+ * @param array incompatible versions
+ */
+ function addExtensionInstallCondition($name, $min = false, $max = false, $recommended = false,
+ $exclude = false)
+ {
+ $r = &$this->_getCurrentRelease();
+ if ($r === null) {
+ return false;
+ }
+ $this->_isValid = 0;
+ $dep = $this->_constructDep($name, false, false, $min, $max, $recommended, $exclude);
+ if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') {
+ $r = $this->_mergeTag($r, $dep,
+ array(
+ 'installconditions' => array('configureoption', 'binarypackage',
+ 'filelist'),
+ 'extension' => array('os', 'arch')
+ ));
+ } else {
+ $r = $this->_mergeTag($r, $dep,
+ array(
+ 'installconditions' => array('filelist'),
+ 'extension' => array('os', 'arch')
+ ));
+ }
+ }
+
+ /**
+ * Set an installation condition based on operating system for the current release set
+ * @param string OS name
+ * @param bool whether this OS is incompatible with the current release
+ */
+ function setOsInstallCondition($name, $conflicts = false)
+ {
+ $r = &$this->_getCurrentRelease();
+ if ($r === null) {
+ return false;
+ }
+ $this->_isValid = 0;
+ if (isset($r['installconditions']['os'])) {
+ unset($r['installconditions']['os']);
+ }
+ $dep = array('name' => $name);
+ if ($conflicts) {
+ $dep['conflicts'] = '';
+ }
+ if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') {
+ $r = $this->_mergeTag($r, $dep,
+ array(
+ 'installconditions' => array('configureoption', 'binarypackage',
+ 'filelist'),
+ 'os' => array('arch')
+ ));
+ } else {
+ $r = $this->_mergeTag($r, $dep,
+ array(
+ 'installconditions' => array('filelist'),
+ 'os' => array('arch')
+ ));
+ }
+ }
+
+ /**
+ * Set an installation condition based on architecture for the current release set
+ * @param string architecture pattern
+ * @param bool whether this arch is incompatible with the current release
+ */
+ function setArchInstallCondition($pattern, $conflicts = false)
+ {
+ $r = &$this->_getCurrentRelease();
+ if ($r === null) {
+ return false;
+ }
+ $this->_isValid = 0;
+ if (isset($r['installconditions']['arch'])) {
+ unset($r['installconditions']['arch']);
+ }
+ $dep = array('pattern' => $pattern);
+ if ($conflicts) {
+ $dep['conflicts'] = '';
+ }
+ if ($this->getPackageType() == 'extsrc' || $this->getPackageType() == 'zendextsrc') {
+ $r = $this->_mergeTag($r, $dep,
+ array(
+ 'installconditions' => array('configureoption', 'binarypackage',
+ 'filelist'),
+ 'arch' => array()
+ ));
+ } else {
+ $r = $this->_mergeTag($r, $dep,
+ array(
+ 'installconditions' => array('filelist'),
+ 'arch' => array()
+ ));
+ }
+ }
+
+ /**
+ * For extension binary releases, this is used to specify either the
+ * static URI to a source package, or the package name and channel of the extsrc/zendextsrc
+ * package it is based on.
+ * @param string Package name, or full URI to source package (extsrc/zendextsrc type)
+ */
+ function setSourcePackage($packageOrUri)
+ {
+ $this->_isValid = 0;
+ if (isset($this->_packageInfo['channel'])) {
+ $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('phprelease',
+ 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'bundle', 'changelog'),
+ $packageOrUri, 'srcpackage');
+ } else {
+ $this->_packageInfo = $this->_insertBefore($this->_packageInfo, array('phprelease',
+ 'extsrcrelease', 'extbinrelease', 'zendextsrcrelease', 'zendextbinrelease',
+ 'bundle', 'changelog'), $packageOrUri, 'srcuri');
+ }
+ }
+
+ /**
+ * Generate a valid change log entry from the current package.xml
+ * @param string|false
+ */
+ function generateChangeLogEntry($notes = false)
+ {
+ return array(
+ 'version' =>
+ array(
+ 'release' => $this->getVersion('release'),
+ 'api' => $this->getVersion('api'),
+ ),
+ 'stability' =>
+ $this->getStability(),
+ 'date' => $this->getDate(),
+ 'license' => $this->getLicense(true),
+ 'notes' => $notes ? $notes : $this->getNotes()
+ );
+ }
+
+ /**
+ * @param string release version to set change log notes for
+ * @param array output of {@link generateChangeLogEntry()}
+ */
+ function setChangelogEntry($releaseversion, $contents)
+ {
+ if (!isset($this->_packageInfo['changelog'])) {
+ $this->_packageInfo['changelog']['release'] = $contents;
+ return;
+ }
+ if (!isset($this->_packageInfo['changelog']['release'][0])) {
+ if ($this->_packageInfo['changelog']['release']['version']['release'] == $releaseversion) {
+ $this->_packageInfo['changelog']['release'] = array(
+ $this->_packageInfo['changelog']['release']);
+ } else {
+ $this->_packageInfo['changelog']['release'] = array(
+ $this->_packageInfo['changelog']['release']);
+ return $this->_packageInfo['changelog']['release'][] = $contents;
+ }
+ }
+ foreach($this->_packageInfo['changelog']['release'] as $index => $changelog) {
+ if (isset($changelog['version']) &&
+ strnatcasecmp($changelog['version']['release'], $releaseversion) == 0) {
+ $curlog = $index;
+ }
+ }
+ if (isset($curlog)) {
+ $this->_packageInfo['changelog']['release'][$curlog] = $contents;
+ } else {
+ $this->_packageInfo['changelog']['release'][] = $contents;
+ }
+ }
+
+ /**
+ * Remove the changelog entirely
+ */
+ function clearChangeLog()
+ {
+ unset($this->_packageInfo['changelog']);
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Packager.php b/includes/pear/PEAR/Packager.php
new file mode 100644
index 0000000..057a714
--- /dev/null
+++ b/includes/pear/PEAR/Packager.php
@@ -0,0 +1,201 @@
+<?php
+/**
+ * PEAR_Packager for generating releases
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V. V. Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR/Common.php';
+require_once 'PEAR/PackageFile.php';
+require_once 'System.php';
+
+/**
+ * Administration class used to make a PEAR release tarball.
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 0.1
+ */
+class PEAR_Packager extends PEAR_Common
+{
+ /**
+ * @var PEAR_Registry
+ */
+ var $_registry;
+
+ function package($pkgfile = null, $compress = true, $pkg2 = null)
+ {
+ // {{{ validate supplied package.xml file
+ if (empty($pkgfile)) {
+ $pkgfile = 'package.xml';
+ }
+
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $pkg = &new PEAR_PackageFile($this->config, $this->debug);
+ $pf = &$pkg->fromPackageFile($pkgfile, PEAR_VALIDATE_NORMAL);
+ $main = &$pf;
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($pf)) {
+ if (is_array($pf->getUserInfo())) {
+ foreach ($pf->getUserInfo() as $error) {
+ $this->log(0, 'Error: ' . $error['message']);
+ }
+ }
+
+ $this->log(0, $pf->getMessage());
+ return $this->raiseError("Cannot package, errors in package file");
+ }
+
+ foreach ($pf->getValidationWarnings() as $warning) {
+ $this->log(1, 'Warning: ' . $warning['message']);
+ }
+
+ // }}}
+ if ($pkg2) {
+ $this->log(0, 'Attempting to process the second package file');
+ PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+ $pf2 = &$pkg->fromPackageFile($pkg2, PEAR_VALIDATE_NORMAL);
+ PEAR::staticPopErrorHandling();
+ if (PEAR::isError($pf2)) {
+ if (is_array($pf2->getUserInfo())) {
+ foreach ($pf2->getUserInfo() as $error) {
+ $this->log(0, 'Error: ' . $error['message']);
+ }
+ }
+ $this->log(0, $pf2->getMessage());
+ return $this->raiseError("Cannot package, errors in second package file");
+ }
+
+ foreach ($pf2->getValidationWarnings() as $warning) {
+ $this->log(1, 'Warning: ' . $warning['message']);
+ }
+
+ if ($pf2->getPackagexmlVersion() == '2.0' ||
+ $pf2->getPackagexmlVersion() == '2.1'
+ ) {
+ $main = &$pf2;
+ $other = &$pf;
+ } else {
+ $main = &$pf;
+ $other = &$pf2;
+ }
+
+ if ($main->getPackagexmlVersion() != '2.0' &&
+ $main->getPackagexmlVersion() != '2.1') {
+ return PEAR::raiseError('Error: cannot package two package.xml version 1.0, can ' .
+ 'only package together a package.xml 1.0 and package.xml 2.0');
+ }
+
+ if ($other->getPackagexmlVersion() != '1.0') {
+ return PEAR::raiseError('Error: cannot package two package.xml version 2.0, can ' .
+ 'only package together a package.xml 1.0 and package.xml 2.0');
+ }
+ }
+
+ $main->setLogger($this);
+ if (!$main->validate(PEAR_VALIDATE_PACKAGING)) {
+ foreach ($main->getValidationWarnings() as $warning) {
+ $this->log(0, 'Error: ' . $warning['message']);
+ }
+ return $this->raiseError("Cannot package, errors in package");
+ }
+
+ foreach ($main->getValidationWarnings() as $warning) {
+ $this->log(1, 'Warning: ' . $warning['message']);
+ }
+
+ if ($pkg2) {
+ $other->setLogger($this);
+ $a = false;
+ if (!$other->validate(PEAR_VALIDATE_NORMAL) || $a = !$main->isEquivalent($other)) {
+ foreach ($other->getValidationWarnings() as $warning) {
+ $this->log(0, 'Error: ' . $warning['message']);
+ }
+
+ foreach ($main->getValidationWarnings() as $warning) {
+ $this->log(0, 'Error: ' . $warning['message']);
+ }
+
+ if ($a) {
+ return $this->raiseError('The two package.xml files are not equivalent!');
+ }
+
+ return $this->raiseError("Cannot package, errors in package");
+ }
+
+ foreach ($other->getValidationWarnings() as $warning) {
+ $this->log(1, 'Warning: ' . $warning['message']);
+ }
+
+ $gen = &$main->getDefaultGenerator();
+ $tgzfile = $gen->toTgz2($this, $other, $compress);
+ if (PEAR::isError($tgzfile)) {
+ return $tgzfile;
+ }
+
+ $dest_package = basename($tgzfile);
+ $pkgdir = dirname($pkgfile);
+
+ // TAR the Package -------------------------------------------
+ $this->log(1, "Package $dest_package done");
+ if (file_exists("$pkgdir/CVS/Root")) {
+ $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $pf->getVersion());
+ $cvstag = "RELEASE_$cvsversion";
+ $this->log(1, 'Tag the released code with "pear cvstag ' .
+ $main->getPackageFile() . '"');
+ $this->log(1, "(or set the CVS tag $cvstag by hand)");
+ } elseif (file_exists("$pkgdir/.svn")) {
+ $svnversion = preg_replace('/[^a-z0-9]/i', '.', $pf->getVersion());
+ $svntag = $pf->getName() . "-$svnversion";
+ $this->log(1, 'Tag the released code with "pear svntag ' .
+ $main->getPackageFile() . '"');
+ $this->log(1, "(or set the SVN tag $svntag by hand)");
+ }
+ } else { // this branch is executed for single packagefile packaging
+ $gen = &$pf->getDefaultGenerator();
+ $tgzfile = $gen->toTgz($this, $compress);
+ if (PEAR::isError($tgzfile)) {
+ $this->log(0, $tgzfile->getMessage());
+ return $this->raiseError("Cannot package, errors in package");
+ }
+
+ $dest_package = basename($tgzfile);
+ $pkgdir = dirname($pkgfile);
+
+ // TAR the Package -------------------------------------------
+ $this->log(1, "Package $dest_package done");
+ if (file_exists("$pkgdir/CVS/Root")) {
+ $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $pf->getVersion());
+ $cvstag = "RELEASE_$cvsversion";
+ $this->log(1, "Tag the released code with `pear cvstag $pkgfile'");
+ $this->log(1, "(or set the CVS tag $cvstag by hand)");
+ } elseif (file_exists("$pkgdir/.svn")) {
+ $svnversion = preg_replace('/[^a-z0-9]/i', '.', $pf->getVersion());
+ $svntag = $pf->getName() . "-$svnversion";
+ $this->log(1, "Tag the released code with `pear svntag $pkgfile'");
+ $this->log(1, "(or set the SVN tag $svntag by hand)");
+ }
+ }
+
+ return $dest_package;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/REST.php b/includes/pear/PEAR/REST.php
new file mode 100644
index 0000000..76a13cd
--- /dev/null
+++ b/includes/pear/PEAR/REST.php
@@ -0,0 +1,483 @@
+<?php
+/**
+ * PEAR_REST
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * For downloading xml files
+ */
+require_once 'PEAR.php';
+require_once 'PEAR/XMLParser.php';
+
+/**
+ * Intelligently retrieve data, following hyperlinks if necessary, and re-directing
+ * as well
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_REST
+{
+ var $config;
+ var $_options;
+
+ function PEAR_REST(&$config, $options = array())
+ {
+ $this->config = &$config;
+ $this->_options = $options;
+ }
+
+ /**
+ * Retrieve REST data, but always retrieve the local cache if it is available.
+ *
+ * This is useful for elements that should never change, such as information on a particular
+ * release
+ * @param string full URL to this resource
+ * @param array|false contents of the accept-encoding header
+ * @param boolean if true, xml will be returned as a string, otherwise, xml will be
+ * parsed using PEAR_XMLParser
+ * @return string|array
+ */
+ function retrieveCacheFirst($url, $accept = false, $forcestring = false, $channel = false)
+ {
+ $cachefile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR .
+ md5($url) . 'rest.cachefile';
+
+ if (file_exists($cachefile)) {
+ return unserialize(implode('', file($cachefile)));
+ }
+
+ return $this->retrieveData($url, $accept, $forcestring, $channel);
+ }
+
+ /**
+ * Retrieve a remote REST resource
+ * @param string full URL to this resource
+ * @param array|false contents of the accept-encoding header
+ * @param boolean if true, xml will be returned as a string, otherwise, xml will be
+ * parsed using PEAR_XMLParser
+ * @return string|array
+ */
+ function retrieveData($url, $accept = false, $forcestring = false, $channel = false)
+ {
+ $cacheId = $this->getCacheId($url);
+ if ($ret = $this->useLocalCache($url, $cacheId)) {
+ return $ret;
+ }
+
+ $file = $trieddownload = false;
+ if (!isset($this->_options['offline'])) {
+ $trieddownload = true;
+ $file = $this->downloadHttp($url, $cacheId ? $cacheId['lastChange'] : false, $accept, $channel);
+ }
+
+ if (PEAR::isError($file)) {
+ if ($file->getCode() !== -9276) {
+ return $file;
+ }
+
+ $trieddownload = false;
+ $file = false; // use local copy if available on socket connect error
+ }
+
+ if (!$file) {
+ $ret = $this->getCache($url);
+ if (!PEAR::isError($ret) && $trieddownload) {
+ // reset the age of the cache if the server says it was unmodified
+ $result = $this->saveCache($url, $ret, null, true, $cacheId);
+ if (PEAR::isError($result)) {
+ return PEAR::raiseError($result->getMessage());
+ }
+ }
+
+ return $ret;
+ }
+
+ if (is_array($file)) {
+ $headers = $file[2];
+ $lastmodified = $file[1];
+ $content = $file[0];
+ } else {
+ $headers = array();
+ $lastmodified = false;
+ $content = $file;
+ }
+
+ if ($forcestring) {
+ $result = $this->saveCache($url, $content, $lastmodified, false, $cacheId);
+ if (PEAR::isError($result)) {
+ return PEAR::raiseError($result->getMessage());
+ }
+
+ return $content;
+ }
+
+ if (isset($headers['content-type'])) {
+ switch ($headers['content-type']) {
+ case 'text/xml' :
+ case 'application/xml' :
+ case 'text/plain' :
+ if ($headers['content-type'] === 'text/plain') {
+ $check = substr($content, 0, 5);
+ if ($check !== '<?xml') {
+ break;
+ }
+ }
+
+ $parser = new PEAR_XMLParser;
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $err = $parser->parse($content);
+ PEAR::popErrorHandling();
+ if (PEAR::isError($err)) {
+ return PEAR::raiseError('Invalid xml downloaded from "' . $url . '": ' .
+ $err->getMessage());
+ }
+ $content = $parser->getData();
+ case 'text/html' :
+ default :
+ // use it as a string
+ }
+ } else {
+ // assume XML
+ $parser = new PEAR_XMLParser;
+ $parser->parse($content);
+ $content = $parser->getData();
+ }
+
+ $result = $this->saveCache($url, $content, $lastmodified, false, $cacheId);
+ if (PEAR::isError($result)) {
+ return PEAR::raiseError($result->getMessage());
+ }
+
+ return $content;
+ }
+
+ function useLocalCache($url, $cacheid = null)
+ {
+ if ($cacheid === null) {
+ $cacheidfile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR .
+ md5($url) . 'rest.cacheid';
+ if (!file_exists($cacheidfile)) {
+ return false;
+ }
+
+ $cacheid = unserialize(implode('', file($cacheidfile)));
+ }
+
+ $cachettl = $this->config->get('cache_ttl');
+ // If cache is newer than $cachettl seconds, we use the cache!
+ if (time() - $cacheid['age'] < $cachettl) {
+ return $this->getCache($url);
+ }
+
+ return false;
+ }
+
+ function getCacheId($url)
+ {
+ $cacheidfile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR .
+ md5($url) . 'rest.cacheid';
+
+ if (!file_exists($cacheidfile)) {
+ return false;
+ }
+
+ $ret = unserialize(implode('', file($cacheidfile)));
+ return $ret;
+ }
+
+ function getCache($url)
+ {
+ $cachefile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR .
+ md5($url) . 'rest.cachefile';
+
+ if (!file_exists($cachefile)) {
+ return PEAR::raiseError('No cached content available for "' . $url . '"');
+ }
+
+ return unserialize(implode('', file($cachefile)));
+ }
+
+ /**
+ * @param string full URL to REST resource
+ * @param string original contents of the REST resource
+ * @param array HTTP Last-Modified and ETag headers
+ * @param bool if true, then the cache id file should be regenerated to
+ * trigger a new time-to-live value
+ */
+ function saveCache($url, $contents, $lastmodified, $nochange = false, $cacheid = null)
+ {
+ $cache_dir = $this->config->get('cache_dir');
+ $d = $cache_dir . DIRECTORY_SEPARATOR . md5($url);
+ $cacheidfile = $d . 'rest.cacheid';
+ $cachefile = $d . 'rest.cachefile';
+
+ if (!is_dir($cache_dir)) {
+ if (System::mkdir(array('-p', $cache_dir)) === false) {
+ return PEAR::raiseError("The value of config option cache_dir ($cache_dir) is not a directory and attempts to create the directory failed.");
+ }
+ }
+
+ if ($cacheid === null && $nochange) {
+ $cacheid = unserialize(implode('', file($cacheidfile)));
+ }
+
+ $idData = serialize(array(
+ 'age' => time(),
+ 'lastChange' => ($nochange ? $cacheid['lastChange'] : $lastmodified),
+ ));
+
+ $result = $this->saveCacheFile($cacheidfile, $idData);
+ if (PEAR::isError($result)) {
+ return $result;
+ } elseif ($nochange) {
+ return true;
+ }
+
+ $result = $this->saveCacheFile($cachefile, serialize($contents));
+ if (PEAR::isError($result)) {
+ if (file_exists($cacheidfile)) {
+ @unlink($cacheidfile);
+ }
+
+ return $result;
+ }
+
+ return true;
+ }
+
+ function saveCacheFile($file, $contents)
+ {
+ $len = strlen($contents);
+
+ $cachefile_fp = @fopen($file, 'xb'); // x is the O_CREAT|O_EXCL mode
+ if ($cachefile_fp !== false) { // create file
+ if (fwrite($cachefile_fp, $contents, $len) < $len) {
+ fclose($cachefile_fp);
+ return PEAR::raiseError("Could not write $file.");
+ }
+ } else { // update file
+ $cachefile_lstat = lstat($file);
+ $cachefile_fp = @fopen($file, 'wb');
+ if (!$cachefile_fp) {
+ return PEAR::raiseError("Could not open $file for writing.");
+ }
+
+ $cachefile_fstat = fstat($cachefile_fp);
+ if (
+ $cachefile_lstat['mode'] == $cachefile_fstat['mode'] &&
+ $cachefile_lstat['ino'] == $cachefile_fstat['ino'] &&
+ $cachefile_lstat['dev'] == $cachefile_fstat['dev'] &&
+ $cachefile_fstat['nlink'] === 1
+ ) {
+ if (fwrite($cachefile_fp, $contents, $len) < $len) {
+ fclose($cachefile_fp);
+ return PEAR::raiseError("Could not write $file.");
+ }
+ } else {
+ fclose($cachefile_fp);
+ $link = function_exists('readlink') ? readlink($file) : $file;
+ return PEAR::raiseError('SECURITY ERROR: Will not write to ' . $file . ' as it is symlinked to ' . $link . ' - Possible symlink attack');
+ }
+ }
+
+ fclose($cachefile_fp);
+ return true;
+ }
+
+ /**
+ * Efficiently Download a file through HTTP. Returns downloaded file as a string in-memory
+ * This is best used for small files
+ *
+ * If an HTTP proxy has been configured (http_proxy PEAR_Config
+ * setting), the proxy will be used.
+ *
+ * @param string $url the URL to download
+ * @param string $save_dir directory to save file in
+ * @param false|string|array $lastmodified header values to check against for caching
+ * use false to return the header values from this download
+ * @param false|array $accept Accept headers to send
+ * @return string|array Returns the contents of the downloaded file or a PEAR
+ * error on failure. If the error is caused by
+ * socket-related errors, the error object will
+ * have the fsockopen error code available through
+ * getCode(). If caching is requested, then return the header
+ * values.
+ *
+ * @access public
+ */
+ function downloadHttp($url, $lastmodified = null, $accept = false, $channel = false)
+ {
+ static $redirect = 0;
+ // always reset , so we are clean case of error
+ $wasredirect = $redirect;
+ $redirect = 0;
+
+ $info = parse_url($url);
+ if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) {
+ return PEAR::raiseError('Cannot download non-http URL "' . $url . '"');
+ }
+
+ if (!isset($info['host'])) {
+ return PEAR::raiseError('Cannot download from non-URL "' . $url . '"');
+ }
+
+ $host = isset($info['host']) ? $info['host'] : null;
+ $port = isset($info['port']) ? $info['port'] : null;
+ $path = isset($info['path']) ? $info['path'] : null;
+ $schema = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http';
+
+ $proxy_host = $proxy_port = $proxy_user = $proxy_pass = '';
+ if ($this->config->get('http_proxy')&&
+ $proxy = parse_url($this->config->get('http_proxy'))
+ ) {
+ $proxy_host = isset($proxy['host']) ? $proxy['host'] : null;
+ if ($schema === 'https') {
+ $proxy_host = 'ssl://' . $proxy_host;
+ }
+
+ $proxy_port = isset($proxy['port']) ? $proxy['port'] : 8080;
+ $proxy_user = isset($proxy['user']) ? urldecode($proxy['user']) : null;
+ $proxy_pass = isset($proxy['pass']) ? urldecode($proxy['pass']) : null;
+ $proxy_schema = (isset($proxy['scheme']) && $proxy['scheme'] == 'https') ? 'https' : 'http';
+ }
+
+ if (empty($port)) {
+ $port = (isset($info['scheme']) && $info['scheme'] == 'https') ? 443 : 80;
+ }
+
+ if (isset($proxy['host'])) {
+ $request = "GET $url HTTP/1.1\r\n";
+ } else {
+ $request = "GET $path HTTP/1.1\r\n";
+ }
+
+ $request .= "Host: $host\r\n";
+ $ifmodifiedsince = '';
+ if (is_array($lastmodified)) {
+ if (isset($lastmodified['Last-Modified'])) {
+ $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n";
+ }
+
+ if (isset($lastmodified['ETag'])) {
+ $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n";
+ }
+ } else {
+ $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : '');
+ }
+
+ $request .= $ifmodifiedsince .
+ "User-Agent: PEAR/1.9.4/PHP/" . PHP_VERSION . "\r\n";
+
+ $username = $this->config->get('username', null, $channel);
+ $password = $this->config->get('password', null, $channel);
+
+ if ($username && $password) {
+ $tmp = base64_encode("$username:$password");
+ $request .= "Authorization: Basic $tmp\r\n";
+ }
+
+ if ($proxy_host != '' && $proxy_user != '') {
+ $request .= 'Proxy-Authorization: Basic ' .
+ base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n";
+ }
+
+ if ($accept) {
+ $request .= 'Accept: ' . implode(', ', $accept) . "\r\n";
+ }
+
+ $request .= "Accept-Encoding:\r\n";
+ $request .= "Connection: close\r\n";
+ $request .= "\r\n";
+
+ if ($proxy_host != '') {
+ $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr, 15);
+ if (!$fp) {
+ return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", -9276);
+ }
+ } else {
+ if ($schema === 'https') {
+ $host = 'ssl://' . $host;
+ }
+
+ $fp = @fsockopen($host, $port, $errno, $errstr);
+ if (!$fp) {
+ return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno);
+ }
+ }
+
+ fwrite($fp, $request);
+
+ $headers = array();
+ $reply = 0;
+ while ($line = trim(fgets($fp, 1024))) {
+ if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) {
+ $headers[strtolower($matches[1])] = trim($matches[2]);
+ } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) {
+ $reply = (int)$matches[1];
+ if ($reply == 304 && ($lastmodified || ($lastmodified === false))) {
+ return false;
+ }
+
+ if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) {
+ return PEAR::raiseError("File $schema://$host:$port$path not valid (received: $line)");
+ }
+ }
+ }
+
+ if ($reply != 200) {
+ if (!isset($headers['location'])) {
+ return PEAR::raiseError("File $schema://$host:$port$path not valid (redirected but no location)");
+ }
+
+ if ($wasredirect > 4) {
+ return PEAR::raiseError("File $schema://$host:$port$path not valid (redirection looped more than 5 times)");
+ }
+
+ $redirect = $wasredirect + 1;
+ return $this->downloadHttp($headers['location'], $lastmodified, $accept, $channel);
+ }
+
+ $length = isset($headers['content-length']) ? $headers['content-length'] : -1;
+
+ $data = '';
+ while ($chunk = @fread($fp, 8192)) {
+ $data .= $chunk;
+ }
+ fclose($fp);
+
+ if ($lastmodified === false || $lastmodified) {
+ if (isset($headers['etag'])) {
+ $lastmodified = array('ETag' => $headers['etag']);
+ }
+
+ if (isset($headers['last-modified'])) {
+ if (is_array($lastmodified)) {
+ $lastmodified['Last-Modified'] = $headers['last-modified'];
+ } else {
+ $lastmodified = $headers['last-modified'];
+ }
+ }
+
+ return array($data, $lastmodified, $headers);
+ }
+
+ return $data;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/REST/10.php b/includes/pear/PEAR/REST/10.php
new file mode 100644
index 0000000..7540efa
--- /dev/null
+++ b/includes/pear/PEAR/REST/10.php
@@ -0,0 +1,871 @@
+<?php
+/**
+ * PEAR_REST_10
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a12
+ */
+
+/**
+ * For downloading REST xml/txt files
+ */
+require_once 'PEAR/REST.php';
+
+/**
+ * Implement REST 1.0
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a12
+ */
+class PEAR_REST_10
+{
+ /**
+ * @var PEAR_REST
+ */
+ var $_rest;
+ function PEAR_REST_10($config, $options = array())
+ {
+ $this->_rest = &new PEAR_REST($config, $options);
+ }
+
+ /**
+ * Retrieve information about a remote package to be downloaded from a REST server
+ *
+ * @param string $base The uri to prepend to all REST calls
+ * @param array $packageinfo an array of format:
+ * <pre>
+ * array(
+ * 'package' => 'packagename',
+ * 'channel' => 'channelname',
+ * ['state' => 'alpha' (or valid state),]
+ * -or-
+ * ['version' => '1.whatever']
+ * </pre>
+ * @param string $prefstate Current preferred_state config variable value
+ * @param bool $installed the installed version of this package to compare against
+ * @return array|false|PEAR_Error see {@link _returnDownloadURL()}
+ */
+ function getDownloadURL($base, $packageinfo, $prefstate, $installed, $channel = false)
+ {
+ $states = $this->betterStates($prefstate, true);
+ if (!$states) {
+ return PEAR::raiseError('"' . $prefstate . '" is not a valid state');
+ }
+
+ $channel = $packageinfo['channel'];
+ $package = $packageinfo['package'];
+ $state = isset($packageinfo['state']) ? $packageinfo['state'] : null;
+ $version = isset($packageinfo['version']) ? $packageinfo['version'] : null;
+ $restFile = $base . 'r/' . strtolower($package) . '/allreleases.xml';
+
+ $info = $this->_rest->retrieveData($restFile, false, false, $channel);
+ if (PEAR::isError($info)) {
+ return PEAR::raiseError('No releases available for package "' .
+ $channel . '/' . $package . '"');
+ }
+
+ if (!isset($info['r'])) {
+ return false;
+ }
+
+ $release = $found = false;
+ if (!is_array($info['r']) || !isset($info['r'][0])) {
+ $info['r'] = array($info['r']);
+ }
+
+ foreach ($info['r'] as $release) {
+ if (!isset($this->_rest->_options['force']) && ($installed &&
+ version_compare($release['v'], $installed, '<'))) {
+ continue;
+ }
+
+ if (isset($state)) {
+ // try our preferred state first
+ if ($release['s'] == $state) {
+ $found = true;
+ break;
+ }
+ // see if there is something newer and more stable
+ // bug #7221
+ if (in_array($release['s'], $this->betterStates($state), true)) {
+ $found = true;
+ break;
+ }
+ } elseif (isset($version)) {
+ if ($release['v'] == $version) {
+ $found = true;
+ break;
+ }
+ } else {
+ if (in_array($release['s'], $states)) {
+ $found = true;
+ break;
+ }
+ }
+ }
+
+ return $this->_returnDownloadURL($base, $package, $release, $info, $found, false, $channel);
+ }
+
+ function getDepDownloadURL($base, $xsdversion, $dependency, $deppackage,
+ $prefstate = 'stable', $installed = false, $channel = false)
+ {
+ $states = $this->betterStates($prefstate, true);
+ if (!$states) {
+ return PEAR::raiseError('"' . $prefstate . '" is not a valid state');
+ }
+
+ $channel = $dependency['channel'];
+ $package = $dependency['name'];
+ $state = isset($dependency['state']) ? $dependency['state'] : null;
+ $version = isset($dependency['version']) ? $dependency['version'] : null;
+ $restFile = $base . 'r/' . strtolower($package) . '/allreleases.xml';
+
+ $info = $this->_rest->retrieveData($restFile, false, false, $channel);
+ if (PEAR::isError($info)) {
+ return PEAR::raiseError('Package "' . $deppackage['channel'] . '/' . $deppackage['package']
+ . '" dependency "' . $channel . '/' . $package . '" has no releases');
+ }
+
+ if (!is_array($info) || !isset($info['r'])) {
+ return false;
+ }
+
+ $exclude = array();
+ $min = $max = $recommended = false;
+ if ($xsdversion == '1.0') {
+ switch ($dependency['rel']) {
+ case 'ge' :
+ $min = $dependency['version'];
+ break;
+ case 'gt' :
+ $min = $dependency['version'];
+ $exclude = array($dependency['version']);
+ break;
+ case 'eq' :
+ $recommended = $dependency['version'];
+ break;
+ case 'lt' :
+ $max = $dependency['version'];
+ $exclude = array($dependency['version']);
+ break;
+ case 'le' :
+ $max = $dependency['version'];
+ break;
+ case 'ne' :
+ $exclude = array($dependency['version']);
+ break;
+ }
+ } else {
+ $min = isset($dependency['min']) ? $dependency['min'] : false;
+ $max = isset($dependency['max']) ? $dependency['max'] : false;
+ $recommended = isset($dependency['recommended']) ?
+ $dependency['recommended'] : false;
+ if (isset($dependency['exclude'])) {
+ if (!isset($dependency['exclude'][0])) {
+ $exclude = array($dependency['exclude']);
+ }
+ }
+ }
+ $release = $found = false;
+ if (!is_array($info['r']) || !isset($info['r'][0])) {
+ $info['r'] = array($info['r']);
+ }
+ foreach ($info['r'] as $release) {
+ if (!isset($this->_rest->_options['force']) && ($installed &&
+ version_compare($release['v'], $installed, '<'))) {
+ continue;
+ }
+ if (in_array($release['v'], $exclude)) { // skip excluded versions
+ continue;
+ }
+ // allow newer releases to say "I'm OK with the dependent package"
+ if ($xsdversion == '2.0' && isset($release['co'])) {
+ if (!is_array($release['co']) || !isset($release['co'][0])) {
+ $release['co'] = array($release['co']);
+ }
+ foreach ($release['co'] as $entry) {
+ if (isset($entry['x']) && !is_array($entry['x'])) {
+ $entry['x'] = array($entry['x']);
+ } elseif (!isset($entry['x'])) {
+ $entry['x'] = array();
+ }
+ if ($entry['c'] == $deppackage['channel'] &&
+ strtolower($entry['p']) == strtolower($deppackage['package']) &&
+ version_compare($deppackage['version'], $entry['min'], '>=') &&
+ version_compare($deppackage['version'], $entry['max'], '<=') &&
+ !in_array($release['v'], $entry['x'])) {
+ $recommended = $release['v'];
+ break;
+ }
+ }
+ }
+ if ($recommended) {
+ if ($release['v'] != $recommended) { // if we want a specific
+ // version, then skip all others
+ continue;
+ } else {
+ if (!in_array($release['s'], $states)) {
+ // the stability is too low, but we must return the
+ // recommended version if possible
+ return $this->_returnDownloadURL($base, $package, $release, $info, true, false, $channel);
+ }
+ }
+ }
+ if ($min && version_compare($release['v'], $min, 'lt')) { // skip too old versions
+ continue;
+ }
+ if ($max && version_compare($release['v'], $max, 'gt')) { // skip too new versions
+ continue;
+ }
+ if ($installed && version_compare($release['v'], $installed, '<')) {
+ continue;
+ }
+ if (in_array($release['s'], $states)) { // if in the preferred state...
+ $found = true; // ... then use it
+ break;
+ }
+ }
+ return $this->_returnDownloadURL($base, $package, $release, $info, $found, false, $channel);
+ }
+
+ /**
+ * Take raw data and return the array needed for processing a download URL
+ *
+ * @param string $base REST base uri
+ * @param string $package Package name
+ * @param array $release an array of format array('v' => version, 's' => state)
+ * describing the release to download
+ * @param array $info list of all releases as defined by allreleases.xml
+ * @param bool|null $found determines whether the release was found or this is the next
+ * best alternative. If null, then versions were skipped because
+ * of PHP dependency
+ * @return array|PEAR_Error
+ * @access private
+ */
+ function _returnDownloadURL($base, $package, $release, $info, $found, $phpversion = false, $channel = false)
+ {
+ if (!$found) {
+ $release = $info['r'][0];
+ }
+
+ $packageLower = strtolower($package);
+ $pinfo = $this->_rest->retrieveCacheFirst($base . 'p/' . $packageLower . '/' .
+ 'info.xml', false, false, $channel);
+ if (PEAR::isError($pinfo)) {
+ return PEAR::raiseError('Package "' . $package .
+ '" does not have REST info xml available');
+ }
+
+ $releaseinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . $packageLower . '/' .
+ $release['v'] . '.xml', false, false, $channel);
+ if (PEAR::isError($releaseinfo)) {
+ return PEAR::raiseError('Package "' . $package . '" Version "' . $release['v'] .
+ '" does not have REST xml available');
+ }
+
+ $packagexml = $this->_rest->retrieveCacheFirst($base . 'r/' . $packageLower . '/' .
+ 'deps.' . $release['v'] . '.txt', false, true, $channel);
+ if (PEAR::isError($packagexml)) {
+ return PEAR::raiseError('Package "' . $package . '" Version "' . $release['v'] .
+ '" does not have REST dependency information available');
+ }
+
+ $packagexml = unserialize($packagexml);
+ if (!$packagexml) {
+ $packagexml = array();
+ }
+
+ $allinfo = $this->_rest->retrieveData($base . 'r/' . $packageLower .
+ '/allreleases.xml', false, false, $channel);
+ if (PEAR::isError($allinfo)) {
+ return $allinfo;
+ }
+
+ if (!is_array($allinfo['r']) || !isset($allinfo['r'][0])) {
+ $allinfo['r'] = array($allinfo['r']);
+ }
+
+ $compatible = false;
+ foreach ($allinfo['r'] as $release) {
+ if ($release['v'] != $releaseinfo['v']) {
+ continue;
+ }
+
+ if (!isset($release['co'])) {
+ break;
+ }
+
+ $compatible = array();
+ if (!is_array($release['co']) || !isset($release['co'][0])) {
+ $release['co'] = array($release['co']);
+ }
+
+ foreach ($release['co'] as $entry) {
+ $comp = array();
+ $comp['name'] = $entry['p'];
+ $comp['channel'] = $entry['c'];
+ $comp['min'] = $entry['min'];
+ $comp['max'] = $entry['max'];
+ if (isset($entry['x']) && !is_array($entry['x'])) {
+ $comp['exclude'] = $entry['x'];
+ }
+
+ $compatible[] = $comp;
+ }
+
+ if (count($compatible) == 1) {
+ $compatible = $compatible[0];
+ }
+
+ break;
+ }
+
+ $deprecated = false;
+ if (isset($pinfo['dc']) && isset($pinfo['dp'])) {
+ if (is_array($pinfo['dp'])) {
+ $deprecated = array('channel' => (string) $pinfo['dc'],
+ 'package' => trim($pinfo['dp']['_content']));
+ } else {
+ $deprecated = array('channel' => (string) $pinfo['dc'],
+ 'package' => trim($pinfo['dp']));
+ }
+ }
+
+ $return = array(
+ 'version' => $releaseinfo['v'],
+ 'info' => $packagexml,
+ 'package' => $releaseinfo['p']['_content'],
+ 'stability' => $releaseinfo['st'],
+ 'compatible' => $compatible,
+ 'deprecated' => $deprecated,
+ );
+
+ if ($found) {
+ $return['url'] = $releaseinfo['g'];
+ return $return;
+ }
+
+ $return['php'] = $phpversion;
+ return $return;
+ }
+
+ function listPackages($base, $channel = false)
+ {
+ $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel);
+ if (PEAR::isError($packagelist)) {
+ return $packagelist;
+ }
+
+ if (!is_array($packagelist) || !isset($packagelist['p'])) {
+ return array();
+ }
+
+ if (!is_array($packagelist['p'])) {
+ $packagelist['p'] = array($packagelist['p']);
+ }
+
+ return $packagelist['p'];
+ }
+
+ /**
+ * List all categories of a REST server
+ *
+ * @param string $base base URL of the server
+ * @return array of categorynames
+ */
+ function listCategories($base, $channel = false)
+ {
+ $categories = array();
+
+ // c/categories.xml does not exist;
+ // check for every package its category manually
+ // This is SLOOOWWWW : ///
+ $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel);
+ if (PEAR::isError($packagelist)) {
+ return $packagelist;
+ }
+
+ if (!is_array($packagelist) || !isset($packagelist['p'])) {
+ $ret = array();
+ return $ret;
+ }
+
+ if (!is_array($packagelist['p'])) {
+ $packagelist['p'] = array($packagelist['p']);
+ }
+
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ foreach ($packagelist['p'] as $package) {
+ $inf = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel);
+ if (PEAR::isError($inf)) {
+ PEAR::popErrorHandling();
+ return $inf;
+ }
+ $cat = $inf['ca']['_content'];
+ if (!isset($categories[$cat])) {
+ $categories[$cat] = $inf['ca'];
+ }
+ }
+
+ return array_values($categories);
+ }
+
+ /**
+ * List a category of a REST server
+ *
+ * @param string $base base URL of the server
+ * @param string $category name of the category
+ * @param boolean $info also download full package info
+ * @return array of packagenames
+ */
+ function listCategory($base, $category, $info = false, $channel = false)
+ {
+ // gives '404 Not Found' error when category doesn't exist
+ $packagelist = $this->_rest->retrieveData($base.'c/'.urlencode($category).'/packages.xml', false, false, $channel);
+ if (PEAR::isError($packagelist)) {
+ return $packagelist;
+ }
+
+ if (!is_array($packagelist) || !isset($packagelist['p'])) {
+ return array();
+ }
+
+ if (!is_array($packagelist['p']) ||
+ !isset($packagelist['p'][0])) { // only 1 pkg
+ $packagelist = array($packagelist['p']);
+ } else {
+ $packagelist = $packagelist['p'];
+ }
+
+ if ($info == true) {
+ // get individual package info
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ foreach ($packagelist as $i => $packageitem) {
+ $url = sprintf('%s'.'r/%s/latest.txt',
+ $base,
+ strtolower($packageitem['_content']));
+ $version = $this->_rest->retrieveData($url, false, false, $channel);
+ if (PEAR::isError($version)) {
+ break; // skipit
+ }
+ $url = sprintf('%s'.'r/%s/%s.xml',
+ $base,
+ strtolower($packageitem['_content']),
+ $version);
+ $info = $this->_rest->retrieveData($url, false, false, $channel);
+ if (PEAR::isError($info)) {
+ break; // skipit
+ }
+ $packagelist[$i]['info'] = $info;
+ }
+ PEAR::popErrorHandling();
+ }
+
+ return $packagelist;
+ }
+
+
+ function listAll($base, $dostable, $basic = true, $searchpackage = false, $searchsummary = false, $channel = false)
+ {
+ $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel);
+ if (PEAR::isError($packagelist)) {
+ return $packagelist;
+ }
+ if ($this->_rest->config->get('verbose') > 0) {
+ $ui = &PEAR_Frontend::singleton();
+ $ui->log('Retrieving data...0%', true);
+ }
+ $ret = array();
+ if (!is_array($packagelist) || !isset($packagelist['p'])) {
+ return $ret;
+ }
+ if (!is_array($packagelist['p'])) {
+ $packagelist['p'] = array($packagelist['p']);
+ }
+
+ // only search-packagename = quicksearch !
+ if ($searchpackage && (!$searchsummary || empty($searchpackage))) {
+ $newpackagelist = array();
+ foreach ($packagelist['p'] as $package) {
+ if (!empty($searchpackage) && stristr($package, $searchpackage) !== false) {
+ $newpackagelist[] = $package;
+ }
+ }
+ $packagelist['p'] = $newpackagelist;
+ }
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $next = .1;
+ foreach ($packagelist['p'] as $progress => $package) {
+ if ($this->_rest->config->get('verbose') > 0) {
+ if ($progress / count($packagelist['p']) >= $next) {
+ if ($next == .5) {
+ $ui->log('50%', false);
+ } else {
+ $ui->log('.', false);
+ }
+ $next += .1;
+ }
+ }
+
+ if ($basic) { // remote-list command
+ if ($dostable) {
+ $latest = $this->_rest->retrieveData($base . 'r/' . strtolower($package) .
+ '/stable.txt', false, false, $channel);
+ } else {
+ $latest = $this->_rest->retrieveData($base . 'r/' . strtolower($package) .
+ '/latest.txt', false, false, $channel);
+ }
+ if (PEAR::isError($latest)) {
+ $latest = false;
+ }
+ $info = array('stable' => $latest);
+ } else { // list-all command
+ $inf = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel);
+ if (PEAR::isError($inf)) {
+ PEAR::popErrorHandling();
+ return $inf;
+ }
+ if ($searchpackage) {
+ $found = (!empty($searchpackage) && stristr($package, $searchpackage) !== false);
+ if (!$found && !(isset($searchsummary) && !empty($searchsummary)
+ && (stristr($inf['s'], $searchsummary) !== false
+ || stristr($inf['d'], $searchsummary) !== false)))
+ {
+ continue;
+ };
+ }
+ $releases = $this->_rest->retrieveData($base . 'r/' . strtolower($package) .
+ '/allreleases.xml', false, false, $channel);
+ if (PEAR::isError($releases)) {
+ continue;
+ }
+ if (!isset($releases['r'][0])) {
+ $releases['r'] = array($releases['r']);
+ }
+ unset($latest);
+ unset($unstable);
+ unset($stable);
+ unset($state);
+ foreach ($releases['r'] as $release) {
+ if (!isset($latest)) {
+ if ($dostable && $release['s'] == 'stable') {
+ $latest = $release['v'];
+ $state = 'stable';
+ }
+ if (!$dostable) {
+ $latest = $release['v'];
+ $state = $release['s'];
+ }
+ }
+ if (!isset($stable) && $release['s'] == 'stable') {
+ $stable = $release['v'];
+ if (!isset($unstable)) {
+ $unstable = $stable;
+ }
+ }
+ if (!isset($unstable) && $release['s'] != 'stable') {
+ $latest = $unstable = $release['v'];
+ $state = $release['s'];
+ }
+ if (isset($latest) && !isset($state)) {
+ $state = $release['s'];
+ }
+ if (isset($latest) && isset($stable) && isset($unstable)) {
+ break;
+ }
+ }
+ $deps = array();
+ if (!isset($unstable)) {
+ $unstable = false;
+ $state = 'stable';
+ if (isset($stable)) {
+ $latest = $unstable = $stable;
+ }
+ } else {
+ $latest = $unstable;
+ }
+ if (!isset($latest)) {
+ $latest = false;
+ }
+ if ($latest) {
+ $d = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/deps.' .
+ $latest . '.txt', false, false, $channel);
+ if (!PEAR::isError($d)) {
+ $d = unserialize($d);
+ if ($d) {
+ if (isset($d['required'])) {
+ if (!class_exists('PEAR_PackageFile_v2')) {
+ require_once 'PEAR/PackageFile/v2.php';
+ }
+ if (!isset($pf)) {
+ $pf = new PEAR_PackageFile_v2;
+ }
+ $pf->setDeps($d);
+ $tdeps = $pf->getDeps();
+ } else {
+ $tdeps = $d;
+ }
+ foreach ($tdeps as $dep) {
+ if ($dep['type'] !== 'pkg') {
+ continue;
+ }
+ $deps[] = $dep;
+ }
+ }
+ }
+ }
+ if (!isset($stable)) {
+ $stable = '-n/a-';
+ }
+ if (!$searchpackage) {
+ $info = array('stable' => $latest, 'summary' => $inf['s'], 'description' =>
+ $inf['d'], 'deps' => $deps, 'category' => $inf['ca']['_content'],
+ 'unstable' => $unstable, 'state' => $state);
+ } else {
+ $info = array('stable' => $stable, 'summary' => $inf['s'], 'description' =>
+ $inf['d'], 'deps' => $deps, 'category' => $inf['ca']['_content'],
+ 'unstable' => $unstable, 'state' => $state);
+ }
+ }
+ $ret[$package] = $info;
+ }
+ PEAR::popErrorHandling();
+ return $ret;
+ }
+
+ function listLatestUpgrades($base, $pref_state, $installed, $channel, &$reg)
+ {
+ $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel);
+ if (PEAR::isError($packagelist)) {
+ return $packagelist;
+ }
+
+ $ret = array();
+ if (!is_array($packagelist) || !isset($packagelist['p'])) {
+ return $ret;
+ }
+
+ if (!is_array($packagelist['p'])) {
+ $packagelist['p'] = array($packagelist['p']);
+ }
+
+ foreach ($packagelist['p'] as $package) {
+ if (!isset($installed[strtolower($package)])) {
+ continue;
+ }
+
+ $inst_version = $reg->packageInfo($package, 'version', $channel);
+ $inst_state = $reg->packageInfo($package, 'release_state', $channel);
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $info = $this->_rest->retrieveData($base . 'r/' . strtolower($package) .
+ '/allreleases.xml', false, false, $channel);
+ PEAR::popErrorHandling();
+ if (PEAR::isError($info)) {
+ continue; // no remote releases
+ }
+
+ if (!isset($info['r'])) {
+ continue;
+ }
+
+ $release = $found = false;
+ if (!is_array($info['r']) || !isset($info['r'][0])) {
+ $info['r'] = array($info['r']);
+ }
+
+ // $info['r'] is sorted by version number
+ usort($info['r'], array($this, '_sortReleasesByVersionNumber'));
+ foreach ($info['r'] as $release) {
+ if ($inst_version && version_compare($release['v'], $inst_version, '<=')) {
+ // not newer than the one installed
+ break;
+ }
+
+ // new version > installed version
+ if (!$pref_state) {
+ // every state is a good state
+ $found = true;
+ break;
+ } else {
+ $new_state = $release['s'];
+ // if new state >= installed state: go
+ if (in_array($new_state, $this->betterStates($inst_state, true))) {
+ $found = true;
+ break;
+ } else {
+ // only allow to lower the state of package,
+ // if new state >= preferred state: go
+ if (in_array($new_state, $this->betterStates($pref_state, true))) {
+ $found = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!$found) {
+ continue;
+ }
+
+ $relinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/' .
+ $release['v'] . '.xml', false, false, $channel);
+ if (PEAR::isError($relinfo)) {
+ return $relinfo;
+ }
+
+ $ret[$package] = array(
+ 'version' => $release['v'],
+ 'state' => $release['s'],
+ 'filesize' => $relinfo['f'],
+ );
+ }
+
+ return $ret;
+ }
+
+ function packageInfo($base, $package, $channel = false)
+ {
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $pinfo = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel);
+ if (PEAR::isError($pinfo)) {
+ PEAR::popErrorHandling();
+ return PEAR::raiseError('Unknown package: "' . $package . '" in channel "' . $channel . '"' . "\n". 'Debug: ' .
+ $pinfo->getMessage());
+ }
+
+ $releases = array();
+ $allreleases = $this->_rest->retrieveData($base . 'r/' . strtolower($package) .
+ '/allreleases.xml', false, false, $channel);
+ if (!PEAR::isError($allreleases)) {
+ if (!class_exists('PEAR_PackageFile_v2')) {
+ require_once 'PEAR/PackageFile/v2.php';
+ }
+
+ if (!is_array($allreleases['r']) || !isset($allreleases['r'][0])) {
+ $allreleases['r'] = array($allreleases['r']);
+ }
+
+ $pf = new PEAR_PackageFile_v2;
+ foreach ($allreleases['r'] as $release) {
+ $ds = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/deps.' .
+ $release['v'] . '.txt', false, false, $channel);
+ if (PEAR::isError($ds)) {
+ continue;
+ }
+
+ if (!isset($latest)) {
+ $latest = $release['v'];
+ }
+
+ $pf->setDeps(unserialize($ds));
+ $ds = $pf->getDeps();
+ $info = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package)
+ . '/' . $release['v'] . '.xml', false, false, $channel);
+
+ if (PEAR::isError($info)) {
+ continue;
+ }
+
+ $releases[$release['v']] = array(
+ 'doneby' => $info['m'],
+ 'license' => $info['l'],
+ 'summary' => $info['s'],
+ 'description' => $info['d'],
+ 'releasedate' => $info['da'],
+ 'releasenotes' => $info['n'],
+ 'state' => $release['s'],
+ 'deps' => $ds ? $ds : array(),
+ );
+ }
+ } else {
+ $latest = '';
+ }
+
+ PEAR::popErrorHandling();
+ if (isset($pinfo['dc']) && isset($pinfo['dp'])) {
+ if (is_array($pinfo['dp'])) {
+ $deprecated = array('channel' => (string) $pinfo['dc'],
+ 'package' => trim($pinfo['dp']['_content']));
+ } else {
+ $deprecated = array('channel' => (string) $pinfo['dc'],
+ 'package' => trim($pinfo['dp']));
+ }
+ } else {
+ $deprecated = false;
+ }
+
+ if (!isset($latest)) {
+ $latest = '';
+ }
+
+ return array(
+ 'name' => $pinfo['n'],
+ 'channel' => $pinfo['c'],
+ 'category' => $pinfo['ca']['_content'],
+ 'stable' => $latest,
+ 'license' => $pinfo['l'],
+ 'summary' => $pinfo['s'],
+ 'description' => $pinfo['d'],
+ 'releases' => $releases,
+ 'deprecated' => $deprecated,
+ );
+ }
+
+ /**
+ * Return an array containing all of the states that are more stable than
+ * or equal to the passed in state
+ *
+ * @param string Release state
+ * @param boolean Determines whether to include $state in the list
+ * @return false|array False if $state is not a valid release state
+ */
+ function betterStates($state, $include = false)
+ {
+ static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable');
+ $i = array_search($state, $states);
+ if ($i === false) {
+ return false;
+ }
+
+ if ($include) {
+ $i--;
+ }
+
+ return array_slice($states, $i + 1);
+ }
+
+ /**
+ * Sort releases by version number
+ *
+ * @access private
+ */
+ function _sortReleasesByVersionNumber($a, $b)
+ {
+ if (version_compare($a['v'], $b['v'], '=')) {
+ return 0;
+ }
+
+ if (version_compare($a['v'], $b['v'], '>')) {
+ return -1;
+ }
+
+ if (version_compare($a['v'], $b['v'], '<')) {
+ return 1;
+ }
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/REST/11.php b/includes/pear/PEAR/REST/11.php
new file mode 100644
index 0000000..6d2fcd8
--- /dev/null
+++ b/includes/pear/PEAR/REST/11.php
@@ -0,0 +1,341 @@
+<?php
+/**
+ * PEAR_REST_11 - implement faster list-all/remote-list command
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.3
+ */
+
+/**
+ * For downloading REST xml/txt files
+ */
+require_once 'PEAR/REST.php';
+
+/**
+ * Implement REST 1.1
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.3
+ */
+class PEAR_REST_11
+{
+ /**
+ * @var PEAR_REST
+ */
+ var $_rest;
+
+ function PEAR_REST_11($config, $options = array())
+ {
+ $this->_rest = &new PEAR_REST($config, $options);
+ }
+
+ function listAll($base, $dostable, $basic = true, $searchpackage = false, $searchsummary = false, $channel = false)
+ {
+ $categorylist = $this->_rest->retrieveData($base . 'c/categories.xml', false, false, $channel);
+ if (PEAR::isError($categorylist)) {
+ return $categorylist;
+ }
+
+ $ret = array();
+ if (!is_array($categorylist['c']) || !isset($categorylist['c'][0])) {
+ $categorylist['c'] = array($categorylist['c']);
+ }
+
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+
+ foreach ($categorylist['c'] as $progress => $category) {
+ $category = $category['_content'];
+ $packagesinfo = $this->_rest->retrieveData($base .
+ 'c/' . urlencode($category) . '/packagesinfo.xml', false, false, $channel);
+
+ if (PEAR::isError($packagesinfo)) {
+ continue;
+ }
+
+ if (!is_array($packagesinfo) || !isset($packagesinfo['pi'])) {
+ continue;
+ }
+
+ if (!is_array($packagesinfo['pi']) || !isset($packagesinfo['pi'][0])) {
+ $packagesinfo['pi'] = array($packagesinfo['pi']);
+ }
+
+ foreach ($packagesinfo['pi'] as $packageinfo) {
+ if (empty($packageinfo)) {
+ continue;
+ }
+
+ $info = $packageinfo['p'];
+ $package = $info['n'];
+ $releases = isset($packageinfo['a']) ? $packageinfo['a'] : false;
+ unset($latest);
+ unset($unstable);
+ unset($stable);
+ unset($state);
+
+ if ($releases) {
+ if (!isset($releases['r'][0])) {
+ $releases['r'] = array($releases['r']);
+ }
+
+ foreach ($releases['r'] as $release) {
+ if (!isset($latest)) {
+ if ($dostable && $release['s'] == 'stable') {
+ $latest = $release['v'];
+ $state = 'stable';
+ }
+ if (!$dostable) {
+ $latest = $release['v'];
+ $state = $release['s'];
+ }
+ }
+
+ if (!isset($stable) && $release['s'] == 'stable') {
+ $stable = $release['v'];
+ if (!isset($unstable)) {
+ $unstable = $stable;
+ }
+ }
+
+ if (!isset($unstable) && $release['s'] != 'stable') {
+ $unstable = $release['v'];
+ $state = $release['s'];
+ }
+
+ if (isset($latest) && !isset($state)) {
+ $state = $release['s'];
+ }
+
+ if (isset($latest) && isset($stable) && isset($unstable)) {
+ break;
+ }
+ }
+ }
+
+ if ($basic) { // remote-list command
+ if (!isset($latest)) {
+ $latest = false;
+ }
+
+ if ($dostable) {
+ // $state is not set if there are no releases
+ if (isset($state) && $state == 'stable') {
+ $ret[$package] = array('stable' => $latest);
+ } else {
+ $ret[$package] = array('stable' => '-n/a-');
+ }
+ } else {
+ $ret[$package] = array('stable' => $latest);
+ }
+
+ continue;
+ }
+
+ // list-all command
+ if (!isset($unstable)) {
+ $unstable = false;
+ $state = 'stable';
+ if (isset($stable)) {
+ $latest = $unstable = $stable;
+ }
+ } else {
+ $latest = $unstable;
+ }
+
+ if (!isset($latest)) {
+ $latest = false;
+ }
+
+ $deps = array();
+ if ($latest && isset($packageinfo['deps'])) {
+ if (!is_array($packageinfo['deps']) ||
+ !isset($packageinfo['deps'][0])
+ ) {
+ $packageinfo['deps'] = array($packageinfo['deps']);
+ }
+
+ $d = false;
+ foreach ($packageinfo['deps'] as $dep) {
+ if ($dep['v'] == $latest) {
+ $d = unserialize($dep['d']);
+ }
+ }
+
+ if ($d) {
+ if (isset($d['required'])) {
+ if (!class_exists('PEAR_PackageFile_v2')) {
+ require_once 'PEAR/PackageFile/v2.php';
+ }
+
+ if (!isset($pf)) {
+ $pf = new PEAR_PackageFile_v2;
+ }
+
+ $pf->setDeps($d);
+ $tdeps = $pf->getDeps();
+ } else {
+ $tdeps = $d;
+ }
+
+ foreach ($tdeps as $dep) {
+ if ($dep['type'] !== 'pkg') {
+ continue;
+ }
+
+ $deps[] = $dep;
+ }
+ }
+ }
+
+ $info = array(
+ 'stable' => $latest,
+ 'summary' => $info['s'],
+ 'description' => $info['d'],
+ 'deps' => $deps,
+ 'category' => $info['ca']['_content'],
+ 'unstable' => $unstable,
+ 'state' => $state
+ );
+ $ret[$package] = $info;
+ }
+ }
+
+ PEAR::popErrorHandling();
+ return $ret;
+ }
+
+ /**
+ * List all categories of a REST server
+ *
+ * @param string $base base URL of the server
+ * @return array of categorynames
+ */
+ function listCategories($base, $channel = false)
+ {
+ $categorylist = $this->_rest->retrieveData($base . 'c/categories.xml', false, false, $channel);
+ if (PEAR::isError($categorylist)) {
+ return $categorylist;
+ }
+
+ if (!is_array($categorylist) || !isset($categorylist['c'])) {
+ return array();
+ }
+
+ if (isset($categorylist['c']['_content'])) {
+ // only 1 category
+ $categorylist['c'] = array($categorylist['c']);
+ }
+
+ return $categorylist['c'];
+ }
+
+ /**
+ * List packages in a category of a REST server
+ *
+ * @param string $base base URL of the server
+ * @param string $category name of the category
+ * @param boolean $info also download full package info
+ * @return array of packagenames
+ */
+ function listCategory($base, $category, $info = false, $channel = false)
+ {
+ if ($info == false) {
+ $url = '%s'.'c/%s/packages.xml';
+ } else {
+ $url = '%s'.'c/%s/packagesinfo.xml';
+ }
+ $url = sprintf($url,
+ $base,
+ urlencode($category));
+
+ // gives '404 Not Found' error when category doesn't exist
+ $packagelist = $this->_rest->retrieveData($url, false, false, $channel);
+ if (PEAR::isError($packagelist)) {
+ return $packagelist;
+ }
+ if (!is_array($packagelist)) {
+ return array();
+ }
+
+ if ($info == false) {
+ if (!isset($packagelist['p'])) {
+ return array();
+ }
+ if (!is_array($packagelist['p']) ||
+ !isset($packagelist['p'][0])) { // only 1 pkg
+ $packagelist = array($packagelist['p']);
+ } else {
+ $packagelist = $packagelist['p'];
+ }
+ return $packagelist;
+ }
+
+ // info == true
+ if (!isset($packagelist['pi'])) {
+ return array();
+ }
+
+ if (!is_array($packagelist['pi']) ||
+ !isset($packagelist['pi'][0])) { // only 1 pkg
+ $packagelist_pre = array($packagelist['pi']);
+ } else {
+ $packagelist_pre = $packagelist['pi'];
+ }
+
+ $packagelist = array();
+ foreach ($packagelist_pre as $i => $item) {
+ // compatibility with r/<latest.txt>.xml
+ if (isset($item['a']['r'][0])) {
+ // multiple releases
+ $item['p']['v'] = $item['a']['r'][0]['v'];
+ $item['p']['st'] = $item['a']['r'][0]['s'];
+ } elseif (isset($item['a'])) {
+ // first and only release
+ $item['p']['v'] = $item['a']['r']['v'];
+ $item['p']['st'] = $item['a']['r']['s'];
+ }
+
+ $packagelist[$i] = array('attribs' => $item['p']['r'],
+ '_content' => $item['p']['n'],
+ 'info' => $item['p']);
+ }
+
+ return $packagelist;
+ }
+
+ /**
+ * Return an array containing all of the states that are more stable than
+ * or equal to the passed in state
+ *
+ * @param string Release state
+ * @param boolean Determines whether to include $state in the list
+ * @return false|array False if $state is not a valid release state
+ */
+ function betterStates($state, $include = false)
+ {
+ static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable');
+ $i = array_search($state, $states);
+ if ($i === false) {
+ return false;
+ }
+ if ($include) {
+ $i--;
+ }
+ return array_slice($states, $i + 1);
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/REST/13.php b/includes/pear/PEAR/REST/13.php
new file mode 100644
index 0000000..f38cb62
--- /dev/null
+++ b/includes/pear/PEAR/REST/13.php
@@ -0,0 +1,299 @@
+<?php
+/**
+ * PEAR_REST_13
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a12
+ */
+
+/**
+ * For downloading REST xml/txt files
+ */
+require_once 'PEAR/REST.php';
+require_once 'PEAR/REST/10.php';
+
+/**
+ * Implement REST 1.3
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a12
+ */
+class PEAR_REST_13 extends PEAR_REST_10
+{
+ /**
+ * Retrieve information about a remote package to be downloaded from a REST server
+ *
+ * This is smart enough to resolve the minimum PHP version dependency prior to download
+ * @param string $base The uri to prepend to all REST calls
+ * @param array $packageinfo an array of format:
+ * <pre>
+ * array(
+ * 'package' => 'packagename',
+ * 'channel' => 'channelname',
+ * ['state' => 'alpha' (or valid state),]
+ * -or-
+ * ['version' => '1.whatever']
+ * </pre>
+ * @param string $prefstate Current preferred_state config variable value
+ * @param bool $installed the installed version of this package to compare against
+ * @return array|false|PEAR_Error see {@link _returnDownloadURL()}
+ */
+ function getDownloadURL($base, $packageinfo, $prefstate, $installed, $channel = false)
+ {
+ $states = $this->betterStates($prefstate, true);
+ if (!$states) {
+ return PEAR::raiseError('"' . $prefstate . '" is not a valid state');
+ }
+
+ $channel = $packageinfo['channel'];
+ $package = $packageinfo['package'];
+ $state = isset($packageinfo['state']) ? $packageinfo['state'] : null;
+ $version = isset($packageinfo['version']) ? $packageinfo['version'] : null;
+ $restFile = $base . 'r/' . strtolower($package) . '/allreleases2.xml';
+
+ $info = $this->_rest->retrieveData($restFile, false, false, $channel);
+ if (PEAR::isError($info)) {
+ return PEAR::raiseError('No releases available for package "' .
+ $channel . '/' . $package . '"');
+ }
+
+ if (!isset($info['r'])) {
+ return false;
+ }
+
+ $release = $found = false;
+ if (!is_array($info['r']) || !isset($info['r'][0])) {
+ $info['r'] = array($info['r']);
+ }
+
+ $skippedphp = false;
+ foreach ($info['r'] as $release) {
+ if (!isset($this->_rest->_options['force']) && ($installed &&
+ version_compare($release['v'], $installed, '<'))) {
+ continue;
+ }
+
+ if (isset($state)) {
+ // try our preferred state first
+ if ($release['s'] == $state) {
+ if (!isset($version) && version_compare($release['m'], phpversion(), '>')) {
+ // skip releases that require a PHP version newer than our PHP version
+ $skippedphp = $release;
+ continue;
+ }
+ $found = true;
+ break;
+ }
+
+ // see if there is something newer and more stable
+ // bug #7221
+ if (in_array($release['s'], $this->betterStates($state), true)) {
+ if (!isset($version) && version_compare($release['m'], phpversion(), '>')) {
+ // skip releases that require a PHP version newer than our PHP version
+ $skippedphp = $release;
+ continue;
+ }
+ $found = true;
+ break;
+ }
+ } elseif (isset($version)) {
+ if ($release['v'] == $version) {
+ if (!isset($this->_rest->_options['force']) &&
+ !isset($version) &&
+ version_compare($release['m'], phpversion(), '>')) {
+ // skip releases that require a PHP version newer than our PHP version
+ $skippedphp = $release;
+ continue;
+ }
+ $found = true;
+ break;
+ }
+ } else {
+ if (in_array($release['s'], $states)) {
+ if (version_compare($release['m'], phpversion(), '>')) {
+ // skip releases that require a PHP version newer than our PHP version
+ $skippedphp = $release;
+ continue;
+ }
+ $found = true;
+ break;
+ }
+ }
+ }
+
+ if (!$found && $skippedphp) {
+ $found = null;
+ }
+
+ return $this->_returnDownloadURL($base, $package, $release, $info, $found, $skippedphp, $channel);
+ }
+
+ function getDepDownloadURL($base, $xsdversion, $dependency, $deppackage,
+ $prefstate = 'stable', $installed = false, $channel = false)
+ {
+ $states = $this->betterStates($prefstate, true);
+ if (!$states) {
+ return PEAR::raiseError('"' . $prefstate . '" is not a valid state');
+ }
+
+ $channel = $dependency['channel'];
+ $package = $dependency['name'];
+ $state = isset($dependency['state']) ? $dependency['state'] : null;
+ $version = isset($dependency['version']) ? $dependency['version'] : null;
+ $restFile = $base . 'r/' . strtolower($package) .'/allreleases2.xml';
+
+ $info = $this->_rest->retrieveData($restFile, false, false, $channel);
+ if (PEAR::isError($info)) {
+ return PEAR::raiseError('Package "' . $deppackage['channel'] . '/' . $deppackage['package']
+ . '" dependency "' . $channel . '/' . $package . '" has no releases');
+ }
+
+ if (!is_array($info) || !isset($info['r'])) {
+ return false;
+ }
+
+ $exclude = array();
+ $min = $max = $recommended = false;
+ if ($xsdversion == '1.0') {
+ $pinfo['package'] = $dependency['name'];
+ $pinfo['channel'] = 'pear.php.net'; // this is always true - don't change this
+ switch ($dependency['rel']) {
+ case 'ge' :
+ $min = $dependency['version'];
+ break;
+ case 'gt' :
+ $min = $dependency['version'];
+ $exclude = array($dependency['version']);
+ break;
+ case 'eq' :
+ $recommended = $dependency['version'];
+ break;
+ case 'lt' :
+ $max = $dependency['version'];
+ $exclude = array($dependency['version']);
+ break;
+ case 'le' :
+ $max = $dependency['version'];
+ break;
+ case 'ne' :
+ $exclude = array($dependency['version']);
+ break;
+ }
+ } else {
+ $pinfo['package'] = $dependency['name'];
+ $min = isset($dependency['min']) ? $dependency['min'] : false;
+ $max = isset($dependency['max']) ? $dependency['max'] : false;
+ $recommended = isset($dependency['recommended']) ?
+ $dependency['recommended'] : false;
+ if (isset($dependency['exclude'])) {
+ if (!isset($dependency['exclude'][0])) {
+ $exclude = array($dependency['exclude']);
+ }
+ }
+ }
+
+ $skippedphp = $found = $release = false;
+ if (!is_array($info['r']) || !isset($info['r'][0])) {
+ $info['r'] = array($info['r']);
+ }
+
+ foreach ($info['r'] as $release) {
+ if (!isset($this->_rest->_options['force']) && ($installed &&
+ version_compare($release['v'], $installed, '<'))) {
+ continue;
+ }
+
+ if (in_array($release['v'], $exclude)) { // skip excluded versions
+ continue;
+ }
+
+ // allow newer releases to say "I'm OK with the dependent package"
+ if ($xsdversion == '2.0' && isset($release['co'])) {
+ if (!is_array($release['co']) || !isset($release['co'][0])) {
+ $release['co'] = array($release['co']);
+ }
+
+ foreach ($release['co'] as $entry) {
+ if (isset($entry['x']) && !is_array($entry['x'])) {
+ $entry['x'] = array($entry['x']);
+ } elseif (!isset($entry['x'])) {
+ $entry['x'] = array();
+ }
+
+ if ($entry['c'] == $deppackage['channel'] &&
+ strtolower($entry['p']) == strtolower($deppackage['package']) &&
+ version_compare($deppackage['version'], $entry['min'], '>=') &&
+ version_compare($deppackage['version'], $entry['max'], '<=') &&
+ !in_array($release['v'], $entry['x'])) {
+ if (version_compare($release['m'], phpversion(), '>')) {
+ // skip dependency releases that require a PHP version
+ // newer than our PHP version
+ $skippedphp = $release;
+ continue;
+ }
+
+ $recommended = $release['v'];
+ break;
+ }
+ }
+ }
+
+ if ($recommended) {
+ if ($release['v'] != $recommended) { // if we want a specific
+ // version, then skip all others
+ continue;
+ }
+
+ if (!in_array($release['s'], $states)) {
+ // the stability is too low, but we must return the
+ // recommended version if possible
+ return $this->_returnDownloadURL($base, $package, $release, $info, true, false, $channel);
+ }
+ }
+
+ if ($min && version_compare($release['v'], $min, 'lt')) { // skip too old versions
+ continue;
+ }
+
+ if ($max && version_compare($release['v'], $max, 'gt')) { // skip too new versions
+ continue;
+ }
+
+ if ($installed && version_compare($release['v'], $installed, '<')) {
+ continue;
+ }
+
+ if (in_array($release['s'], $states)) { // if in the preferred state...
+ if (version_compare($release['m'], phpversion(), '>')) {
+ // skip dependency releases that require a PHP version
+ // newer than our PHP version
+ $skippedphp = $release;
+ continue;
+ }
+
+ $found = true; // ... then use it
+ break;
+ }
+ }
+
+ if (!$found && $skippedphp) {
+ $found = null;
+ }
+
+ return $this->_returnDownloadURL($base, $package, $release, $info, $found, $skippedphp, $channel);
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/REST/14.php b/includes/pear/PEAR/REST/14.php
new file mode 100644
index 0000000..3358a46
--- /dev/null
+++ b/includes/pear/PEAR/REST/14.php
@@ -0,0 +1,120 @@
+<?php
+/**
+ * PEAR_REST_14
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Helgi Þormar Þorbjörnsson <helgi@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: $
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.9
+ */
+
+/**
+ * For downloading REST xml/txt files
+ */
+require_once 'PEAR/REST.php';
+require_once 'PEAR/REST/13.php';
+
+/**
+ * Implement REST 1.4
+ *
+ * @category pear
+ * @package PEAR
+ * @author Helgi Þormar Þorbjörnsson <helgi@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.9
+ */
+class PEAR_REST_14 extends PEAR_REST_13
+{
+ function listLatestUpgrades($base, $pref_state, $installed, $channel, &$reg)
+ {
+ $packagelist = $this->_rest->retrieveData($base . 'p/latestpackages.xml', false, false, $channel);
+ if (PEAR::isError($packagelist)) {
+ return $packagelist;
+ }
+
+ $ret = array();
+ if (!is_array($packagelist) || !isset($packagelist['p'])) {
+ return $ret;
+ }
+
+ if (isset($packagelist['p']['n'])) {
+ $packagelist['p'] = array($packagelist['p']);
+ }
+
+ foreach ($packagelist['p'] as $package) {
+ if (!isset($installed[strtolower($package['n'])])) {
+ continue;
+ }
+
+ $inst_version = $reg->packageInfo($package['n'], 'version', $channel);
+ $inst_state = $reg->packageInfo($package['n'], 'release_state', $channel);
+
+
+ $release = $found = false;
+ $data = array();
+ if (isset($package['alpha'])) {
+ $data['alpha'] = $package['alpha'];
+ }
+
+ if (isset($package['beta'])) {
+ $data['beta'] = $package['beta'];
+ }
+
+ if (isset($package['stable'])) {
+ $data['stable'] = $package['stable'];
+ }
+
+ foreach ($data as $state => $release) {
+ if ($inst_version && version_compare($release['v'], $inst_version, '<=')) {
+ // not newer than the one installed
+ break;
+ }
+
+ // new version > installed version
+ if (!$pref_state) {
+ // every state is a good state
+ $found = true;
+ $release['state'] = $state;
+ break;
+ } else {
+ $new_state = $state;
+ // if new state >= installed state: go
+ if (in_array($new_state, $this->betterStates($inst_state, true))) {
+ $found = true;
+ $release['state'] = $state;
+ break;
+ } else {
+ // only allow to lower the state of package,
+ // if new state >= preferred state: go
+ if (in_array($new_state, $this->betterStates($pref_state, true))) {
+ $found = true;
+ $release['state'] = $state;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!$found) {
+ continue;
+ }
+
+ $ret[$package] = array(
+ 'version' => $release['v'],
+ 'state' => $release['s'],
+ 'filesize' => $release['f'],
+ );
+ }
+
+ return $ret;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Registry.php b/includes/pear/PEAR/Registry.php
new file mode 100644
index 0000000..02168db
--- /dev/null
+++ b/includes/pear/PEAR/Registry.php
@@ -0,0 +1,2339 @@
+<?php
+/**
+ * PEAR_Registry
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V. V. Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**
+ * for PEAR_Error
+ */
+require_once 'PEAR.php';
+require_once 'PEAR/DependencyDB.php';
+
+define('PEAR_REGISTRY_ERROR_LOCK', -2);
+define('PEAR_REGISTRY_ERROR_FORMAT', -3);
+define('PEAR_REGISTRY_ERROR_FILE', -4);
+define('PEAR_REGISTRY_ERROR_CONFLICT', -5);
+define('PEAR_REGISTRY_ERROR_CHANNEL_FILE', -6);
+
+/**
+ * Administration class used to maintain the installed package database.
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V. V. Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Registry extends PEAR
+{
+ /**
+ * File containing all channel information.
+ * @var string
+ */
+ var $channels = '';
+
+ /** Directory where registry files are stored.
+ * @var string
+ */
+ var $statedir = '';
+
+ /** File where the file map is stored
+ * @var string
+ */
+ var $filemap = '';
+
+ /** Directory where registry files for channels are stored.
+ * @var string
+ */
+ var $channelsdir = '';
+
+ /** Name of file used for locking the registry
+ * @var string
+ */
+ var $lockfile = '';
+
+ /** File descriptor used during locking
+ * @var resource
+ */
+ var $lock_fp = null;
+
+ /** Mode used during locking
+ * @var int
+ */
+ var $lock_mode = 0; // XXX UNUSED
+
+ /** Cache of package information. Structure:
+ * array(
+ * 'package' => array('id' => ... ),
+ * ... )
+ * @var array
+ */
+ var $pkginfo_cache = array();
+
+ /** Cache of file map. Structure:
+ * array( '/path/to/file' => 'package', ... )
+ * @var array
+ */
+ var $filemap_cache = array();
+
+ /**
+ * @var false|PEAR_ChannelFile
+ */
+ var $_pearChannel;
+
+ /**
+ * @var false|PEAR_ChannelFile
+ */
+ var $_peclChannel;
+
+ /**
+ * @var false|PEAR_ChannelFile
+ */
+ var $_docChannel;
+
+ /**
+ * @var PEAR_DependencyDB
+ */
+ var $_dependencyDB;
+
+ /**
+ * @var PEAR_Config
+ */
+ var $_config;
+
+ /**
+ * PEAR_Registry constructor.
+ *
+ * @param string (optional) PEAR install directory (for .php files)
+ * @param PEAR_ChannelFile PEAR_ChannelFile object representing the PEAR channel, if
+ * default values are not desired. Only used the very first time a PEAR
+ * repository is initialized
+ * @param PEAR_ChannelFile PEAR_ChannelFile object representing the PECL channel, if
+ * default values are not desired. Only used the very first time a PEAR
+ * repository is initialized
+ *
+ * @access public
+ */
+ function PEAR_Registry($pear_install_dir = PEAR_INSTALL_DIR, $pear_channel = false,
+ $pecl_channel = false)
+ {
+ parent::PEAR();
+ $this->setInstallDir($pear_install_dir);
+ $this->_pearChannel = $pear_channel;
+ $this->_peclChannel = $pecl_channel;
+ $this->_config = false;
+ }
+
+ function setInstallDir($pear_install_dir = PEAR_INSTALL_DIR)
+ {
+ $ds = DIRECTORY_SEPARATOR;
+ $this->install_dir = $pear_install_dir;
+ $this->channelsdir = $pear_install_dir.$ds.'.channels';
+ $this->statedir = $pear_install_dir.$ds.'.registry';
+ $this->filemap = $pear_install_dir.$ds.'.filemap';
+ $this->lockfile = $pear_install_dir.$ds.'.lock';
+ }
+
+ function hasWriteAccess()
+ {
+ if (!file_exists($this->install_dir)) {
+ $dir = $this->install_dir;
+ while ($dir && $dir != '.') {
+ $olddir = $dir;
+ $dir = dirname($dir);
+ if ($dir != '.' && file_exists($dir)) {
+ if (is_writeable($dir)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ if ($dir == $olddir) { // this can happen in safe mode
+ return @is_writable($dir);
+ }
+ }
+
+ return false;
+ }
+
+ return is_writeable($this->install_dir);
+ }
+
+ function setConfig(&$config, $resetInstallDir = true)
+ {
+ $this->_config = &$config;
+ if ($resetInstallDir) {
+ $this->setInstallDir($config->get('php_dir'));
+ }
+ }
+
+ function _initializeChannelDirs()
+ {
+ static $running = false;
+ if (!$running) {
+ $running = true;
+ $ds = DIRECTORY_SEPARATOR;
+ if (!is_dir($this->channelsdir) ||
+ !file_exists($this->channelsdir . $ds . 'pear.php.net.reg') ||
+ !file_exists($this->channelsdir . $ds . 'pecl.php.net.reg') ||
+ !file_exists($this->channelsdir . $ds . 'doc.php.net.reg') ||
+ !file_exists($this->channelsdir . $ds . '__uri.reg')) {
+ if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) {
+ $pear_channel = $this->_pearChannel;
+ if (!is_a($pear_channel, 'PEAR_ChannelFile') || !$pear_channel->validate()) {
+ if (!class_exists('PEAR_ChannelFile')) {
+ require_once 'PEAR/ChannelFile.php';
+ }
+
+ $pear_channel = new PEAR_ChannelFile;
+ $pear_channel->setAlias('pear');
+ $pear_channel->setServer('pear.php.net');
+ $pear_channel->setSummary('PHP Extension and Application Repository');
+ $pear_channel->setDefaultPEARProtocols();
+ $pear_channel->setBaseURL('REST1.0', 'http://pear.php.net/rest/');
+ $pear_channel->setBaseURL('REST1.1', 'http://pear.php.net/rest/');
+ $pear_channel->setBaseURL('REST1.3', 'http://pear.php.net/rest/');
+ //$pear_channel->setBaseURL('REST1.4', 'http://pear.php.net/rest/');
+ } else {
+ $pear_channel->setServer('pear.php.net');
+ $pear_channel->setAlias('pear');
+ }
+
+ $pear_channel->validate();
+ $this->_addChannel($pear_channel);
+ }
+
+ if (!file_exists($this->channelsdir . $ds . 'pecl.php.net.reg')) {
+ $pecl_channel = $this->_peclChannel;
+ if (!is_a($pecl_channel, 'PEAR_ChannelFile') || !$pecl_channel->validate()) {
+ if (!class_exists('PEAR_ChannelFile')) {
+ require_once 'PEAR/ChannelFile.php';
+ }
+
+ $pecl_channel = new PEAR_ChannelFile;
+ $pecl_channel->setAlias('pecl');
+ $pecl_channel->setServer('pecl.php.net');
+ $pecl_channel->setSummary('PHP Extension Community Library');
+ $pecl_channel->setDefaultPEARProtocols();
+ $pecl_channel->setBaseURL('REST1.0', 'http://pecl.php.net/rest/');
+ $pecl_channel->setBaseURL('REST1.1', 'http://pecl.php.net/rest/');
+ $pecl_channel->setValidationPackage('PEAR_Validator_PECL', '1.0');
+ } else {
+ $pecl_channel->setServer('pecl.php.net');
+ $pecl_channel->setAlias('pecl');
+ }
+
+ $pecl_channel->validate();
+ $this->_addChannel($pecl_channel);
+ }
+
+ if (!file_exists($this->channelsdir . $ds . 'doc.php.net.reg')) {
+ $doc_channel = $this->_docChannel;
+ if (!is_a($doc_channel, 'PEAR_ChannelFile') || !$doc_channel->validate()) {
+ if (!class_exists('PEAR_ChannelFile')) {
+ require_once 'PEAR/ChannelFile.php';
+ }
+
+ $doc_channel = new PEAR_ChannelFile;
+ $doc_channel->setAlias('phpdocs');
+ $doc_channel->setServer('doc.php.net');
+ $doc_channel->setSummary('PHP Documentation Team');
+ $doc_channel->setDefaultPEARProtocols();
+ $doc_channel->setBaseURL('REST1.0', 'http://doc.php.net/rest/');
+ $doc_channel->setBaseURL('REST1.1', 'http://doc.php.net/rest/');
+ $doc_channel->setBaseURL('REST1.3', 'http://doc.php.net/rest/');
+ } else {
+ $doc_channel->setServer('doc.php.net');
+ $doc_channel->setAlias('doc');
+ }
+
+ $doc_channel->validate();
+ $this->_addChannel($doc_channel);
+ }
+
+ if (!file_exists($this->channelsdir . $ds . '__uri.reg')) {
+ if (!class_exists('PEAR_ChannelFile')) {
+ require_once 'PEAR/ChannelFile.php';
+ }
+
+ $private = new PEAR_ChannelFile;
+ $private->setName('__uri');
+ $private->setDefaultPEARProtocols();
+ $private->setBaseURL('REST1.0', '****');
+ $private->setSummary('Pseudo-channel for static packages');
+ $this->_addChannel($private);
+ }
+ $this->_rebuildFileMap();
+ }
+
+ $running = false;
+ }
+ }
+
+ function _initializeDirs()
+ {
+ $ds = DIRECTORY_SEPARATOR;
+ // XXX Compatibility code should be removed in the future
+ // rename all registry files if any to lowercase
+ if (!OS_WINDOWS && file_exists($this->statedir) && is_dir($this->statedir) &&
+ $handle = opendir($this->statedir)) {
+ $dest = $this->statedir . $ds;
+ while (false !== ($file = readdir($handle))) {
+ if (preg_match('/^.*[A-Z].*\.reg\\z/', $file)) {
+ rename($dest . $file, $dest . strtolower($file));
+ }
+ }
+ closedir($handle);
+ }
+
+ $this->_initializeChannelDirs();
+ if (!file_exists($this->filemap)) {
+ $this->_rebuildFileMap();
+ }
+ $this->_initializeDepDB();
+ }
+
+ function _initializeDepDB()
+ {
+ if (!isset($this->_dependencyDB)) {
+ static $initializing = false;
+ if (!$initializing) {
+ $initializing = true;
+ if (!$this->_config) { // never used?
+ $file = OS_WINDOWS ? 'pear.ini' : '.pearrc';
+ $this->_config = &new PEAR_Config($this->statedir . DIRECTORY_SEPARATOR .
+ $file);
+ $this->_config->setRegistry($this);
+ $this->_config->set('php_dir', $this->install_dir);
+ }
+
+ $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->_config);
+ if (PEAR::isError($this->_dependencyDB)) {
+ // attempt to recover by removing the dep db
+ if (file_exists($this->_config->get('php_dir', null, 'pear.php.net') .
+ DIRECTORY_SEPARATOR . '.depdb')) {
+ @unlink($this->_config->get('php_dir', null, 'pear.php.net') .
+ DIRECTORY_SEPARATOR . '.depdb');
+ }
+
+ $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->_config);
+ if (PEAR::isError($this->_dependencyDB)) {
+ echo $this->_dependencyDB->getMessage();
+ echo 'Unrecoverable error';
+ exit(1);
+ }
+ }
+
+ $initializing = false;
+ }
+ }
+ }
+
+ /**
+ * PEAR_Registry destructor. Makes sure no locks are forgotten.
+ *
+ * @access private
+ */
+ function _PEAR_Registry()
+ {
+ parent::_PEAR();
+ if (is_resource($this->lock_fp)) {
+ $this->_unlock();
+ }
+ }
+
+ /**
+ * Make sure the directory where we keep registry files exists.
+ *
+ * @return bool TRUE if directory exists, FALSE if it could not be
+ * created
+ *
+ * @access private
+ */
+ function _assertStateDir($channel = false)
+ {
+ if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') {
+ return $this->_assertChannelStateDir($channel);
+ }
+
+ static $init = false;
+ if (!file_exists($this->statedir)) {
+ if (!$this->hasWriteAccess()) {
+ return false;
+ }
+
+ require_once 'System.php';
+ if (!System::mkdir(array('-p', $this->statedir))) {
+ return $this->raiseError("could not create directory '{$this->statedir}'");
+ }
+ $init = true;
+ } elseif (!is_dir($this->statedir)) {
+ return $this->raiseError('Cannot create directory ' . $this->statedir . ', ' .
+ 'it already exists and is not a directory');
+ }
+
+ $ds = DIRECTORY_SEPARATOR;
+ if (!file_exists($this->channelsdir)) {
+ if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg') ||
+ !file_exists($this->channelsdir . $ds . 'pecl.php.net.reg') ||
+ !file_exists($this->channelsdir . $ds . 'doc.php.net.reg') ||
+ !file_exists($this->channelsdir . $ds . '__uri.reg')) {
+ $init = true;
+ }
+ } elseif (!is_dir($this->channelsdir)) {
+ return $this->raiseError('Cannot create directory ' . $this->channelsdir . ', ' .
+ 'it already exists and is not a directory');
+ }
+
+ if ($init) {
+ static $running = false;
+ if (!$running) {
+ $running = true;
+ $this->_initializeDirs();
+ $running = false;
+ $init = false;
+ }
+ } else {
+ $this->_initializeDepDB();
+ }
+
+ return true;
+ }
+
+ /**
+ * Make sure the directory where we keep registry files exists for a non-standard channel.
+ *
+ * @param string channel name
+ * @return bool TRUE if directory exists, FALSE if it could not be
+ * created
+ *
+ * @access private
+ */
+ function _assertChannelStateDir($channel)
+ {
+ $ds = DIRECTORY_SEPARATOR;
+ if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') {
+ if (!file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) {
+ $this->_initializeChannelDirs();
+ }
+ return $this->_assertStateDir($channel);
+ }
+
+ $channelDir = $this->_channelDirectoryName($channel);
+ if (!is_dir($this->channelsdir) ||
+ !file_exists($this->channelsdir . $ds . 'pear.php.net.reg')) {
+ $this->_initializeChannelDirs();
+ }
+
+ if (!file_exists($channelDir)) {
+ if (!$this->hasWriteAccess()) {
+ return false;
+ }
+
+ require_once 'System.php';
+ if (!System::mkdir(array('-p', $channelDir))) {
+ return $this->raiseError("could not create directory '" . $channelDir .
+ "'");
+ }
+ } elseif (!is_dir($channelDir)) {
+ return $this->raiseError("could not create directory '" . $channelDir .
+ "', already exists and is not a directory");
+ }
+
+ return true;
+ }
+
+ /**
+ * Make sure the directory where we keep registry files for channels exists
+ *
+ * @return bool TRUE if directory exists, FALSE if it could not be
+ * created
+ *
+ * @access private
+ */
+ function _assertChannelDir()
+ {
+ if (!file_exists($this->channelsdir)) {
+ if (!$this->hasWriteAccess()) {
+ return false;
+ }
+
+ require_once 'System.php';
+ if (!System::mkdir(array('-p', $this->channelsdir))) {
+ return $this->raiseError("could not create directory '{$this->channelsdir}'");
+ }
+ } elseif (!is_dir($this->channelsdir)) {
+ return $this->raiseError("could not create directory '{$this->channelsdir}" .
+ "', it already exists and is not a directory");
+ }
+
+ if (!file_exists($this->channelsdir . DIRECTORY_SEPARATOR . '.alias')) {
+ if (!$this->hasWriteAccess()) {
+ return false;
+ }
+
+ require_once 'System.php';
+ if (!System::mkdir(array('-p', $this->channelsdir . DIRECTORY_SEPARATOR . '.alias'))) {
+ return $this->raiseError("could not create directory '{$this->channelsdir}/.alias'");
+ }
+ } elseif (!is_dir($this->channelsdir . DIRECTORY_SEPARATOR . '.alias')) {
+ return $this->raiseError("could not create directory '{$this->channelsdir}" .
+ "/.alias', it already exists and is not a directory");
+ }
+
+ return true;
+ }
+
+ /**
+ * Get the name of the file where data for a given package is stored.
+ *
+ * @param string channel name, or false if this is a PEAR package
+ * @param string package name
+ *
+ * @return string registry file name
+ *
+ * @access public
+ */
+ function _packageFileName($package, $channel = false)
+ {
+ if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') {
+ return $this->_channelDirectoryName($channel) . DIRECTORY_SEPARATOR .
+ strtolower($package) . '.reg';
+ }
+
+ return $this->statedir . DIRECTORY_SEPARATOR . strtolower($package) . '.reg';
+ }
+
+ /**
+ * Get the name of the file where data for a given channel is stored.
+ * @param string channel name
+ * @return string registry file name
+ */
+ function _channelFileName($channel, $noaliases = false)
+ {
+ if (!$noaliases) {
+ if (file_exists($this->_getChannelAliasFileName($channel))) {
+ $channel = implode('', file($this->_getChannelAliasFileName($channel)));
+ }
+ }
+ return $this->channelsdir . DIRECTORY_SEPARATOR . str_replace('/', '_',
+ strtolower($channel)) . '.reg';
+ }
+
+ /**
+ * @param string
+ * @return string
+ */
+ function _getChannelAliasFileName($alias)
+ {
+ return $this->channelsdir . DIRECTORY_SEPARATOR . '.alias' .
+ DIRECTORY_SEPARATOR . str_replace('/', '_', strtolower($alias)) . '.txt';
+ }
+
+ /**
+ * Get the name of a channel from its alias
+ */
+ function _getChannelFromAlias($channel)
+ {
+ if (!$this->_channelExists($channel)) {
+ if ($channel == 'pear.php.net') {
+ return 'pear.php.net';
+ }
+
+ if ($channel == 'pecl.php.net') {
+ return 'pecl.php.net';
+ }
+
+ if ($channel == 'doc.php.net') {
+ return 'doc.php.net';
+ }
+
+ if ($channel == '__uri') {
+ return '__uri';
+ }
+
+ return false;
+ }
+
+ $channel = strtolower($channel);
+ if (file_exists($this->_getChannelAliasFileName($channel))) {
+ // translate an alias to an actual channel
+ return implode('', file($this->_getChannelAliasFileName($channel)));
+ }
+
+ return $channel;
+ }
+
+ /**
+ * Get the alias of a channel from its alias or its name
+ */
+ function _getAlias($channel)
+ {
+ if (!$this->_channelExists($channel)) {
+ if ($channel == 'pear.php.net') {
+ return 'pear';
+ }
+
+ if ($channel == 'pecl.php.net') {
+ return 'pecl';
+ }
+
+ if ($channel == 'doc.php.net') {
+ return 'phpdocs';
+ }
+
+ return false;
+ }
+
+ $channel = $this->_getChannel($channel);
+ if (PEAR::isError($channel)) {
+ return $channel;
+ }
+
+ return $channel->getAlias();
+ }
+
+ /**
+ * Get the name of the file where data for a given package is stored.
+ *
+ * @param string channel name, or false if this is a PEAR package
+ * @param string package name
+ *
+ * @return string registry file name
+ *
+ * @access public
+ */
+ function _channelDirectoryName($channel)
+ {
+ if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') {
+ return $this->statedir;
+ }
+
+ $ch = $this->_getChannelFromAlias($channel);
+ if (!$ch) {
+ $ch = $channel;
+ }
+
+ return $this->statedir . DIRECTORY_SEPARATOR . strtolower('.channel.' .
+ str_replace('/', '_', $ch));
+ }
+
+ function _openPackageFile($package, $mode, $channel = false)
+ {
+ if (!$this->_assertStateDir($channel)) {
+ return null;
+ }
+
+ if (!in_array($mode, array('r', 'rb')) && !$this->hasWriteAccess()) {
+ return null;
+ }
+
+ $file = $this->_packageFileName($package, $channel);
+ if (!file_exists($file) && $mode == 'r' || $mode == 'rb') {
+ return null;
+ }
+
+ $fp = @fopen($file, $mode);
+ if (!$fp) {
+ return null;
+ }
+
+ return $fp;
+ }
+
+ function _closePackageFile($fp)
+ {
+ fclose($fp);
+ }
+
+ function _openChannelFile($channel, $mode)
+ {
+ if (!$this->_assertChannelDir()) {
+ return null;
+ }
+
+ if (!in_array($mode, array('r', 'rb')) && !$this->hasWriteAccess()) {
+ return null;
+ }
+
+ $file = $this->_channelFileName($channel);
+ if (!file_exists($file) && $mode == 'r' || $mode == 'rb') {
+ return null;
+ }
+
+ $fp = @fopen($file, $mode);
+ if (!$fp) {
+ return null;
+ }
+
+ return $fp;
+ }
+
+ function _closeChannelFile($fp)
+ {
+ fclose($fp);
+ }
+
+ function _rebuildFileMap()
+ {
+ if (!class_exists('PEAR_Installer_Role')) {
+ require_once 'PEAR/Installer/Role.php';
+ }
+
+ $channels = $this->_listAllPackages();
+ $files = array();
+ foreach ($channels as $channel => $packages) {
+ foreach ($packages as $package) {
+ $version = $this->_packageInfo($package, 'version', $channel);
+ $filelist = $this->_packageInfo($package, 'filelist', $channel);
+ if (!is_array($filelist)) {
+ continue;
+ }
+
+ foreach ($filelist as $name => $attrs) {
+ if (isset($attrs['attribs'])) {
+ $attrs = $attrs['attribs'];
+ }
+
+ // it is possible for conflicting packages in different channels to
+ // conflict with data files/doc files
+ if ($name == 'dirtree') {
+ continue;
+ }
+
+ if (isset($attrs['role']) && !in_array($attrs['role'],
+ PEAR_Installer_Role::getInstallableRoles())) {
+ // these are not installed
+ continue;
+ }
+
+ if (isset($attrs['role']) && !in_array($attrs['role'],
+ PEAR_Installer_Role::getBaseinstallRoles())) {
+ $attrs['baseinstalldir'] = $package;
+ }
+
+ if (isset($attrs['baseinstalldir'])) {
+ $file = $attrs['baseinstalldir'].DIRECTORY_SEPARATOR.$name;
+ } else {
+ $file = $name;
+ }
+
+ $file = preg_replace(',^/+,', '', $file);
+ if ($channel != 'pear.php.net') {
+ if (!isset($files[$attrs['role']])) {
+ $files[$attrs['role']] = array();
+ }
+ $files[$attrs['role']][$file] = array(strtolower($channel),
+ strtolower($package));
+ } else {
+ if (!isset($files[$attrs['role']])) {
+ $files[$attrs['role']] = array();
+ }
+ $files[$attrs['role']][$file] = strtolower($package);
+ }
+ }
+ }
+ }
+
+
+ $this->_assertStateDir();
+ if (!$this->hasWriteAccess()) {
+ return false;
+ }
+
+ $fp = @fopen($this->filemap, 'wb');
+ if (!$fp) {
+ return false;
+ }
+
+ $this->filemap_cache = $files;
+ fwrite($fp, serialize($files));
+ fclose($fp);
+ return true;
+ }
+
+ function _readFileMap()
+ {
+ if (!file_exists($this->filemap)) {
+ return array();
+ }
+
+ $fp = @fopen($this->filemap, 'r');
+ if (!$fp) {
+ return $this->raiseError('PEAR_Registry: could not open filemap "' . $this->filemap . '"', PEAR_REGISTRY_ERROR_FILE, null, null, $php_errormsg);
+ }
+
+ clearstatcache();
+ $rt = get_magic_quotes_runtime();
+ set_magic_quotes_runtime(0);
+ $fsize = filesize($this->filemap);
+ fclose($fp);
+ $data = file_get_contents($this->filemap);
+ set_magic_quotes_runtime($rt);
+ $tmp = unserialize($data);
+ if (!$tmp && $fsize > 7) {
+ return $this->raiseError('PEAR_Registry: invalid filemap data', PEAR_REGISTRY_ERROR_FORMAT, null, null, $data);
+ }
+
+ $this->filemap_cache = $tmp;
+ return true;
+ }
+
+ /**
+ * Lock the registry.
+ *
+ * @param integer lock mode, one of LOCK_EX, LOCK_SH or LOCK_UN.
+ * See flock manual for more information.
+ *
+ * @return bool TRUE on success, FALSE if locking failed, or a
+ * PEAR error if some other error occurs (such as the
+ * lock file not being writable).
+ *
+ * @access private
+ */
+ function _lock($mode = LOCK_EX)
+ {
+ if (stristr(php_uname(), 'Windows 9')) {
+ return true;
+ }
+
+ if ($mode != LOCK_UN && is_resource($this->lock_fp)) {
+ // XXX does not check type of lock (LOCK_SH/LOCK_EX)
+ return true;
+ }
+
+ if (!$this->_assertStateDir()) {
+ if ($mode == LOCK_EX) {
+ return $this->raiseError('Registry directory is not writeable by the current user');
+ }
+
+ return true;
+ }
+
+ $open_mode = 'w';
+ // XXX People reported problems with LOCK_SH and 'w'
+ if ($mode === LOCK_SH || $mode === LOCK_UN) {
+ if (!file_exists($this->lockfile)) {
+ touch($this->lockfile);
+ }
+ $open_mode = 'r';
+ }
+
+ if (!is_resource($this->lock_fp)) {
+ $this->lock_fp = @fopen($this->lockfile, $open_mode);
+ }
+
+ if (!is_resource($this->lock_fp)) {
+ $this->lock_fp = null;
+ return $this->raiseError("could not create lock file" .
+ (isset($php_errormsg) ? ": " . $php_errormsg : ""));
+ }
+
+ if (!(int)flock($this->lock_fp, $mode)) {
+ switch ($mode) {
+ case LOCK_SH: $str = 'shared'; break;
+ case LOCK_EX: $str = 'exclusive'; break;
+ case LOCK_UN: $str = 'unlock'; break;
+ default: $str = 'unknown'; break;
+ }
+
+ //is resource at this point, close it on error.
+ fclose($this->lock_fp);
+ $this->lock_fp = null;
+ return $this->raiseError("could not acquire $str lock ($this->lockfile)",
+ PEAR_REGISTRY_ERROR_LOCK);
+ }
+
+ return true;
+ }
+
+ function _unlock()
+ {
+ $ret = $this->_lock(LOCK_UN);
+ if (is_resource($this->lock_fp)) {
+ fclose($this->lock_fp);
+ }
+
+ $this->lock_fp = null;
+ return $ret;
+ }
+
+ function _packageExists($package, $channel = false)
+ {
+ return file_exists($this->_packageFileName($package, $channel));
+ }
+
+ /**
+ * Determine whether a channel exists in the registry
+ *
+ * @param string Channel name
+ * @param bool if true, then aliases will be ignored
+ * @return boolean
+ */
+ function _channelExists($channel, $noaliases = false)
+ {
+ $a = file_exists($this->_channelFileName($channel, $noaliases));
+ if (!$a && $channel == 'pear.php.net') {
+ return true;
+ }
+
+ if (!$a && $channel == 'pecl.php.net') {
+ return true;
+ }
+
+ if (!$a && $channel == 'doc.php.net') {
+ return true;
+ }
+
+ return $a;
+ }
+
+ /**
+ * Determine whether a mirror exists within the deafult channel in the registry
+ *
+ * @param string Channel name
+ * @param string Mirror name
+ *
+ * @return boolean
+ */
+ function _mirrorExists($channel, $mirror)
+ {
+ $data = $this->_channelInfo($channel);
+ if (!isset($data['servers']['mirror'])) {
+ return false;
+ }
+
+ foreach ($data['servers']['mirror'] as $m) {
+ if ($m['attribs']['host'] == $mirror) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param PEAR_ChannelFile Channel object
+ * @param donotuse
+ * @param string Last-Modified HTTP tag from remote request
+ * @return boolean|PEAR_Error True on creation, false if it already exists
+ */
+ function _addChannel($channel, $update = false, $lastmodified = false)
+ {
+ if (!is_a($channel, 'PEAR_ChannelFile')) {
+ return false;
+ }
+
+ if (!$channel->validate()) {
+ return false;
+ }
+
+ if (file_exists($this->_channelFileName($channel->getName()))) {
+ if (!$update) {
+ return false;
+ }
+
+ $checker = $this->_getChannel($channel->getName());
+ if (PEAR::isError($checker)) {
+ return $checker;
+ }
+
+ if ($channel->getAlias() != $checker->getAlias()) {
+ if (file_exists($this->_getChannelAliasFileName($checker->getAlias()))) {
+ @unlink($this->_getChannelAliasFileName($checker->getAlias()));
+ }
+ }
+ } else {
+ if ($update && !in_array($channel->getName(), array('pear.php.net', 'pecl.php.net', 'doc.php.net'))) {
+ return false;
+ }
+ }
+
+ $ret = $this->_assertChannelDir();
+ if (PEAR::isError($ret)) {
+ return $ret;
+ }
+
+ $ret = $this->_assertChannelStateDir($channel->getName());
+ if (PEAR::isError($ret)) {
+ return $ret;
+ }
+
+ if ($channel->getAlias() != $channel->getName()) {
+ if (file_exists($this->_getChannelAliasFileName($channel->getAlias())) &&
+ $this->_getChannelFromAlias($channel->getAlias()) != $channel->getName()) {
+ $channel->setAlias($channel->getName());
+ }
+
+ if (!$this->hasWriteAccess()) {
+ return false;
+ }
+
+ $fp = @fopen($this->_getChannelAliasFileName($channel->getAlias()), 'w');
+ if (!$fp) {
+ return false;
+ }
+
+ fwrite($fp, $channel->getName());
+ fclose($fp);
+ }
+
+ if (!$this->hasWriteAccess()) {
+ return false;
+ }
+
+ $fp = @fopen($this->_channelFileName($channel->getName()), 'wb');
+ if (!$fp) {
+ return false;
+ }
+
+ $info = $channel->toArray();
+ if ($lastmodified) {
+ $info['_lastmodified'] = $lastmodified;
+ } else {
+ $info['_lastmodified'] = date('r');
+ }
+
+ fwrite($fp, serialize($info));
+ fclose($fp);
+ return true;
+ }
+
+ /**
+ * Deletion fails if there are any packages installed from the channel
+ * @param string|PEAR_ChannelFile channel name
+ * @return boolean|PEAR_Error True on deletion, false if it doesn't exist
+ */
+ function _deleteChannel($channel)
+ {
+ if (!is_string($channel)) {
+ if (!is_a($channel, 'PEAR_ChannelFile')) {
+ return false;
+ }
+
+ if (!$channel->validate()) {
+ return false;
+ }
+ $channel = $channel->getName();
+ }
+
+ if ($this->_getChannelFromAlias($channel) == '__uri') {
+ return false;
+ }
+
+ if ($this->_getChannelFromAlias($channel) == 'pecl.php.net') {
+ return false;
+ }
+
+ if ($this->_getChannelFromAlias($channel) == 'doc.php.net') {
+ return false;
+ }
+
+ if (!$this->_channelExists($channel)) {
+ return false;
+ }
+
+ if (!$channel || $this->_getChannelFromAlias($channel) == 'pear.php.net') {
+ return false;
+ }
+
+ $channel = $this->_getChannelFromAlias($channel);
+ if ($channel == 'pear.php.net') {
+ return false;
+ }
+
+ $test = $this->_listChannelPackages($channel);
+ if (count($test)) {
+ return false;
+ }
+
+ $test = @rmdir($this->_channelDirectoryName($channel));
+ if (!$test) {
+ return false;
+ }
+
+ $file = $this->_getChannelAliasFileName($this->_getAlias($channel));
+ if (file_exists($file)) {
+ $test = @unlink($file);
+ if (!$test) {
+ return false;
+ }
+ }
+
+ $file = $this->_channelFileName($channel);
+ $ret = true;
+ if (file_exists($file)) {
+ $ret = @unlink($file);
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Determine whether a channel exists in the registry
+ * @param string Channel Alias
+ * @return boolean
+ */
+ function _isChannelAlias($alias)
+ {
+ return file_exists($this->_getChannelAliasFileName($alias));
+ }
+
+ /**
+ * @param string|null
+ * @param string|null
+ * @param string|null
+ * @return array|null
+ * @access private
+ */
+ function _packageInfo($package = null, $key = null, $channel = 'pear.php.net')
+ {
+ if ($package === null) {
+ if ($channel === null) {
+ $channels = $this->_listChannels();
+ $ret = array();
+ foreach ($channels as $channel) {
+ $channel = strtolower($channel);
+ $ret[$channel] = array();
+ $packages = $this->_listPackages($channel);
+ foreach ($packages as $package) {
+ $ret[$channel][] = $this->_packageInfo($package, null, $channel);
+ }
+ }
+
+ return $ret;
+ }
+
+ $ps = $this->_listPackages($channel);
+ if (!count($ps)) {
+ return array();
+ }
+ return array_map(array(&$this, '_packageInfo'),
+ $ps, array_fill(0, count($ps), null),
+ array_fill(0, count($ps), $channel));
+ }
+
+ $fp = $this->_openPackageFile($package, 'r', $channel);
+ if ($fp === null) {
+ return null;
+ }
+
+ $rt = get_magic_quotes_runtime();
+ set_magic_quotes_runtime(0);
+ clearstatcache();
+ $this->_closePackageFile($fp);
+ $data = file_get_contents($this->_packageFileName($package, $channel));
+ set_magic_quotes_runtime($rt);
+ $data = unserialize($data);
+ if ($key === null) {
+ return $data;
+ }
+
+ // compatibility for package.xml version 2.0
+ if (isset($data['old'][$key])) {
+ return $data['old'][$key];
+ }
+
+ if (isset($data[$key])) {
+ return $data[$key];
+ }
+
+ return null;
+ }
+
+ /**
+ * @param string Channel name
+ * @param bool whether to strictly retrieve info of channels, not just aliases
+ * @return array|null
+ */
+ function _channelInfo($channel, $noaliases = false)
+ {
+ if (!$this->_channelExists($channel, $noaliases)) {
+ return null;
+ }
+
+ $fp = $this->_openChannelFile($channel, 'r');
+ if ($fp === null) {
+ return null;
+ }
+
+ $rt = get_magic_quotes_runtime();
+ set_magic_quotes_runtime(0);
+ clearstatcache();
+ $this->_closeChannelFile($fp);
+ $data = file_get_contents($this->_channelFileName($channel));
+ set_magic_quotes_runtime($rt);
+ $data = unserialize($data);
+ return $data;
+ }
+
+ function _listChannels()
+ {
+ $channellist = array();
+ if (!file_exists($this->channelsdir) || !is_dir($this->channelsdir)) {
+ return array('pear.php.net', 'pecl.php.net', 'doc.php.net', '__uri');
+ }
+
+ $dp = opendir($this->channelsdir);
+ while ($ent = readdir($dp)) {
+ if ($ent{0} == '.' || substr($ent, -4) != '.reg') {
+ continue;
+ }
+
+ if ($ent == '__uri.reg') {
+ $channellist[] = '__uri';
+ continue;
+ }
+
+ $channellist[] = str_replace('_', '/', substr($ent, 0, -4));
+ }
+
+ closedir($dp);
+ if (!in_array('pear.php.net', $channellist)) {
+ $channellist[] = 'pear.php.net';
+ }
+
+ if (!in_array('pecl.php.net', $channellist)) {
+ $channellist[] = 'pecl.php.net';
+ }
+
+ if (!in_array('doc.php.net', $channellist)) {
+ $channellist[] = 'doc.php.net';
+ }
+
+
+ if (!in_array('__uri', $channellist)) {
+ $channellist[] = '__uri';
+ }
+
+ natsort($channellist);
+ return $channellist;
+ }
+
+ function _listPackages($channel = false)
+ {
+ if ($channel && $this->_getChannelFromAlias($channel) != 'pear.php.net') {
+ return $this->_listChannelPackages($channel);
+ }
+
+ if (!file_exists($this->statedir) || !is_dir($this->statedir)) {
+ return array();
+ }
+
+ $pkglist = array();
+ $dp = opendir($this->statedir);
+ if (!$dp) {
+ return $pkglist;
+ }
+
+ while ($ent = readdir($dp)) {
+ if ($ent{0} == '.' || substr($ent, -4) != '.reg') {
+ continue;
+ }
+
+ $pkglist[] = substr($ent, 0, -4);
+ }
+ closedir($dp);
+ return $pkglist;
+ }
+
+ function _listChannelPackages($channel)
+ {
+ $pkglist = array();
+ if (!file_exists($this->_channelDirectoryName($channel)) ||
+ !is_dir($this->_channelDirectoryName($channel))) {
+ return array();
+ }
+
+ $dp = opendir($this->_channelDirectoryName($channel));
+ if (!$dp) {
+ return $pkglist;
+ }
+
+ while ($ent = readdir($dp)) {
+ if ($ent{0} == '.' || substr($ent, -4) != '.reg') {
+ continue;
+ }
+ $pkglist[] = substr($ent, 0, -4);
+ }
+
+ closedir($dp);
+ return $pkglist;
+ }
+
+ function _listAllPackages()
+ {
+ $ret = array();
+ foreach ($this->_listChannels() as $channel) {
+ $ret[$channel] = $this->_listPackages($channel);
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Add an installed package to the registry
+ * @param string package name
+ * @param array package info (parsed by PEAR_Common::infoFrom*() methods)
+ * @return bool success of saving
+ * @access private
+ */
+ function _addPackage($package, $info)
+ {
+ if ($this->_packageExists($package)) {
+ return false;
+ }
+
+ $fp = $this->_openPackageFile($package, 'wb');
+ if ($fp === null) {
+ return false;
+ }
+
+ $info['_lastmodified'] = time();
+ fwrite($fp, serialize($info));
+ $this->_closePackageFile($fp);
+ if (isset($info['filelist'])) {
+ $this->_rebuildFileMap();
+ }
+
+ return true;
+ }
+
+ /**
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * @return bool
+ * @access private
+ */
+ function _addPackage2($info)
+ {
+ if (!is_a($info, 'PEAR_PackageFile_v1') && !is_a($info, 'PEAR_PackageFile_v2')) {
+ return false;
+ }
+
+ if (!$info->validate()) {
+ if (class_exists('PEAR_Common')) {
+ $ui = PEAR_Frontend::singleton();
+ if ($ui) {
+ foreach ($info->getValidationWarnings() as $err) {
+ $ui->log($err['message'], true);
+ }
+ }
+ }
+ return false;
+ }
+
+ $channel = $info->getChannel();
+ $package = $info->getPackage();
+ $save = $info;
+ if ($this->_packageExists($package, $channel)) {
+ return false;
+ }
+
+ if (!$this->_channelExists($channel, true)) {
+ return false;
+ }
+
+ $info = $info->toArray(true);
+ if (!$info) {
+ return false;
+ }
+
+ $fp = $this->_openPackageFile($package, 'wb', $channel);
+ if ($fp === null) {
+ return false;
+ }
+
+ $info['_lastmodified'] = time();
+ fwrite($fp, serialize($info));
+ $this->_closePackageFile($fp);
+ $this->_rebuildFileMap();
+ return true;
+ }
+
+ /**
+ * @param string Package name
+ * @param array parsed package.xml 1.0
+ * @param bool this parameter is only here for BC. Don't use it.
+ * @access private
+ */
+ function _updatePackage($package, $info, $merge = true)
+ {
+ $oldinfo = $this->_packageInfo($package);
+ if (empty($oldinfo)) {
+ return false;
+ }
+
+ $fp = $this->_openPackageFile($package, 'w');
+ if ($fp === null) {
+ return false;
+ }
+
+ if (is_object($info)) {
+ $info = $info->toArray();
+ }
+ $info['_lastmodified'] = time();
+
+ $newinfo = $info;
+ if ($merge) {
+ $info = array_merge($oldinfo, $info);
+ } else {
+ $diff = $info;
+ }
+
+ fwrite($fp, serialize($info));
+ $this->_closePackageFile($fp);
+ if (isset($newinfo['filelist'])) {
+ $this->_rebuildFileMap();
+ }
+
+ return true;
+ }
+
+ /**
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * @return bool
+ * @access private
+ */
+ function _updatePackage2($info)
+ {
+ if (!$this->_packageExists($info->getPackage(), $info->getChannel())) {
+ return false;
+ }
+
+ $fp = $this->_openPackageFile($info->getPackage(), 'w', $info->getChannel());
+ if ($fp === null) {
+ return false;
+ }
+
+ $save = $info;
+ $info = $save->getArray(true);
+ $info['_lastmodified'] = time();
+ fwrite($fp, serialize($info));
+ $this->_closePackageFile($fp);
+ $this->_rebuildFileMap();
+ return true;
+ }
+
+ /**
+ * @param string Package name
+ * @param string Channel name
+ * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|null
+ * @access private
+ */
+ function &_getPackage($package, $channel = 'pear.php.net')
+ {
+ $info = $this->_packageInfo($package, null, $channel);
+ if ($info === null) {
+ return $info;
+ }
+
+ $a = $this->_config;
+ if (!$a) {
+ $this->_config = &new PEAR_Config;
+ $this->_config->set('php_dir', $this->statedir);
+ }
+
+ if (!class_exists('PEAR_PackageFile')) {
+ require_once 'PEAR/PackageFile.php';
+ }
+
+ $pkg = &new PEAR_PackageFile($this->_config);
+ $pf = &$pkg->fromArray($info);
+ return $pf;
+ }
+
+ /**
+ * @param string channel name
+ * @param bool whether to strictly retrieve channel names
+ * @return PEAR_ChannelFile|PEAR_Error
+ * @access private
+ */
+ function &_getChannel($channel, $noaliases = false)
+ {
+ $ch = false;
+ if ($this->_channelExists($channel, $noaliases)) {
+ $chinfo = $this->_channelInfo($channel, $noaliases);
+ if ($chinfo) {
+ if (!class_exists('PEAR_ChannelFile')) {
+ require_once 'PEAR/ChannelFile.php';
+ }
+
+ $ch = &PEAR_ChannelFile::fromArrayWithErrors($chinfo);
+ }
+ }
+
+ if ($ch) {
+ if ($ch->validate()) {
+ return $ch;
+ }
+
+ foreach ($ch->getErrors(true) as $err) {
+ $message = $err['message'] . "\n";
+ }
+
+ $ch = PEAR::raiseError($message);
+ return $ch;
+ }
+
+ if ($this->_getChannelFromAlias($channel) == 'pear.php.net') {
+ // the registry is not properly set up, so use defaults
+ if (!class_exists('PEAR_ChannelFile')) {
+ require_once 'PEAR/ChannelFile.php';
+ }
+
+ $pear_channel = new PEAR_ChannelFile;
+ $pear_channel->setServer('pear.php.net');
+ $pear_channel->setAlias('pear');
+ $pear_channel->setSummary('PHP Extension and Application Repository');
+ $pear_channel->setDefaultPEARProtocols();
+ $pear_channel->setBaseURL('REST1.0', 'http://pear.php.net/rest/');
+ $pear_channel->setBaseURL('REST1.1', 'http://pear.php.net/rest/');
+ $pear_channel->setBaseURL('REST1.3', 'http://pear.php.net/rest/');
+ return $pear_channel;
+ }
+
+ if ($this->_getChannelFromAlias($channel) == 'pecl.php.net') {
+ // the registry is not properly set up, so use defaults
+ if (!class_exists('PEAR_ChannelFile')) {
+ require_once 'PEAR/ChannelFile.php';
+ }
+ $pear_channel = new PEAR_ChannelFile;
+ $pear_channel->setServer('pecl.php.net');
+ $pear_channel->setAlias('pecl');
+ $pear_channel->setSummary('PHP Extension Community Library');
+ $pear_channel->setDefaultPEARProtocols();
+ $pear_channel->setBaseURL('REST1.0', 'http://pecl.php.net/rest/');
+ $pear_channel->setBaseURL('REST1.1', 'http://pecl.php.net/rest/');
+ $pear_channel->setValidationPackage('PEAR_Validator_PECL', '1.0');
+ return $pear_channel;
+ }
+
+ if ($this->_getChannelFromAlias($channel) == 'doc.php.net') {
+ // the registry is not properly set up, so use defaults
+ if (!class_exists('PEAR_ChannelFile')) {
+ require_once 'PEAR/ChannelFile.php';
+ }
+
+ $doc_channel = new PEAR_ChannelFile;
+ $doc_channel->setServer('doc.php.net');
+ $doc_channel->setAlias('phpdocs');
+ $doc_channel->setSummary('PHP Documentation Team');
+ $doc_channel->setDefaultPEARProtocols();
+ $doc_channel->setBaseURL('REST1.0', 'http://doc.php.net/rest/');
+ $doc_channel->setBaseURL('REST1.1', 'http://doc.php.net/rest/');
+ $doc_channel->setBaseURL('REST1.3', 'http://doc.php.net/rest/');
+ return $doc_channel;
+ }
+
+
+ if ($this->_getChannelFromAlias($channel) == '__uri') {
+ // the registry is not properly set up, so use defaults
+ if (!class_exists('PEAR_ChannelFile')) {
+ require_once 'PEAR/ChannelFile.php';
+ }
+
+ $private = new PEAR_ChannelFile;
+ $private->setName('__uri');
+ $private->setDefaultPEARProtocols();
+ $private->setBaseURL('REST1.0', '****');
+ $private->setSummary('Pseudo-channel for static packages');
+ return $private;
+ }
+
+ return $ch;
+ }
+
+ /**
+ * @param string Package name
+ * @param string Channel name
+ * @return bool
+ */
+ function packageExists($package, $channel = 'pear.php.net')
+ {
+ if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+ return $e;
+ }
+ $ret = $this->_packageExists($package, $channel);
+ $this->_unlock();
+ return $ret;
+ }
+
+ /**
+ * @param string channel name
+ * @param bool if true, then aliases will be ignored
+ * @return bool
+ */
+ function channelExists($channel, $noaliases = false)
+ {
+ if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+ return $e;
+ }
+ $ret = $this->_channelExists($channel, $noaliases);
+ $this->_unlock();
+ return $ret;
+ }
+
+ /**
+ * @param string channel name mirror is in
+ * @param string mirror name
+ *
+ * @return bool
+ */
+ function mirrorExists($channel, $mirror)
+ {
+ if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+ return $e;
+ }
+
+ $ret = $this->_mirrorExists($channel, $mirror);
+ $this->_unlock();
+ return $ret;
+ }
+
+ /**
+ * Determines whether the parameter is an alias of a channel
+ * @param string
+ * @return bool
+ */
+ function isAlias($alias)
+ {
+ if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+ return $e;
+ }
+ $ret = $this->_isChannelAlias($alias);
+ $this->_unlock();
+ return $ret;
+ }
+
+ /**
+ * @param string|null
+ * @param string|null
+ * @param string
+ * @return array|null
+ */
+ function packageInfo($package = null, $key = null, $channel = 'pear.php.net')
+ {
+ if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+ return $e;
+ }
+ $ret = $this->_packageInfo($package, $key, $channel);
+ $this->_unlock();
+ return $ret;
+ }
+
+ /**
+ * Retrieve a raw array of channel data.
+ *
+ * Do not use this, instead use {@link getChannel()} for normal
+ * operations. Array structure is undefined in this method
+ * @param string channel name
+ * @param bool whether to strictly retrieve information only on non-aliases
+ * @return array|null|PEAR_Error
+ */
+ function channelInfo($channel = null, $noaliases = false)
+ {
+ if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+ return $e;
+ }
+ $ret = $this->_channelInfo($channel, $noaliases);
+ $this->_unlock();
+ return $ret;
+ }
+
+ /**
+ * @param string
+ */
+ function channelName($channel)
+ {
+ if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+ return $e;
+ }
+ $ret = $this->_getChannelFromAlias($channel);
+ $this->_unlock();
+ return $ret;
+ }
+
+ /**
+ * @param string
+ */
+ function channelAlias($channel)
+ {
+ if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+ return $e;
+ }
+ $ret = $this->_getAlias($channel);
+ $this->_unlock();
+ return $ret;
+ }
+
+ function listPackages($channel = false)
+ {
+ if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+ return $e;
+ }
+ $ret = $this->_listPackages($channel);
+ $this->_unlock();
+ return $ret;
+ }
+
+ function listAllPackages()
+ {
+ if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+ return $e;
+ }
+ $ret = $this->_listAllPackages();
+ $this->_unlock();
+ return $ret;
+ }
+
+ function listChannels()
+ {
+ if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+ return $e;
+ }
+ $ret = $this->_listChannels();
+ $this->_unlock();
+ return $ret;
+ }
+
+ /**
+ * Add an installed package to the registry
+ * @param string|PEAR_PackageFile_v1|PEAR_PackageFile_v2 package name or object
+ * that will be passed to {@link addPackage2()}
+ * @param array package info (parsed by PEAR_Common::infoFrom*() methods)
+ * @return bool success of saving
+ */
+ function addPackage($package, $info)
+ {
+ if (is_object($info)) {
+ return $this->addPackage2($info);
+ }
+ if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
+ return $e;
+ }
+ $ret = $this->_addPackage($package, $info);
+ $this->_unlock();
+ if ($ret) {
+ if (!class_exists('PEAR_PackageFile_v1')) {
+ require_once 'PEAR/PackageFile/v1.php';
+ }
+ $pf = new PEAR_PackageFile_v1;
+ $pf->setConfig($this->_config);
+ $pf->fromArray($info);
+ $this->_dependencyDB->uninstallPackage($pf);
+ $this->_dependencyDB->installPackage($pf);
+ }
+ return $ret;
+ }
+
+ function addPackage2($info)
+ {
+ if (!is_object($info)) {
+ return $this->addPackage($info['package'], $info);
+ }
+ if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
+ return $e;
+ }
+ $ret = $this->_addPackage2($info);
+ $this->_unlock();
+ if ($ret) {
+ $this->_dependencyDB->uninstallPackage($info);
+ $this->_dependencyDB->installPackage($info);
+ }
+ return $ret;
+ }
+
+ /**
+ * For future expandibility purposes, separate this
+ * @param PEAR_ChannelFile
+ */
+ function updateChannel($channel, $lastmodified = null)
+ {
+ if ($channel->getName() == '__uri') {
+ return false;
+ }
+ return $this->addChannel($channel, $lastmodified, true);
+ }
+
+ /**
+ * Deletion fails if there are any packages installed from the channel
+ * @param string|PEAR_ChannelFile channel name
+ * @return boolean|PEAR_Error True on deletion, false if it doesn't exist
+ */
+ function deleteChannel($channel)
+ {
+ if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
+ return $e;
+ }
+
+ $ret = $this->_deleteChannel($channel);
+ $this->_unlock();
+ if ($ret && is_a($this->_config, 'PEAR_Config')) {
+ $this->_config->setChannels($this->listChannels());
+ }
+
+ return $ret;
+ }
+
+ /**
+ * @param PEAR_ChannelFile Channel object
+ * @param string Last-Modified header from HTTP for caching
+ * @return boolean|PEAR_Error True on creation, false if it already exists
+ */
+ function addChannel($channel, $lastmodified = false, $update = false)
+ {
+ if (!is_a($channel, 'PEAR_ChannelFile') || !$channel->validate()) {
+ if (!$channel->validate()) {
+ $msg = '';
+ $errors = $channel->getErrors();
+ foreach ($errors as $error) {
+ $msg .= $error['message'] . ', ';
+ }
+ return PEAR::raiseError(substr($msg, 0, -2));
+ }
+ return false;
+ }
+
+ if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
+ return $e;
+ }
+
+ $ret = $this->_addChannel($channel, $update, $lastmodified);
+ $this->_unlock();
+ if (!$update && $ret && is_a($this->_config, 'PEAR_Config')) {
+ $this->_config->setChannels($this->listChannels());
+ }
+
+ return $ret;
+ }
+
+ function deletePackage($package, $channel = 'pear.php.net')
+ {
+ if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
+ return $e;
+ }
+
+ $file = $this->_packageFileName($package, $channel);
+ $ret = file_exists($file) ? @unlink($file) : false;
+ $this->_rebuildFileMap();
+ $this->_unlock();
+ $p = array('channel' => $channel, 'package' => $package);
+ $this->_dependencyDB->uninstallPackage($p);
+ return $ret;
+ }
+
+ function updatePackage($package, $info, $merge = true)
+ {
+ if (is_object($info)) {
+ return $this->updatePackage2($info, $merge);
+ }
+ if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
+ return $e;
+ }
+ $ret = $this->_updatePackage($package, $info, $merge);
+ $this->_unlock();
+ if ($ret) {
+ if (!class_exists('PEAR_PackageFile_v1')) {
+ require_once 'PEAR/PackageFile/v1.php';
+ }
+ $pf = new PEAR_PackageFile_v1;
+ $pf->setConfig($this->_config);
+ $pf->fromArray($this->packageInfo($package));
+ $this->_dependencyDB->uninstallPackage($pf);
+ $this->_dependencyDB->installPackage($pf);
+ }
+ return $ret;
+ }
+
+ function updatePackage2($info)
+ {
+
+ if (!is_object($info)) {
+ return $this->updatePackage($info['package'], $info, $merge);
+ }
+
+ if (!$info->validate(PEAR_VALIDATE_DOWNLOADING)) {
+ return false;
+ }
+
+ if (PEAR::isError($e = $this->_lock(LOCK_EX))) {
+ return $e;
+ }
+
+ $ret = $this->_updatePackage2($info);
+ $this->_unlock();
+ if ($ret) {
+ $this->_dependencyDB->uninstallPackage($info);
+ $this->_dependencyDB->installPackage($info);
+ }
+
+ return $ret;
+ }
+
+ /**
+ * @param string channel name
+ * @param bool whether to strictly return raw channels (no aliases)
+ * @return PEAR_ChannelFile|PEAR_Error
+ */
+ function &getChannel($channel, $noaliases = false)
+ {
+ if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+ return $e;
+ }
+ $ret = &$this->_getChannel($channel, $noaliases);
+ $this->_unlock();
+ if (!$ret) {
+ return PEAR::raiseError('Unknown channel: ' . $channel);
+ }
+ return $ret;
+ }
+
+ /**
+ * @param string package name
+ * @param string channel name
+ * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|null
+ */
+ function &getPackage($package, $channel = 'pear.php.net')
+ {
+ if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+ return $e;
+ }
+ $pf = &$this->_getPackage($package, $channel);
+ $this->_unlock();
+ return $pf;
+ }
+
+ /**
+ * Get PEAR_PackageFile_v[1/2] objects representing the contents of
+ * a dependency group that are installed.
+ *
+ * This is used at uninstall-time
+ * @param array
+ * @return array|false
+ */
+ function getInstalledGroup($group)
+ {
+ $ret = array();
+ if (isset($group['package'])) {
+ if (!isset($group['package'][0])) {
+ $group['package'] = array($group['package']);
+ }
+ foreach ($group['package'] as $package) {
+ $depchannel = isset($package['channel']) ? $package['channel'] : '__uri';
+ $p = &$this->getPackage($package['name'], $depchannel);
+ if ($p) {
+ $save = &$p;
+ $ret[] = &$save;
+ }
+ }
+ }
+ if (isset($group['subpackage'])) {
+ if (!isset($group['subpackage'][0])) {
+ $group['subpackage'] = array($group['subpackage']);
+ }
+ foreach ($group['subpackage'] as $package) {
+ $depchannel = isset($package['channel']) ? $package['channel'] : '__uri';
+ $p = &$this->getPackage($package['name'], $depchannel);
+ if ($p) {
+ $save = &$p;
+ $ret[] = &$save;
+ }
+ }
+ }
+ if (!count($ret)) {
+ return false;
+ }
+ return $ret;
+ }
+
+ /**
+ * @param string channel name
+ * @return PEAR_Validate|false
+ */
+ function &getChannelValidator($channel)
+ {
+ $chan = $this->getChannel($channel);
+ if (PEAR::isError($chan)) {
+ return $chan;
+ }
+ $val = $chan->getValidationObject();
+ return $val;
+ }
+
+ /**
+ * @param string channel name
+ * @return array an array of PEAR_ChannelFile objects representing every installed channel
+ */
+ function &getChannels()
+ {
+ $ret = array();
+ if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+ return $e;
+ }
+ foreach ($this->_listChannels() as $channel) {
+ $e = &$this->_getChannel($channel);
+ if (!$e || PEAR::isError($e)) {
+ continue;
+ }
+ $ret[] = $e;
+ }
+ $this->_unlock();
+ return $ret;
+ }
+
+ /**
+ * Test whether a file or set of files belongs to a package.
+ *
+ * If an array is passed in
+ * @param string|array file path, absolute or relative to the pear
+ * install dir
+ * @param string|array name of PEAR package or array('package' => name, 'channel' =>
+ * channel) of a package that will be ignored
+ * @param string API version - 1.1 will exclude any files belonging to a package
+ * @param array private recursion variable
+ * @return array|false which package and channel the file belongs to, or an empty
+ * string if the file does not belong to an installed package,
+ * or belongs to the second parameter's package
+ */
+ function checkFileMap($path, $package = false, $api = '1.0', $attrs = false)
+ {
+ if (is_array($path)) {
+ static $notempty;
+ if (empty($notempty)) {
+ if (!class_exists('PEAR_Installer_Role')) {
+ require_once 'PEAR/Installer/Role.php';
+ }
+ $notempty = create_function('$a','return !empty($a);');
+ }
+ $package = is_array($package) ? array(strtolower($package[0]), strtolower($package[1]))
+ : strtolower($package);
+ $pkgs = array();
+ foreach ($path as $name => $attrs) {
+ if (is_array($attrs)) {
+ if (isset($attrs['install-as'])) {
+ $name = $attrs['install-as'];
+ }
+ if (!in_array($attrs['role'], PEAR_Installer_Role::getInstallableRoles())) {
+ // these are not installed
+ continue;
+ }
+ if (!in_array($attrs['role'], PEAR_Installer_Role::getBaseinstallRoles())) {
+ $attrs['baseinstalldir'] = is_array($package) ? $package[1] : $package;
+ }
+ if (isset($attrs['baseinstalldir'])) {
+ $name = $attrs['baseinstalldir'] . DIRECTORY_SEPARATOR . $name;
+ }
+ }
+ $pkgs[$name] = $this->checkFileMap($name, $package, $api, $attrs);
+ if (PEAR::isError($pkgs[$name])) {
+ return $pkgs[$name];
+ }
+ }
+ return array_filter($pkgs, $notempty);
+ }
+ if (empty($this->filemap_cache)) {
+ if (PEAR::isError($e = $this->_lock(LOCK_SH))) {
+ return $e;
+ }
+ $err = $this->_readFileMap();
+ $this->_unlock();
+ if (PEAR::isError($err)) {
+ return $err;
+ }
+ }
+ if (!$attrs) {
+ $attrs = array('role' => 'php'); // any old call would be for PHP role only
+ }
+ if (isset($this->filemap_cache[$attrs['role']][$path])) {
+ if ($api >= '1.1' && $this->filemap_cache[$attrs['role']][$path] == $package) {
+ return false;
+ }
+ return $this->filemap_cache[$attrs['role']][$path];
+ }
+ $l = strlen($this->install_dir);
+ if (substr($path, 0, $l) == $this->install_dir) {
+ $path = preg_replace('!^'.DIRECTORY_SEPARATOR.'+!', '', substr($path, $l));
+ }
+ if (isset($this->filemap_cache[$attrs['role']][$path])) {
+ if ($api >= '1.1' && $this->filemap_cache[$attrs['role']][$path] == $package) {
+ return false;
+ }
+ return $this->filemap_cache[$attrs['role']][$path];
+ }
+ return false;
+ }
+
+ /**
+ * Force a reload of the filemap
+ * @since 1.5.0RC3
+ */
+ function flushFileMap()
+ {
+ $this->filemap_cache = null;
+ clearstatcache(); // ensure that the next read gets the full, current filemap
+ }
+
+ /**
+ * Get the expected API version. Channels API is version 1.1, as it is backwards
+ * compatible with 1.0
+ * @return string
+ */
+ function apiVersion()
+ {
+ return '1.1';
+ }
+
+ /**
+ * Parse a package name, or validate a parsed package name array
+ * @param string|array pass in an array of format
+ * array(
+ * 'package' => 'pname',
+ * ['channel' => 'channame',]
+ * ['version' => 'version',]
+ * ['state' => 'state',]
+ * ['group' => 'groupname'])
+ * or a string of format
+ * [channel://][channame/]pname[-version|-state][/group=groupname]
+ * @return array|PEAR_Error
+ */
+ function parsePackageName($param, $defaultchannel = 'pear.php.net')
+ {
+ $saveparam = $param;
+ if (is_array($param)) {
+ // convert to string for error messages
+ $saveparam = $this->parsedPackageNameToString($param);
+ // process the array
+ if (!isset($param['package'])) {
+ return PEAR::raiseError('parsePackageName(): array $param ' .
+ 'must contain a valid package name in index "param"',
+ 'package', null, null, $param);
+ }
+ if (!isset($param['uri'])) {
+ if (!isset($param['channel'])) {
+ $param['channel'] = $defaultchannel;
+ }
+ } else {
+ $param['channel'] = '__uri';
+ }
+ } else {
+ $components = @parse_url((string) $param);
+ if (isset($components['scheme'])) {
+ if ($components['scheme'] == 'http') {
+ // uri package
+ $param = array('uri' => $param, 'channel' => '__uri');
+ } elseif($components['scheme'] != 'channel') {
+ return PEAR::raiseError('parsePackageName(): only channel:// uris may ' .
+ 'be downloaded, not "' . $param . '"', 'invalid', null, null, $param);
+ }
+ }
+ if (!isset($components['path'])) {
+ return PEAR::raiseError('parsePackageName(): array $param ' .
+ 'must contain a valid package name in "' . $param . '"',
+ 'package', null, null, $param);
+ }
+ if (isset($components['host'])) {
+ // remove the leading "/"
+ $components['path'] = substr($components['path'], 1);
+ }
+ if (!isset($components['scheme'])) {
+ if (strpos($components['path'], '/') !== false) {
+ if ($components['path']{0} == '/') {
+ return PEAR::raiseError('parsePackageName(): this is not ' .
+ 'a package name, it begins with "/" in "' . $param . '"',
+ 'invalid', null, null, $param);
+ }
+ $parts = explode('/', $components['path']);
+ $components['host'] = array_shift($parts);
+ if (count($parts) > 1) {
+ $components['path'] = array_pop($parts);
+ $components['host'] .= '/' . implode('/', $parts);
+ } else {
+ $components['path'] = implode('/', $parts);
+ }
+ } else {
+ $components['host'] = $defaultchannel;
+ }
+ } else {
+ if (strpos($components['path'], '/')) {
+ $parts = explode('/', $components['path']);
+ $components['path'] = array_pop($parts);
+ $components['host'] .= '/' . implode('/', $parts);
+ }
+ }
+
+ if (is_array($param)) {
+ $param['package'] = $components['path'];
+ } else {
+ $param = array(
+ 'package' => $components['path']
+ );
+ if (isset($components['host'])) {
+ $param['channel'] = $components['host'];
+ }
+ }
+ if (isset($components['fragment'])) {
+ $param['group'] = $components['fragment'];
+ }
+ if (isset($components['user'])) {
+ $param['user'] = $components['user'];
+ }
+ if (isset($components['pass'])) {
+ $param['pass'] = $components['pass'];
+ }
+ if (isset($components['query'])) {
+ parse_str($components['query'], $param['opts']);
+ }
+ // check for extension
+ $pathinfo = pathinfo($param['package']);
+ if (isset($pathinfo['extension']) &&
+ in_array(strtolower($pathinfo['extension']), array('tgz', 'tar'))) {
+ $param['extension'] = $pathinfo['extension'];
+ $param['package'] = substr($pathinfo['basename'], 0,
+ strlen($pathinfo['basename']) - 4);
+ }
+ // check for version
+ if (strpos($param['package'], '-')) {
+ $test = explode('-', $param['package']);
+ if (count($test) != 2) {
+ return PEAR::raiseError('parsePackageName(): only one version/state ' .
+ 'delimiter "-" is allowed in "' . $saveparam . '"',
+ 'version', null, null, $param);
+ }
+ list($param['package'], $param['version']) = $test;
+ }
+ }
+ // validation
+ $info = $this->channelExists($param['channel']);
+ if (PEAR::isError($info)) {
+ return $info;
+ }
+ if (!$info) {
+ return PEAR::raiseError('unknown channel "' . $param['channel'] .
+ '" in "' . $saveparam . '"', 'channel', null, null, $param);
+ }
+ $chan = $this->getChannel($param['channel']);
+ if (PEAR::isError($chan)) {
+ return $chan;
+ }
+ if (!$chan) {
+ return PEAR::raiseError("Exception: corrupt registry, could not " .
+ "retrieve channel " . $param['channel'] . " information",
+ 'registry', null, null, $param);
+ }
+ $param['channel'] = $chan->getName();
+ $validate = $chan->getValidationObject();
+ $vpackage = $chan->getValidationPackage();
+ // validate package name
+ if (!$validate->validPackageName($param['package'], $vpackage['_content'])) {
+ return PEAR::raiseError('parsePackageName(): invalid package name "' .
+ $param['package'] . '" in "' . $saveparam . '"',
+ 'package', null, null, $param);
+ }
+ if (isset($param['group'])) {
+ if (!PEAR_Validate::validGroupName($param['group'])) {
+ return PEAR::raiseError('parsePackageName(): dependency group "' . $param['group'] .
+ '" is not a valid group name in "' . $saveparam . '"', 'group', null, null,
+ $param);
+ }
+ }
+ if (isset($param['state'])) {
+ if (!in_array(strtolower($param['state']), $validate->getValidStates())) {
+ return PEAR::raiseError('parsePackageName(): state "' . $param['state']
+ . '" is not a valid state in "' . $saveparam . '"',
+ 'state', null, null, $param);
+ }
+ }
+ if (isset($param['version'])) {
+ if (isset($param['state'])) {
+ return PEAR::raiseError('parsePackageName(): cannot contain both ' .
+ 'a version and a stability (state) in "' . $saveparam . '"',
+ 'version/state', null, null, $param);
+ }
+ // check whether version is actually a state
+ if (in_array(strtolower($param['version']), $validate->getValidStates())) {
+ $param['state'] = strtolower($param['version']);
+ unset($param['version']);
+ } else {
+ if (!$validate->validVersion($param['version'])) {
+ return PEAR::raiseError('parsePackageName(): "' . $param['version'] .
+ '" is neither a valid version nor a valid state in "' .
+ $saveparam . '"', 'version/state', null, null, $param);
+ }
+ }
+ }
+ return $param;
+ }
+
+ /**
+ * @param array
+ * @return string
+ */
+ function parsedPackageNameToString($parsed, $brief = false)
+ {
+ if (is_string($parsed)) {
+ return $parsed;
+ }
+ if (is_object($parsed)) {
+ $p = $parsed;
+ $parsed = array(
+ 'package' => $p->getPackage(),
+ 'channel' => $p->getChannel(),
+ 'version' => $p->getVersion(),
+ );
+ }
+ if (isset($parsed['uri'])) {
+ return $parsed['uri'];
+ }
+ if ($brief) {
+ if ($channel = $this->channelAlias($parsed['channel'])) {
+ return $channel . '/' . $parsed['package'];
+ }
+ }
+ $upass = '';
+ if (isset($parsed['user'])) {
+ $upass = $parsed['user'];
+ if (isset($parsed['pass'])) {
+ $upass .= ':' . $parsed['pass'];
+ }
+ $upass = "$upass@";
+ }
+ $ret = 'channel://' . $upass . $parsed['channel'] . '/' . $parsed['package'];
+ if (isset($parsed['version']) || isset($parsed['state'])) {
+ $ver = isset($parsed['version']) ? $parsed['version'] : '';
+ $ver .= isset($parsed['state']) ? $parsed['state'] : '';
+ $ret .= '-' . $ver;
+ }
+ if (isset($parsed['extension'])) {
+ $ret .= '.' . $parsed['extension'];
+ }
+ if (isset($parsed['opts'])) {
+ $ret .= '?';
+ foreach ($parsed['opts'] as $name => $value) {
+ $parsed['opts'][$name] = "$name=$value";
+ }
+ $ret .= implode('&', $parsed['opts']);
+ }
+ if (isset($parsed['group'])) {
+ $ret .= '#' . $parsed['group'];
+ }
+ return $ret;
+ }
+}
diff --git a/includes/pear/PEAR/RunTest.php b/includes/pear/PEAR/RunTest.php
new file mode 100644
index 0000000..a916af4
--- /dev/null
+++ b/includes/pear/PEAR/RunTest.php
@@ -0,0 +1,973 @@
+<?php
+/**
+ * PEAR_RunTest
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.3.3
+ */
+
+/**
+ * for error handling
+ */
+require_once 'PEAR.php';
+require_once 'PEAR/Config.php';
+
+define('DETAILED', 1);
+putenv("PHP_PEAR_RUNTESTS=1");
+
+/**
+ * Simplified version of PHP's test suite
+ *
+ * Try it with:
+ *
+ * $ php -r 'include "../PEAR/RunTest.php"; $t=new PEAR_RunTest; $o=$t->run("./pear_system.phpt");print_r($o);'
+ *
+ *
+ * @category pear
+ * @package PEAR
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.3.3
+ */
+class PEAR_RunTest
+{
+ var $_headers = array();
+ var $_logger;
+ var $_options;
+ var $_php;
+ var $tests_count;
+ var $xdebug_loaded;
+ /**
+ * Saved value of php executable, used to reset $_php when we
+ * have a test that uses cgi
+ *
+ * @var unknown_type
+ */
+ var $_savephp;
+ var $ini_overwrites = array(
+ 'output_handler=',
+ 'open_basedir=',
+ 'safe_mode=0',
+ 'disable_functions=',
+ 'output_buffering=Off',
+ 'display_errors=1',
+ 'log_errors=0',
+ 'html_errors=0',
+ 'track_errors=1',
+ 'report_memleaks=0',
+ 'report_zend_debug=0',
+ 'docref_root=',
+ 'docref_ext=.html',
+ 'error_prepend_string=',
+ 'error_append_string=',
+ 'auto_prepend_file=',
+ 'auto_append_file=',
+ 'magic_quotes_runtime=0',
+ 'xdebug.default_enable=0',
+ 'allow_url_fopen=1',
+ );
+
+ /**
+ * An object that supports the PEAR_Common->log() signature, or null
+ * @param PEAR_Common|null
+ */
+ function PEAR_RunTest($logger = null, $options = array())
+ {
+ if (!defined('E_DEPRECATED')) {
+ define('E_DEPRECATED', 0);
+ }
+ if (!defined('E_STRICT')) {
+ define('E_STRICT', 0);
+ }
+ $this->ini_overwrites[] = 'error_reporting=' . (E_ALL & ~(E_DEPRECATED | E_STRICT));
+ if (is_null($logger)) {
+ require_once 'PEAR/Common.php';
+ $logger = new PEAR_Common;
+ }
+ $this->_logger = $logger;
+ $this->_options = $options;
+
+ $conf = &PEAR_Config::singleton();
+ $this->_php = $conf->get('php_bin');
+ }
+
+ /**
+ * Taken from php-src/run-tests.php
+ *
+ * @param string $commandline command name
+ * @param array $env
+ * @param string $stdin standard input to pass to the command
+ * @return unknown
+ */
+ function system_with_timeout($commandline, $env = null, $stdin = null)
+ {
+ $data = '';
+ if (version_compare(phpversion(), '5.0.0', '<')) {
+ $proc = proc_open($commandline, array(
+ 0 => array('pipe', 'r'),
+ 1 => array('pipe', 'w'),
+ 2 => array('pipe', 'w')
+ ), $pipes);
+ } else {
+ $proc = proc_open($commandline, array(
+ 0 => array('pipe', 'r'),
+ 1 => array('pipe', 'w'),
+ 2 => array('pipe', 'w')
+ ), $pipes, null, $env, array('suppress_errors' => true));
+ }
+
+ if (!$proc) {
+ return false;
+ }
+
+ if (is_string($stdin)) {
+ fwrite($pipes[0], $stdin);
+ }
+ fclose($pipes[0]);
+
+ while (true) {
+ /* hide errors from interrupted syscalls */
+ $r = $pipes;
+ $e = $w = null;
+ $n = @stream_select($r, $w, $e, 60);
+
+ if ($n === 0) {
+ /* timed out */
+ $data .= "\n ** ERROR: process timed out **\n";
+ proc_terminate($proc);
+ return array(1234567890, $data);
+ } else if ($n > 0) {
+ $line = fread($pipes[1], 8192);
+ if (strlen($line) == 0) {
+ /* EOF */
+ break;
+ }
+ $data .= $line;
+ }
+ }
+ if (function_exists('proc_get_status')) {
+ $stat = proc_get_status($proc);
+ if ($stat['signaled']) {
+ $data .= "\nTermsig=".$stat['stopsig'];
+ }
+ }
+ $code = proc_close($proc);
+ if (function_exists('proc_get_status')) {
+ $code = $stat['exitcode'];
+ }
+ return array($code, $data);
+ }
+
+ /**
+ * Turns a PHP INI string into an array
+ *
+ * Turns -d "include_path=/foo/bar" into this:
+ * array(
+ * 'include_path' => array(
+ * 'operator' => '-d',
+ * 'value' => '/foo/bar',
+ * )
+ * )
+ * Works both with quotes and without
+ *
+ * @param string an PHP INI string, -d "include_path=/foo/bar"
+ * @return array
+ */
+ function iniString2array($ini_string)
+ {
+ if (!$ini_string) {
+ return array();
+ }
+ $split = preg_split('/[\s]|=/', $ini_string, -1, PREG_SPLIT_NO_EMPTY);
+ $key = $split[1][0] == '"' ? substr($split[1], 1) : $split[1];
+ $value = $split[2][strlen($split[2]) - 1] == '"' ? substr($split[2], 0, -1) : $split[2];
+ // FIXME review if this is really the struct to go with
+ $array = array($key => array('operator' => $split[0], 'value' => $value));
+ return $array;
+ }
+
+ function settings2array($settings, $ini_settings)
+ {
+ foreach ($settings as $setting) {
+ if (strpos($setting, '=') !== false) {
+ $setting = explode('=', $setting, 2);
+ $name = trim(strtolower($setting[0]));
+ $value = trim($setting[1]);
+ $ini_settings[$name] = $value;
+ }
+ }
+ return $ini_settings;
+ }
+
+ function settings2params($ini_settings)
+ {
+ $settings = '';
+ foreach ($ini_settings as $name => $value) {
+ if (is_array($value)) {
+ $operator = $value['operator'];
+ $value = $value['value'];
+ } else {
+ $operator = '-d';
+ }
+ $value = addslashes($value);
+ $settings .= " $operator \"$name=$value\"";
+ }
+ return $settings;
+ }
+
+ function _preparePhpBin($php, $file, $ini_settings)
+ {
+ $file = escapeshellarg($file);
+ // This was fixed in php 5.3 and is not needed after that
+ if (OS_WINDOWS && version_compare(PHP_VERSION, '5.3', '<')) {
+ $cmd = '"'.escapeshellarg($php).' '.$ini_settings.' -f ' . $file .'"';
+ } else {
+ $cmd = $php . $ini_settings . ' -f ' . $file;
+ }
+
+ return $cmd;
+ }
+
+ function runPHPUnit($file, $ini_settings = '')
+ {
+ if (!file_exists($file) && file_exists(getcwd() . DIRECTORY_SEPARATOR . $file)) {
+ $file = realpath(getcwd() . DIRECTORY_SEPARATOR . $file);
+ } elseif (file_exists($file)) {
+ $file = realpath($file);
+ }
+
+ $cmd = $this->_preparePhpBin($this->_php, $file, $ini_settings);
+ if (isset($this->_logger)) {
+ $this->_logger->log(2, 'Running command "' . $cmd . '"');
+ }
+
+ $savedir = getcwd(); // in case the test moves us around
+ chdir(dirname($file));
+ echo `$cmd`;
+ chdir($savedir);
+ return 'PASSED'; // we have no way of knowing this information so assume passing
+ }
+
+ /**
+ * Runs an individual test case.
+ *
+ * @param string The filename of the test
+ * @param array|string INI settings to be applied to the test run
+ * @param integer Number what the current running test is of the
+ * whole test suite being runned.
+ *
+ * @return string|object Returns PASSED, WARNED, FAILED depending on how the
+ * test came out.
+ * PEAR Error when the tester it self fails
+ */
+ function run($file, $ini_settings_cmd_line = array(), $test_number = 1)
+ {
+ if (isset($this->_savephp)) {
+ $this->_php = $this->_savephp;
+ unset($this->_savephp);
+ }
+ if (empty($this->_options['cgi'])) {
+ // try to see if php-cgi is in the path
+ $res = $this->system_with_timeout('php-cgi -v');
+ if (false !== $res && !(is_array($res) && in_array($res[0], array(-1, 127)))) {
+ $this->_options['cgi'] = 'php-cgi';
+ }
+ }
+ if (1 < $len = strlen($this->tests_count)) {
+ $test_number = str_pad($test_number, $len, ' ', STR_PAD_LEFT);
+ $test_nr = "[$test_number/$this->tests_count] ";
+ } else {
+ $test_nr = '';
+ }
+
+ $file = realpath($file);
+ $section_text = $this->_readFile($file);
+ if (PEAR::isError($section_text)) {
+ return $section_text;
+ }
+
+ if (isset($section_text['POST_RAW']) && isset($section_text['UPLOAD'])) {
+ return PEAR::raiseError("Cannot contain both POST_RAW and UPLOAD in test file: $file");
+ }
+
+ $cwd = getcwd();
+
+ $pass_options = '';
+ if (!empty($this->_options['ini'])) {
+ $pass_options = $this->_options['ini'];
+ }
+
+ if (is_string($ini_settings_cmd_line)) {
+ $ini_settings_cmd_line = $this->iniString2array($ini_settings_cmd_line);
+ }
+
+ $ini_settings_base = $this->settings2array($this->ini_overwrites, $ini_settings_cmd_line);
+ if ($section_text['INI']) {
+ if (strpos($section_text['INI'], '{PWD}') !== false) {
+ $section_text['INI'] = str_replace('{PWD}', dirname($file), $section_text['INI']);
+ }
+ $ini = preg_split( "/[\n\r]+/", $section_text['INI']);
+ $ini_settings_phpt_section = $this->settings2array($ini, array());
+ $ini_settings_phpt_section = $this->settings2params($ini_settings_phpt_section);
+ } else {
+ $ini_settings_phpt_section = '';
+ }
+ $ini_settings_base = $this->settings2params($ini_settings_base);
+ $ini_settings_all = $ini_settings_base . ' ' . $ini_settings_phpt_section;
+
+ $shortname = str_replace($cwd . DIRECTORY_SEPARATOR, '', $file);
+
+ $tested = trim($section_text['TEST']);
+ $tested.= !isset($this->_options['simple']) ? "[$shortname]" : ' ';
+
+ if (!empty($section_text['POST']) || !empty($section_text['POST_RAW']) ||
+ !empty($section_text['UPLOAD']) || !empty($section_text['GET']) ||
+ !empty($section_text['COOKIE']) || !empty($section_text['EXPECTHEADERS'])) {
+ if (empty($this->_options['cgi'])) {
+ if (!isset($this->_options['quiet'])) {
+ $this->_logger->log(0, "SKIP $test_nr$tested (reason: --cgi option needed for this test, type 'pear help run-tests')");
+ }
+ if (isset($this->_options['tapoutput'])) {
+ return array('ok', ' # skip --cgi option needed for this test, "pear help run-tests" for info');
+ }
+ return 'SKIPPED';
+ }
+ $this->_savephp = $this->_php;
+ $this->_php = $this->_options['cgi'];
+ }
+
+ $temp_dir = realpath(dirname($file));
+ $main_file_name = basename($file, 'phpt');
+ $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'diff';
+ $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'log';
+ $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'exp';
+ $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'out';
+ $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'mem';
+ $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'php';
+ $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'skip.php';
+ $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'clean.php';
+ $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . uniqid('phpt.');
+
+ // unlink old test results
+ $this->_cleanupOldFiles($file);
+
+ // Check if test should be skipped.
+ $res = $this->_runSkipIf($section_text, $temp_skipif, $tested, $ini_settings_base);
+ if (count($res) != 2) {
+ return $res;
+ }
+ $info = $res['info'];
+ $warn = $res['warn'];
+
+ // We've satisfied the preconditions - run the test!
+ if (isset($this->_options['coverage']) && $this->xdebug_loaded) {
+ $xdebug_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'xdebug';
+ $text = "\n" . 'function coverage_shutdown() {' .
+ "\n" . ' $xdebug = var_export(xdebug_get_code_coverage(), true);';
+ if (!function_exists('file_put_contents')) {
+ $text .= "\n" . ' $fh = fopen(\'' . $xdebug_file . '\', "wb");' .
+ "\n" . ' if ($fh !== false) {' .
+ "\n" . ' fwrite($fh, $xdebug);' .
+ "\n" . ' fclose($fh);' .
+ "\n" . ' }';
+ } else {
+ $text .= "\n" . ' file_put_contents(\'' . $xdebug_file . '\', $xdebug);';
+ }
+
+ // Workaround for http://pear.php.net/bugs/bug.php?id=17292
+ $lines = explode("\n", $section_text['FILE']);
+ $numLines = count($lines);
+ $namespace = '';
+ $coverage_shutdown = 'coverage_shutdown';
+
+ if (
+ substr($lines[0], 0, 2) == '<?' ||
+ substr($lines[0], 0, 5) == '<?php'
+ ) {
+ unset($lines[0]);
+ }
+
+
+ for ($i = 0; $i < $numLines; $i++) {
+ if (isset($lines[$i]) && substr($lines[$i], 0, 9) == 'namespace') {
+ $namespace = substr($lines[$i], 10, -1);
+ $coverage_shutdown = $namespace . '\\coverage_shutdown';
+ $namespace = "namespace " . $namespace . ";\n";
+
+ unset($lines[$i]);
+ break;
+ }
+ }
+
+ $text .= "\n xdebug_stop_code_coverage();" .
+ "\n" . '} // end coverage_shutdown()' .
+ "\n\n" . 'register_shutdown_function("' . $coverage_shutdown . '");';
+ $text .= "\n" . 'xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);' . "\n";
+
+ $this->save_text($temp_file, "<?php\n" . $namespace . $text . "\n" . implode("\n", $lines));
+ } else {
+ $this->save_text($temp_file, $section_text['FILE']);
+ }
+
+ $args = $section_text['ARGS'] ? ' -- '.$section_text['ARGS'] : '';
+ $cmd = $this->_preparePhpBin($this->_php, $temp_file, $ini_settings_all);
+ $cmd.= "$args 2>&1";
+ if (isset($this->_logger)) {
+ $this->_logger->log(2, 'Running command "' . $cmd . '"');
+ }
+
+ // Reset environment from any previous test.
+ $env = $this->_resetEnv($section_text, $temp_file);
+
+ $section_text = $this->_processUpload($section_text, $file);
+ if (PEAR::isError($section_text)) {
+ return $section_text;
+ }
+
+ if (array_key_exists('POST_RAW', $section_text) && !empty($section_text['POST_RAW'])) {
+ $post = trim($section_text['POST_RAW']);
+ $raw_lines = explode("\n", $post);
+
+ $request = '';
+ $started = false;
+ foreach ($raw_lines as $i => $line) {
+ if (empty($env['CONTENT_TYPE']) &&
+ preg_match('/^Content-Type:(.*)/i', $line, $res)) {
+ $env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1]));
+ continue;
+ }
+ if ($started) {
+ $request .= "\n";
+ }
+ $started = true;
+ $request .= $line;
+ }
+
+ $env['CONTENT_LENGTH'] = strlen($request);
+ $env['REQUEST_METHOD'] = 'POST';
+
+ $this->save_text($tmp_post, $request);
+ $cmd = "$this->_php $pass_options $ini_settings_all \"$temp_file\" 2>&1 < \"$tmp_post\"";
+ } elseif (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) {
+ $post = trim($section_text['POST']);
+ $this->save_text($tmp_post, $post);
+ $content_length = strlen($post);
+
+ $env['REQUEST_METHOD'] = 'POST';
+ $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
+ $env['CONTENT_LENGTH'] = $content_length;
+
+ $cmd = "$this->_php $pass_options $ini_settings_all \"$temp_file\" 2>&1 < \"$tmp_post\"";
+ } else {
+ $env['REQUEST_METHOD'] = 'GET';
+ $env['CONTENT_TYPE'] = '';
+ $env['CONTENT_LENGTH'] = '';
+ }
+
+ if (OS_WINDOWS && isset($section_text['RETURNS'])) {
+ ob_start();
+ system($cmd, $return_value);
+ $out = ob_get_contents();
+ ob_end_clean();
+ $section_text['RETURNS'] = (int) trim($section_text['RETURNS']);
+ $returnfail = ($return_value != $section_text['RETURNS']);
+ } else {
+ $returnfail = false;
+ $stdin = isset($section_text['STDIN']) ? $section_text['STDIN'] : null;
+ $out = $this->system_with_timeout($cmd, $env, $stdin);
+ $return_value = $out[0];
+ $out = $out[1];
+ }
+
+ $output = preg_replace('/\r\n/', "\n", trim($out));
+
+ if (isset($tmp_post) && realpath($tmp_post) && file_exists($tmp_post)) {
+ @unlink(realpath($tmp_post));
+ }
+ chdir($cwd); // in case the test moves us around
+
+ $this->_testCleanup($section_text, $temp_clean);
+
+ /* when using CGI, strip the headers from the output */
+ $output = $this->_stripHeadersCGI($output);
+
+ if (isset($section_text['EXPECTHEADERS'])) {
+ $testheaders = $this->_processHeaders($section_text['EXPECTHEADERS']);
+ $missing = array_diff_assoc($testheaders, $this->_headers);
+ $changed = '';
+ foreach ($missing as $header => $value) {
+ if (isset($this->_headers[$header])) {
+ $changed .= "-$header: $value\n+$header: ";
+ $changed .= $this->_headers[$header];
+ } else {
+ $changed .= "-$header: $value\n";
+ }
+ }
+ if ($missing) {
+ // tack on failed headers to output:
+ $output .= "\n====EXPECTHEADERS FAILURE====:\n$changed";
+ }
+ }
+ // Does the output match what is expected?
+ do {
+ if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) {
+ if (isset($section_text['EXPECTF'])) {
+ $wanted = trim($section_text['EXPECTF']);
+ } else {
+ $wanted = trim($section_text['EXPECTREGEX']);
+ }
+ $wanted_re = preg_replace('/\r\n/', "\n", $wanted);
+ if (isset($section_text['EXPECTF'])) {
+ $wanted_re = preg_quote($wanted_re, '/');
+ // Stick to basics
+ $wanted_re = str_replace("%s", ".+?", $wanted_re); //not greedy
+ $wanted_re = str_replace("%i", "[+\-]?[0-9]+", $wanted_re);
+ $wanted_re = str_replace("%d", "[0-9]+", $wanted_re);
+ $wanted_re = str_replace("%x", "[0-9a-fA-F]+", $wanted_re);
+ $wanted_re = str_replace("%f", "[+\-]?\.?[0-9]+\.?[0-9]*(E-?[0-9]+)?", $wanted_re);
+ $wanted_re = str_replace("%c", ".", $wanted_re);
+ // %f allows two points "-.0.0" but that is the best *simple* expression
+ }
+
+ /* DEBUG YOUR REGEX HERE
+ var_dump($wanted_re);
+ print(str_repeat('=', 80) . "\n");
+ var_dump($output);
+ */
+ if (!$returnfail && preg_match("/^$wanted_re\$/s", $output)) {
+ if (file_exists($temp_file)) {
+ unlink($temp_file);
+ }
+ if (array_key_exists('FAIL', $section_text)) {
+ break;
+ }
+ if (!isset($this->_options['quiet'])) {
+ $this->_logger->log(0, "PASS $test_nr$tested$info");
+ }
+ if (isset($this->_options['tapoutput'])) {
+ return array('ok', ' - ' . $tested);
+ }
+ return 'PASSED';
+ }
+ } else {
+ if (isset($section_text['EXPECTFILE'])) {
+ $f = $temp_dir . '/' . trim($section_text['EXPECTFILE']);
+ if (!($fp = @fopen($f, 'rb'))) {
+ return PEAR::raiseError('--EXPECTFILE-- section file ' .
+ $f . ' not found');
+ }
+ fclose($fp);
+ $section_text['EXPECT'] = file_get_contents($f);
+ }
+
+ if (isset($section_text['EXPECT'])) {
+ $wanted = preg_replace('/\r\n/', "\n", trim($section_text['EXPECT']));
+ } else {
+ $wanted = '';
+ }
+
+ // compare and leave on success
+ if (!$returnfail && 0 == strcmp($output, $wanted)) {
+ if (file_exists($temp_file)) {
+ unlink($temp_file);
+ }
+ if (array_key_exists('FAIL', $section_text)) {
+ break;
+ }
+ if (!isset($this->_options['quiet'])) {
+ $this->_logger->log(0, "PASS $test_nr$tested$info");
+ }
+ if (isset($this->_options['tapoutput'])) {
+ return array('ok', ' - ' . $tested);
+ }
+ return 'PASSED';
+ }
+ }
+ } while (false);
+
+ if (array_key_exists('FAIL', $section_text)) {
+ // we expect a particular failure
+ // this is only used for testing PEAR_RunTest
+ $expectf = isset($section_text['EXPECTF']) ? $wanted_re : null;
+ $faildiff = $this->generate_diff($wanted, $output, null, $expectf);
+ $faildiff = preg_replace('/\r/', '', $faildiff);
+ $wanted = preg_replace('/\r/', '', trim($section_text['FAIL']));
+ if ($faildiff == $wanted) {
+ if (!isset($this->_options['quiet'])) {
+ $this->_logger->log(0, "PASS $test_nr$tested$info");
+ }
+ if (isset($this->_options['tapoutput'])) {
+ return array('ok', ' - ' . $tested);
+ }
+ return 'PASSED';
+ }
+ unset($section_text['EXPECTF']);
+ $output = $faildiff;
+ if (isset($section_text['RETURNS'])) {
+ return PEAR::raiseError('Cannot have both RETURNS and FAIL in the same test: ' .
+ $file);
+ }
+ }
+
+ // Test failed so we need to report details.
+ $txt = $warn ? 'WARN ' : 'FAIL ';
+ $this->_logger->log(0, $txt . $test_nr . $tested . $info);
+
+ // write .exp
+ $res = $this->_writeLog($exp_filename, $wanted);
+ if (PEAR::isError($res)) {
+ return $res;
+ }
+
+ // write .out
+ $res = $this->_writeLog($output_filename, $output);
+ if (PEAR::isError($res)) {
+ return $res;
+ }
+
+ // write .diff
+ $returns = isset($section_text['RETURNS']) ?
+ array(trim($section_text['RETURNS']), $return_value) : null;
+ $expectf = isset($section_text['EXPECTF']) ? $wanted_re : null;
+ $data = $this->generate_diff($wanted, $output, $returns, $expectf);
+ $res = $this->_writeLog($diff_filename, $data);
+ if (PEAR::isError($res)) {
+ return $res;
+ }
+
+ // write .log
+ $data = "
+---- EXPECTED OUTPUT
+$wanted
+---- ACTUAL OUTPUT
+$output
+---- FAILED
+";
+
+ if ($returnfail) {
+ $data .= "
+---- EXPECTED RETURN
+$section_text[RETURNS]
+---- ACTUAL RETURN
+$return_value
+";
+ }
+
+ $res = $this->_writeLog($log_filename, $data);
+ if (PEAR::isError($res)) {
+ return $res;
+ }
+
+ if (isset($this->_options['tapoutput'])) {
+ $wanted = explode("\n", $wanted);
+ $wanted = "# Expected output:\n#\n#" . implode("\n#", $wanted);
+ $output = explode("\n", $output);
+ $output = "#\n#\n# Actual output:\n#\n#" . implode("\n#", $output);
+ return array($wanted . $output . 'not ok', ' - ' . $tested);
+ }
+ return $warn ? 'WARNED' : 'FAILED';
+ }
+
+ function generate_diff($wanted, $output, $rvalue, $wanted_re)
+ {
+ $w = explode("\n", $wanted);
+ $o = explode("\n", $output);
+ $wr = explode("\n", $wanted_re);
+ $w1 = array_diff_assoc($w, $o);
+ $o1 = array_diff_assoc($o, $w);
+ $o2 = $w2 = array();
+ foreach ($w1 as $idx => $val) {
+ if (!$wanted_re || !isset($wr[$idx]) || !isset($o1[$idx]) ||
+ !preg_match('/^' . $wr[$idx] . '\\z/', $o1[$idx])) {
+ $w2[sprintf("%03d<", $idx)] = sprintf("%03d- ", $idx + 1) . $val;
+ }
+ }
+ foreach ($o1 as $idx => $val) {
+ if (!$wanted_re || !isset($wr[$idx]) ||
+ !preg_match('/^' . $wr[$idx] . '\\z/', $val)) {
+ $o2[sprintf("%03d>", $idx)] = sprintf("%03d+ ", $idx + 1) . $val;
+ }
+ }
+ $diff = array_merge($w2, $o2);
+ ksort($diff);
+ $extra = $rvalue ? "##EXPECTED: $rvalue[0]\r\n##RETURNED: $rvalue[1]" : '';
+ return implode("\r\n", $diff) . $extra;
+ }
+
+ // Write the given text to a temporary file, and return the filename.
+ function save_text($filename, $text)
+ {
+ if (!$fp = fopen($filename, 'w')) {
+ return PEAR::raiseError("Cannot open file '" . $filename . "' (save_text)");
+ }
+ fwrite($fp, $text);
+ fclose($fp);
+ if (1 < DETAILED) echo "
+FILE $filename {{{
+$text
+}}}
+";
+ }
+
+ function _cleanupOldFiles($file)
+ {
+ $temp_dir = realpath(dirname($file));
+ $mainFileName = basename($file, 'phpt');
+ $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'diff';
+ $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'log';
+ $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'exp';
+ $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'out';
+ $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'mem';
+ $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'php';
+ $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'skip.php';
+ $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'clean.php';
+ $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . uniqid('phpt.');
+
+ // unlink old test results
+ @unlink($diff_filename);
+ @unlink($log_filename);
+ @unlink($exp_filename);
+ @unlink($output_filename);
+ @unlink($memcheck_filename);
+ @unlink($temp_file);
+ @unlink($temp_skipif);
+ @unlink($tmp_post);
+ @unlink($temp_clean);
+ }
+
+ function _runSkipIf($section_text, $temp_skipif, $tested, $ini_settings)
+ {
+ $info = '';
+ $warn = false;
+ if (array_key_exists('SKIPIF', $section_text) && trim($section_text['SKIPIF'])) {
+ $this->save_text($temp_skipif, $section_text['SKIPIF']);
+ $output = $this->system_with_timeout("$this->_php $ini_settings -f \"$temp_skipif\"");
+ $output = $output[1];
+ $loutput = ltrim($output);
+ unlink($temp_skipif);
+ if (!strncasecmp('skip', $loutput, 4)) {
+ $skipreason = "SKIP $tested";
+ if (preg_match('/^\s*skip\s*(.+)\s*/i', $output, $m)) {
+ $skipreason .= '(reason: ' . $m[1] . ')';
+ }
+ if (!isset($this->_options['quiet'])) {
+ $this->_logger->log(0, $skipreason);
+ }
+ if (isset($this->_options['tapoutput'])) {
+ return array('ok', ' # skip ' . $reason);
+ }
+ return 'SKIPPED';
+ }
+
+ if (!strncasecmp('info', $loutput, 4)
+ && preg_match('/^\s*info\s*(.+)\s*/i', $output, $m)) {
+ $info = " (info: $m[1])";
+ }
+
+ if (!strncasecmp('warn', $loutput, 4)
+ && preg_match('/^\s*warn\s*(.+)\s*/i', $output, $m)) {
+ $warn = true; /* only if there is a reason */
+ $info = " (warn: $m[1])";
+ }
+ }
+
+ return array('warn' => $warn, 'info' => $info);
+ }
+
+ function _stripHeadersCGI($output)
+ {
+ $this->headers = array();
+ if (!empty($this->_options['cgi']) &&
+ $this->_php == $this->_options['cgi'] &&
+ preg_match("/^(.*?)(?:\n\n(.*)|\\z)/s", $output, $match)) {
+ $output = isset($match[2]) ? trim($match[2]) : '';
+ $this->_headers = $this->_processHeaders($match[1]);
+ }
+
+ return $output;
+ }
+
+ /**
+ * Return an array that can be used with array_diff() to compare headers
+ *
+ * @param string $text
+ */
+ function _processHeaders($text)
+ {
+ $headers = array();
+ $rh = preg_split("/[\n\r]+/", $text);
+ foreach ($rh as $line) {
+ if (strpos($line, ':')!== false) {
+ $line = explode(':', $line, 2);
+ $headers[trim($line[0])] = trim($line[1]);
+ }
+ }
+ return $headers;
+ }
+
+ function _readFile($file)
+ {
+ // Load the sections of the test file.
+ $section_text = array(
+ 'TEST' => '(unnamed test)',
+ 'SKIPIF' => '',
+ 'GET' => '',
+ 'COOKIE' => '',
+ 'POST' => '',
+ 'ARGS' => '',
+ 'INI' => '',
+ 'CLEAN' => '',
+ );
+
+ if (!is_file($file) || !$fp = fopen($file, "r")) {
+ return PEAR::raiseError("Cannot open test file: $file");
+ }
+
+ $section = '';
+ while (!feof($fp)) {
+ $line = fgets($fp);
+
+ // Match the beginning of a section.
+ if (preg_match('/^--([_A-Z]+)--/', $line, $r)) {
+ $section = $r[1];
+ $section_text[$section] = '';
+ continue;
+ } elseif (empty($section)) {
+ fclose($fp);
+ return PEAR::raiseError("Invalid sections formats in test file: $file");
+ }
+
+ // Add to the section text.
+ $section_text[$section] .= $line;
+ }
+ fclose($fp);
+
+ return $section_text;
+ }
+
+ function _writeLog($logname, $data)
+ {
+ if (!$log = fopen($logname, 'w')) {
+ return PEAR::raiseError("Cannot create test log - $logname");
+ }
+ fwrite($log, $data);
+ fclose($log);
+ }
+
+ function _resetEnv($section_text, $temp_file)
+ {
+ $env = $_ENV;
+ $env['REDIRECT_STATUS'] = '';
+ $env['QUERY_STRING'] = '';
+ $env['PATH_TRANSLATED'] = '';
+ $env['SCRIPT_FILENAME'] = '';
+ $env['REQUEST_METHOD'] = '';
+ $env['CONTENT_TYPE'] = '';
+ $env['CONTENT_LENGTH'] = '';
+ if (!empty($section_text['ENV'])) {
+ if (strpos($section_text['ENV'], '{PWD}') !== false) {
+ $section_text['ENV'] = str_replace('{PWD}', dirname($temp_file), $section_text['ENV']);
+ }
+ foreach (explode("\n", trim($section_text['ENV'])) as $e) {
+ $e = explode('=', trim($e), 2);
+ if (!empty($e[0]) && isset($e[1])) {
+ $env[$e[0]] = $e[1];
+ }
+ }
+ }
+ if (array_key_exists('GET', $section_text)) {
+ $env['QUERY_STRING'] = trim($section_text['GET']);
+ } else {
+ $env['QUERY_STRING'] = '';
+ }
+ if (array_key_exists('COOKIE', $section_text)) {
+ $env['HTTP_COOKIE'] = trim($section_text['COOKIE']);
+ } else {
+ $env['HTTP_COOKIE'] = '';
+ }
+ $env['REDIRECT_STATUS'] = '1';
+ $env['PATH_TRANSLATED'] = $temp_file;
+ $env['SCRIPT_FILENAME'] = $temp_file;
+
+ return $env;
+ }
+
+ function _processUpload($section_text, $file)
+ {
+ if (array_key_exists('UPLOAD', $section_text) && !empty($section_text['UPLOAD'])) {
+ $upload_files = trim($section_text['UPLOAD']);
+ $upload_files = explode("\n", $upload_files);
+
+ $request = "Content-Type: multipart/form-data; boundary=---------------------------20896060251896012921717172737\n" .
+ "-----------------------------20896060251896012921717172737\n";
+ foreach ($upload_files as $fileinfo) {
+ $fileinfo = explode('=', $fileinfo);
+ if (count($fileinfo) != 2) {
+ return PEAR::raiseError("Invalid UPLOAD section in test file: $file");
+ }
+ if (!realpath(dirname($file) . '/' . $fileinfo[1])) {
+ return PEAR::raiseError("File for upload does not exist: $fileinfo[1] " .
+ "in test file: $file");
+ }
+ $file_contents = file_get_contents(dirname($file) . '/' . $fileinfo[1]);
+ $fileinfo[1] = basename($fileinfo[1]);
+ $request .= "Content-Disposition: form-data; name=\"$fileinfo[0]\"; filename=\"$fileinfo[1]\"\n";
+ $request .= "Content-Type: text/plain\n\n";
+ $request .= $file_contents . "\n" .
+ "-----------------------------20896060251896012921717172737\n";
+ }
+
+ if (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) {
+ // encode POST raw
+ $post = trim($section_text['POST']);
+ $post = explode('&', $post);
+ foreach ($post as $i => $post_info) {
+ $post_info = explode('=', $post_info);
+ if (count($post_info) != 2) {
+ return PEAR::raiseError("Invalid POST data in test file: $file");
+ }
+ $post_info[0] = rawurldecode($post_info[0]);
+ $post_info[1] = rawurldecode($post_info[1]);
+ $post[$i] = $post_info;
+ }
+ foreach ($post as $post_info) {
+ $request .= "Content-Disposition: form-data; name=\"$post_info[0]\"\n\n";
+ $request .= $post_info[1] . "\n" .
+ "-----------------------------20896060251896012921717172737\n";
+ }
+ unset($section_text['POST']);
+ }
+ $section_text['POST_RAW'] = $request;
+ }
+
+ return $section_text;
+ }
+
+ function _testCleanup($section_text, $temp_clean)
+ {
+ if ($section_text['CLEAN']) {
+ // perform test cleanup
+ $this->save_text($temp_clean, $section_text['CLEAN']);
+ $output = $this->system_with_timeout("$this->_php \"$temp_clean\" 2>&1");
+ if (strlen($output[1])) {
+ echo "BORKED --CLEAN-- section! output:\n", $output[1];
+ }
+ if (file_exists($temp_clean)) {
+ unlink($temp_clean);
+ }
+ }
+ }
+}
diff --git a/includes/pear/PEAR/Start.php b/includes/pear/PEAR/Start.php
new file mode 100644
index 0000000..da69246
--- /dev/null
+++ b/includes/pear/PEAR/Start.php
@@ -0,0 +1,427 @@
+<?php
+require_once 'PEAR.php';
+require_once 'System.php';
+require_once 'PEAR/Config.php';
+require_once 'PEAR/Command.php';
+require_once 'PEAR/Common.php';
+class PEAR_Start extends PEAR
+{
+ var $bin_dir;
+ var $data_dir;
+ var $cfg_dir;
+ var $www_dir;
+ var $install_pfc;
+ var $corePackages =
+ array(
+ 'Archive_Tar',
+ 'Console_Getopt',
+ 'PEAR',
+ 'Structures_Graph',
+ 'XML_Util',
+ );
+ var $local_dir = array();
+ var $origpwd;
+ var $pfc_packages = array(
+ 'DB',
+ 'Net_Socket',
+ 'Net_SMTP',
+ 'Mail',
+ 'XML_Parser',
+ 'XML_RPC',
+ 'PHPUnit'
+ );
+ var $php_dir;
+ var $php_bin;
+ var $pear_conf;
+ var $validPHPBin = false;
+ var $test_dir;
+ var $download_dir;
+ var $temp_dir;
+ var $config =
+ array(
+ 'prefix',
+ 'bin_dir',
+ 'php_dir',
+ 'doc_dir',
+ 'data_dir',
+ 'cfg_dir',
+ 'www_dir',
+ 'test_dir',
+ 'temp_dir',
+ 'download_dir',
+ 'pear_conf',
+ );
+ var $prefix;
+ var $progress = 0;
+ var $configPrompt =
+ array(
+ 'prefix' => 'Installation base ($prefix)',
+ 'temp_dir' => 'Temporary directory for processing',
+ 'download_dir' => 'Temporary directory for downloads',
+ 'bin_dir' => 'Binaries directory',
+ 'php_dir' => 'PHP code directory ($php_dir)',
+ 'doc_dir' => 'Documentation directory',
+ 'data_dir' => 'Data directory',
+ 'cfg_dir' => 'User-modifiable configuration files directory',
+ 'www_dir' => 'Public Web Files directory',
+ 'test_dir' => 'Tests directory',
+ 'pear_conf' => 'Name of configuration file',
+ );
+
+ var $localInstall;
+ var $PEARConfig;
+ var $tarball = array();
+
+ function PEAR_Start()
+ {
+ parent::PEAR();
+ if (OS_WINDOWS) {
+ $this->configPrompt['php_bin'] = 'Path to CLI php.exe';
+ $this->config[] = 'php_bin';
+ $this->prefix = getcwd();
+
+ if (!@is_dir($this->prefix)) {
+ if (@is_dir('c:\php5')) {
+ $this->prefix = 'c:\php5';
+ } elseif (@is_dir('c:\php4')) {
+ $this->prefix = 'c:\php4';
+ } elseif (@is_dir('c:\php')) {
+ $this->prefix = 'c:\php';
+ }
+ }
+
+ $slash = "\\";
+ if (strrpos($this->prefix, '\\') === (strlen($this->prefix) - 1)) {
+ $slash = '';
+ }
+
+ $this->localInstall = false;
+ $this->bin_dir = '$prefix';
+ $this->temp_dir = '$prefix' . $slash . 'tmp';
+ $this->download_dir = '$prefix' . $slash . 'tmp';
+ $this->php_dir = '$prefix' . $slash . 'pear';
+ $this->doc_dir = '$prefix' . $slash . 'docs';
+ $this->data_dir = '$prefix' . $slash . 'data';
+ $this->test_dir = '$prefix' . $slash . 'tests';
+ $this->www_dir = '$prefix' . $slash . 'www';
+ $this->cfg_dir = '$prefix' . $slash . 'cfg';
+ $this->pear_conf = PEAR_CONFIG_SYSCONFDIR . '\\pear.ini';
+ /*
+ * Detects php.exe
+ */
+ $this->validPHPBin = true;
+ if ($t = $this->safeGetenv('PHP_PEAR_PHP_BIN')) {
+ $this->php_bin = dirname($t);
+ } elseif ($t = $this->safeGetenv('PHP_BIN')) {
+ $this->php_bin = dirname($t);
+ } elseif ($t = System::which('php')) {
+ $this->php_bin = dirname($t);
+ } elseif (is_file($this->prefix . '\cli\php.exe')) {
+ $this->php_bin = $this->prefix . '\cli';
+ } elseif (is_file($this->prefix . '\php.exe')) {
+ $this->php_bin = $this->prefix;
+ }
+ $phpexe = OS_WINDOWS ? '\\php.exe' : '/php';
+ if ($this->php_bin && !is_file($this->php_bin . $phpexe)) {
+ $this->php_bin = '';
+ } else {
+ if (strpos($this->php_bin, ':') === 0) {
+ $this->php_bin = getcwd() . DIRECTORY_SEPARATOR . $this->php_bin;
+ }
+ }
+ if (!is_file($this->php_bin . $phpexe)) {
+ if (is_file('c:/php/cli/php.exe')) {
+ $this->php_bin = 'c"\\php\\cli';
+ } elseif (is_file('c:/php5/php.exe')) {
+ $this->php_bin = 'c:\\php5';
+ } elseif (is_file('c:/php4/cli/php.exe')) {
+ $this->php_bin = 'c:\\php4\\cli';
+ } else {
+ $this->validPHPBin = false;
+ }
+ }
+ } else {
+ $this->prefix = dirname(PHP_BINDIR);
+ $this->pear_conf = PEAR_CONFIG_SYSCONFDIR . '/pear.conf';
+ if (get_current_user() != 'root') {
+ $this->prefix = $this->safeGetenv('HOME') . '/pear';
+ $this->pear_conf = $this->safeGetenv('HOME') . '.pearrc';
+ }
+ $this->bin_dir = '$prefix/bin';
+ $this->php_dir = '$prefix/share/pear';
+ $this->temp_dir = '/tmp/pear/install';
+ $this->download_dir = '/tmp/pear/install';
+ $this->doc_dir = '$prefix/docs';
+ $this->www_dir = '$prefix/www';
+ $this->cfg_dir = '$prefix/cfg';
+ $this->data_dir = '$prefix/data';
+ $this->test_dir = '$prefix/tests';
+ // check if the user has installed PHP with PHP or GNU layout
+ if (@is_dir("$this->prefix/lib/php/.registry")) {
+ $this->php_dir = '$prefix/lib/php';
+ } elseif (@is_dir("$this->prefix/share/pear/lib/.registry")) {
+ $this->php_dir = '$prefix/share/pear/lib';
+ $this->doc_dir = '$prefix/share/pear/docs';
+ $this->data_dir = '$prefix/share/pear/data';
+ $this->test_dir = '$prefix/share/pear/tests';
+ } elseif (@is_dir("$this->prefix/share/php/.registry")) {
+ $this->php_dir = '$prefix/share/php';
+ }
+ }
+ }
+
+ function safeGetenv($var)
+ {
+ if (is_array($_ENV) && isset($_ENV[$var])) {
+ return $_ENV[$var];
+ }
+
+ return getenv($var);
+ }
+
+ function show($stuff)
+ {
+ print $stuff;
+ }
+
+ function locatePackagesToInstall()
+ {
+ $dp = @opendir(dirname(__FILE__) . '/go-pear-tarballs');
+ if (empty($dp)) {
+ return PEAR::raiseError("while locating packages to install: opendir('" .
+ dirname(__FILE__) . "/go-pear-tarballs') failed");
+ }
+
+ $potentials = array();
+ while (false !== ($entry = readdir($dp))) {
+ if ($entry{0} == '.' || !in_array(substr($entry, -4), array('.tar', '.tgz'))) {
+ continue;
+ }
+ $potentials[] = $entry;
+ }
+
+ closedir($dp);
+ $notfound = array();
+ foreach ($this->corePackages as $package) {
+ foreach ($potentials as $i => $candidate) {
+ if (preg_match('/^' . $package . '-' . _PEAR_COMMON_PACKAGE_VERSION_PREG
+ . '\.(tar|tgz)\\z/', $candidate)) {
+ $this->tarball[$package] = dirname(__FILE__) . '/go-pear-tarballs/' . $candidate;
+ unset($potentials[$i]);
+ continue 2;
+ }
+ }
+
+ $notfound[] = $package;
+ }
+
+ if (count($notfound)) {
+ return PEAR::raiseError("No tarballs found for core packages: " .
+ implode(', ', $notfound));
+ }
+
+ $this->tarball = array_merge($this->tarball, $potentials);
+ }
+
+ function setupTempStuff()
+ {
+ if (!($this->ptmp = System::mktemp(array('-d')))) {
+ $this->show("System's Tempdir failed, trying to use \$prefix/tmp ...");
+ $res = System::mkDir(array($this->prefix . '/tmp'));
+ if (!$res) {
+ return PEAR::raiseError('mkdir ' . $this->prefix . '/tmp ... failed');
+ }
+
+ $_temp = tempnam($this->prefix . '/tmp', 'gope');
+ System::rm(array('-rf', $_temp));
+ System::mkdir(array('-p','-m', '0700', $_temp));
+ $this->ptmp = $this->prefix . '/tmp';
+ $ok = @chdir($this->ptmp);
+
+ if (!$ok) { // This should not happen, really ;)
+ $this->bail('chdir ' . $this->ptmp . ' ... failed');
+ }
+
+ print "ok\n";
+
+ // Adjust TEMPDIR envvars
+ if (!isset($_ENV)) {
+ $_ENV = array();
+ };
+ $_ENV['TMPDIR'] = $_ENV['TEMP'] = $this->prefix . '/tmp';
+ }
+
+ return @chdir($this->ptmp);
+ }
+
+ /**
+ * Try to detect the kind of SAPI used by the
+ * the given php.exe.
+ * @author Pierrre-Alain Joye
+ */
+ function win32DetectPHPSAPI()
+ {
+ if ($this->php_bin != '') {
+ if (OS_WINDOWS) {
+ exec('"' . $this->php_bin . '\\php.exe" -v', $res);
+ } else {
+ exec('"' . $this->php_bin . '/php" -v', $res);
+ }
+
+ if (is_array($res)) {
+ if (isset($res[0]) && strpos($res[0],"(cli)")) {
+ return 'cli';
+ }
+
+ if (isset($res[0]) && strpos($res[0],"cgi")) {
+ return 'cgi';
+ }
+
+ if (isset($res[0]) && strpos($res[0],"cgi-fcgi")) {
+ return 'cgi';
+ }
+
+ return 'unknown';
+ }
+ }
+
+ return 'unknown';
+ }
+
+ function doInstall()
+ {
+ print "Beginning install...\n";
+ // finish php_bin config
+ if (OS_WINDOWS) {
+ $this->php_bin .= '\\php.exe';
+ } else {
+ $this->php_bin .= '/php';
+ }
+ $this->PEARConfig = &PEAR_Config::singleton($this->pear_conf, $this->pear_conf);
+ $this->PEARConfig->set('preferred_state', 'stable');
+ foreach ($this->config as $var) {
+ if ($var == 'pear_conf' || $var == 'prefix') {
+ continue;
+ }
+ $this->PEARConfig->set($var, $this->$var);
+ }
+
+ $this->PEARConfig->store();
+// $this->PEARConfig->set('verbose', 6);
+ print "Configuration written to $this->pear_conf...\n";
+ $this->registry = &$this->PEARConfig->getRegistry();
+ print "Initialized registry...\n";
+ $install = &PEAR_Command::factory('install', $this->PEARConfig);
+ print "Preparing to install...\n";
+ $options = array(
+ 'nodeps' => true,
+ 'force' => true,
+ 'upgrade' => true,
+ );
+ foreach ($this->tarball as $pkg => $src) {
+ print "installing $src...\n";
+ }
+ $install->run('install', $options, array_values($this->tarball));
+ }
+
+ function postProcessConfigVars()
+ {
+ foreach ($this->config as $n => $var) {
+ for ($m = 1; $m <= count($this->config); $m++) {
+ $var2 = $this->config[$m];
+ $this->$var = str_replace('$'.$var2, $this->$var2, $this->$var);
+ }
+ }
+
+ foreach ($this->config as $var) {
+ $dir = $this->$var;
+
+ if (!preg_match('/_dir\\z/', $var)) {
+ continue;
+ }
+
+ if (!@is_dir($dir)) {
+ if (!System::mkDir(array('-p', $dir))) {
+ $root = OS_WINDOWS ? 'administrator' : 'root';
+ return PEAR::raiseError("Unable to create {$this->configPrompt[$var]} $dir.
+Run this script as $root or pick another location.\n");
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the php.ini file used with the current
+ * process or with the given php.exe
+ *
+ * Horrible hack, but well ;)
+ *
+ * Not used yet, will add the support later
+ * @author Pierre-Alain Joye <paj@pearfr.org>
+ */
+ function getPhpiniPath()
+ {
+ $pathIni = get_cfg_var('cfg_file_path');
+ if ($pathIni && is_file($pathIni)) {
+ return $pathIni;
+ }
+
+ // Oh well, we can keep this too :)
+ // I dunno if get_cfg_var() is safe on every OS
+ if (OS_WINDOWS) {
+ // on Windows, we can be pretty sure that there is a php.ini
+ // file somewhere
+ do {
+ $php_ini = PHP_CONFIG_FILE_PATH . DIRECTORY_SEPARATOR . 'php.ini';
+ if (@file_exists($php_ini)) {
+ break;
+ }
+ $php_ini = 'c:\winnt\php.ini';
+ if (@file_exists($php_ini)) {
+ break;
+ }
+ $php_ini = 'c:\windows\php.ini';
+ } while (false);
+ } else {
+ $php_ini = PHP_CONFIG_FILE_PATH . DIRECTORY_SEPARATOR . 'php.ini';
+ }
+
+ if (@is_file($php_ini)) {
+ return $php_ini;
+ }
+
+ // We re running in hackz&troubles :)
+ ob_implicit_flush(false);
+ ob_start();
+ phpinfo(INFO_GENERAL);
+ $strInfo = ob_get_contents();
+ ob_end_clean();
+ ob_implicit_flush(true);
+
+ if (php_sapi_name() != 'cli') {
+ $strInfo = strip_tags($strInfo,'<td>');
+ $arrayInfo = explode("</td>", $strInfo );
+ $cli = false;
+ } else {
+ $arrayInfo = explode("\n", $strInfo);
+ $cli = true;
+ }
+
+ foreach ($arrayInfo as $val) {
+ if (strpos($val,"php.ini")) {
+ if ($cli) {
+ list(,$pathIni) = explode('=>', $val);
+ } else {
+ $pathIni = strip_tags(trim($val));
+ }
+ $pathIni = trim($pathIni);
+ if (is_file($pathIni)) {
+ return $pathIni;
+ }
+ }
+ }
+
+ return false;
+ }
+}
+?>
diff --git a/includes/pear/PEAR/Start/CLI.php b/includes/pear/PEAR/Start/CLI.php
new file mode 100644
index 0000000..a5ea9c5
--- /dev/null
+++ b/includes/pear/PEAR/Start/CLI.php
@@ -0,0 +1,616 @@
+<?php
+require_once 'PEAR/Start.php';
+class PEAR_Start_CLI extends PEAR_Start
+{
+
+ var $descLength;
+ var $descFormat;
+ var $first;
+ var $last;
+ var $origpwd;
+ var $tty;
+
+ function PEAR_Start_CLI()
+ {
+ parent::PEAR_Start();
+ ini_set('html_errors', 0);
+ define('WIN32GUI', OS_WINDOWS && php_sapi_name() == 'cli' && System::which('cscript'));
+ $this->tty = OS_WINDOWS ? @fopen('\con', 'r') : @fopen('/dev/tty', 'r');
+
+ if (!$this->tty) {
+ $this->tty = fopen('php://stdin', 'r');
+ }
+ $this->origpwd = getcwd();
+ $this->config = array_keys($this->configPrompt);
+
+ // make indices run from 1...
+ array_unshift($this->config, "");
+ unset($this->config[0]);
+ reset($this->config);
+ $this->descLength = max(array_map('strlen', $this->configPrompt));
+ $this->descFormat = "%-{$this->descLength}s";
+ $this->first = key($this->config);
+ end($this->config);
+ $this->last = key($this->config);
+ PEAR_Command::setFrontendType('CLI');
+ }
+
+ function _PEAR_Start_CLI()
+ {
+ if ($this->tty) {
+ @fclose($this->tty);
+ }
+ }
+
+ function run()
+ {
+ if (PEAR::isError($err = $this->locatePackagesToInstall())) {
+ return $err;
+ }
+ $this->startupQuestion();
+ $this->setupTempStuff();
+ $this->getInstallLocations();
+ $this->displayPreamble();
+ if (PEAR::isError($err = $this->postProcessConfigVars())) {
+ return $err;
+ }
+ $this->doInstall();
+ $this->finishInstall();
+ }
+
+ function startupQuestion()
+ {
+ if (OS_WINDOWS) {
+ print "
+Are you installing a system-wide PEAR or a local copy?
+(system|local) [system] : ";
+ $tmp = trim(fgets($this->tty, 1024));
+ if (!empty($tmp) && strtolower($tmp) !== 'system') {
+ print "Please confirm local copy by typing 'yes' : ";
+ $tmp = trim(fgets($this->tty, 1024));
+ if (strtolower($tmp) == 'yes') {
+ $slash = "\\";
+ if (strrpos($this->prefix, '\\') === (strlen($this->prefix) - 1)) {
+ $slash = '';
+ }
+
+ $this->localInstall = true;
+ $this->pear_conf = '$prefix' . $slash . 'pear.ini';
+ }
+ }
+ } else {
+ if (get_current_user() == 'root') {
+ return;
+ }
+ $this->pear_conf = $this->safeGetenv('HOME') . '/.pearrc';
+ }
+ }
+
+ function getInstallLocations()
+ {
+ while (true) {
+ print "
+Below is a suggested file layout for your new PEAR installation. To
+change individual locations, type the number in front of the
+directory. Type 'all' to change all of them or simply press Enter to
+accept these locations.
+
+";
+
+ foreach ($this->config as $n => $var) {
+ $fullvar = $this->$var;
+ foreach ($this->config as $blah => $unused) {
+ foreach ($this->config as $m => $var2) {
+ $fullvar = str_replace('$'.$var2, $this->$var2, $fullvar);
+ }
+ }
+ printf("%2d. $this->descFormat : %s\n", $n, $this->configPrompt[$var], $fullvar);
+ }
+
+ print "\n$this->first-$this->last, 'all' or Enter to continue: ";
+ $tmp = trim(fgets($this->tty, 1024));
+ if (empty($tmp)) {
+ if (OS_WINDOWS && !$this->validPHPBin) {
+ echo "**ERROR**
+Please, enter the php.exe path.
+
+";
+ } else {
+ break;
+ }
+ }
+
+ if (isset($this->config[(int)$tmp])) {
+ $var = $this->config[(int)$tmp];
+ $desc = $this->configPrompt[$var];
+ $current = $this->$var;
+ if (WIN32GUI && $var != 'pear_conf'){
+ $tmp = $this->win32BrowseForFolder("Choose a Folder for $desc [$current] :");
+ $tmp.= '\\';
+ } else {
+ print "(Use \$prefix as a shortcut for '$this->prefix', etc.)
+$desc [$current] : ";
+ $tmp = trim(fgets($this->tty, 1024));
+ }
+ $old = $this->$var;
+ $this->$var = $$var = $tmp;
+ if (OS_WINDOWS && $var=='php_bin') {
+ if ($this->validatePhpExecutable($tmp)) {
+ $this->php_bin = $tmp;
+ } else {
+ $this->php_bin = $old;
+ }
+ }
+ } elseif ($tmp == 'all') {
+ foreach ($this->config as $n => $var) {
+ $desc = $this->configPrompt[$var];
+ $current = $this->$var;
+ print "$desc [$current] : ";
+ $tmp = trim(fgets($this->tty, 1024));
+ if (!empty($tmp)) {
+ $this->$var = $tmp;
+ }
+ }
+ }
+ }
+ }
+
+ function validatePhpExecutable($tmp)
+ {
+ if (OS_WINDOWS) {
+ if (strpos($tmp, 'php.exe')) {
+ $tmp = str_replace('php.exe', '', $tmp);
+ }
+ if (file_exists($tmp . DIRECTORY_SEPARATOR . 'php.exe')) {
+ $tmp = $tmp . DIRECTORY_SEPARATOR . 'php.exe';
+ $this->php_bin_sapi = $this->win32DetectPHPSAPI();
+ if ($this->php_bin_sapi=='cgi'){
+ print "
+******************************************************************************
+NOTICE! We found php.exe under $this->php_bin, it uses a $this->php_bin_sapi SAPI.
+PEAR commandline tool works well with it.
+If you have a CLI php.exe available, we recommend using it.
+
+Press Enter to continue...";
+ $tmp = trim(fgets($this->tty, 1024));
+ } elseif ($this->php_bin_sapi=='unknown') {
+ print "
+******************************************************************************
+WARNING! We found php.exe under $this->php_bin, it uses an $this->php_bin_sapi SAPI.
+PEAR commandline tool has NOT been tested with it.
+If you have a CLI (or CGI) php.exe available, we strongly recommend using it.
+
+Press Enter to continue...";
+ $tmp = trim(fgets($this->tty, 1024));
+ }
+ echo "php.exe (sapi: $this->php_bin_sapi) found.\n\n";
+ return $this->validPHPBin = true;
+ } else {
+ echo "**ERROR**: not a folder, or no php.exe found in this folder.
+Press Enter to continue...";
+ $tmp = trim(fgets($this->tty, 1024));
+ return $this->validPHPBin = false;
+ }
+ }
+ }
+
+ /**
+ * Create a vbs script to browse the getfolder dialog, called
+ * by cscript, if it's available.
+ * $label is the label text in the header of the dialog box
+ *
+ * TODO:
+ * - Do not show Control panel
+ * - Replace WSH with calls to w32 as soon as callbacks work
+ * @author Pierrre-Alain Joye
+ */
+ function win32BrowseForFolder($label)
+ {
+ static $wshSaved=false;
+ static $cscript='';
+ $wsh_browserfolder = 'Option Explicit
+Dim ArgObj, var1, var2, sa, sFld
+Set ArgObj = WScript.Arguments
+Const BIF_EDITBOX = &H10
+Const BIF_NEWDIALOGSTYLE = &H40
+Const BIF_RETURNONLYFSDIRS = &H0001
+Const BIF_DONTGOBELOWDOMAIN = &H0002
+Const BIF_STATUSTEXT = &H0004
+Const BIF_RETURNFSANCESTORS = &H0008
+Const BIF_VALIDATE = &H0020
+Const BIF_BROWSEFORCOMPUTER = &H1000
+Const BIF_BROWSEFORPRINTER = &H2000
+Const BIF_BROWSEINCLUDEFILES = &H4000
+Const OFN_LONGNAMES = &H200000
+Const OFN_NOLONGNAMES = &H40000
+Const ssfDRIVES = &H11
+Const ssfNETWORK = &H12
+Set sa = CreateObject("Shell.Application")
+var1=ArgObj(0)
+Set sFld = sa.BrowseForFolder(0, var1, BIF_EDITBOX + BIF_VALIDATE + BIF_BROWSEINCLUDEFILES + BIF_RETURNFSANCESTORS+BIF_NEWDIALOGSTYLE , ssfDRIVES )
+if not sFld is nothing Then
+ if not left(sFld.items.item.path,1)=":" Then
+ WScript.Echo sFld.items.item.path
+ Else
+ WScript.Echo "invalid"
+ End If
+Else
+ WScript.Echo "cancel"
+End If
+';
+ if( !$wshSaved){
+ $cscript = $this->ptmp . DIRECTORY_SEPARATOR . "bf.vbs";
+ $fh = fopen($cscript, "wb+");
+ fwrite($fh, $wsh_browserfolder, strlen($wsh_browserfolder));
+ fclose($fh);
+ $wshSaved = true;
+ }
+
+ exec('cscript ' . escapeshellarg($cscript) . ' "' . escapeshellarg($label) . '" //noLogo', $arPath);
+ if (!count($arPath) || $arPath[0]=='' || $arPath[0]=='cancel') {
+ return '';
+ } elseif ($arPath[0]=='invalid') {
+ echo "Invalid Path.\n";
+ return '';
+ }
+
+ @unlink($cscript);
+ return $arPath[0];
+ }
+
+ function displayPreamble()
+ {
+ if (OS_WINDOWS) {
+ /*
+ * Checks PHP SAPI version under windows/CLI
+ */
+ if ($this->php_bin == '') {
+ print "
+We do not find any php.exe, please select the php.exe folder (CLI is
+recommended, usually in c:\php\cli\php.exe)
+";
+ $this->validPHPBin = false;
+ } elseif (strlen($this->php_bin)) {
+ $this->php_bin_sapi = $this->win32DetectPHPSAPI();
+ $this->validPHPBin = true;
+ switch ($this->php_bin_sapi) {
+ case 'cli':
+ break;
+ case 'cgi':
+ case 'cgi-fcgi':
+ print "
+*NOTICE*
+We found php.exe under $this->php_bin, it uses a $this->php_bin_sapi SAPI. PEAR commandline
+tool works well with it, if you have a CLI php.exe available, we
+recommend using it.
+";
+ break;
+ default:
+ print "
+*WARNING*
+We found php.exe under $this->php_bin, it uses an unknown SAPI. PEAR commandline
+tool has not been tested with it, if you have a CLI (or CGI) php.exe available,
+we strongly recommend using it.
+
+";
+ break;
+ }
+ }
+ }
+ }
+
+ function finishInstall()
+ {
+ $sep = OS_WINDOWS ? ';' : ':';
+ $include_path = explode($sep, ini_get('include_path'));
+ if (OS_WINDOWS) {
+ $found = false;
+ $t = strtolower($this->php_dir);
+ foreach ($include_path as $path) {
+ if ($t == strtolower($path)) {
+ $found = true;
+ break;
+ }
+ }
+ } else {
+ $found = in_array($this->php_dir, $include_path);
+ }
+ if (!$found) {
+ print "
+******************************************************************************
+WARNING! The include_path defined in the currently used php.ini does not
+contain the PEAR PHP directory you just specified:
+<$this->php_dir>
+If the specified directory is also not in the include_path used by
+your scripts, you will have problems getting any PEAR packages working.
+";
+
+ if ($php_ini = $this->getPhpiniPath()) {
+ print "\n\nWould you like to alter php.ini <$php_ini>? [Y/n] : ";
+ $alter_phpini = !stristr(fgets($this->tty, 1024), "n");
+ if ($alter_phpini) {
+ $this->alterPhpIni($php_ini);
+ } else {
+ if (OS_WINDOWS) {
+ print "
+Please look over your php.ini file to make sure
+$this->php_dir is in your include_path.";
+ } else {
+ print "
+I will add a workaround for this in the 'pear' command to make sure
+the installer works, but please look over your php.ini or Apache
+configuration to make sure $this->php_dir is in your include_path.
+";
+ }
+ }
+ }
+
+ print "
+Current include path : ".ini_get('include_path')."
+Configured directory : $this->php_dir
+Currently used php.ini (guess) : $php_ini
+";
+
+ print "Press Enter to continue: ";
+ fgets($this->tty, 1024);
+ }
+
+ $pear_cmd = $this->bin_dir . DIRECTORY_SEPARATOR . 'pear';
+ $pear_cmd = OS_WINDOWS ? strtolower($pear_cmd).'.bat' : $pear_cmd;
+
+ // check that the installed pear and the one in the path are the same (if any)
+ $pear_old = System::which(OS_WINDOWS ? 'pear.bat' : 'pear', $this->bin_dir);
+ if ($pear_old && ($pear_old != $pear_cmd)) {
+ // check if it is a link or symlink
+ $islink = OS_WINDOWS ? false : is_link($pear_old) ;
+ if ($islink && readlink($pear_old) != $pear_cmd) {
+ print "\n** WARNING! The link $pear_old does not point to the " .
+ "installed $pear_cmd\n";
+ } elseif (!$this->localInstall && is_writable($pear_old) && !is_dir($pear_old)) {
+ rename($pear_old, "{$pear_old}_old");
+ print "\n** WARNING! Backed up old pear to {$pear_old}_old\n";
+ } else {
+ print "\n** WARNING! Old version found at $pear_old, please remove it or ".
+ "be sure to use the new $pear_cmd command\n";
+ }
+ }
+
+ print "\nThe 'pear' command is now at your service at $pear_cmd\n";
+
+ // Alert the user if the pear cmd is not in PATH
+ $old_dir = $pear_old ? dirname($pear_old) : false;
+ if (!$this->which('pear', $old_dir)) {
+ print "
+** The 'pear' command is not currently in your PATH, so you need to
+** use '$pear_cmd' until you have added
+** '$this->bin_dir' to your PATH environment variable.
+
+";
+
+ print "Run it without parameters to see the available actions, try 'pear list'
+to see what packages are installed, or 'pear help' for help.
+
+For more information about PEAR, see:
+
+ http://pear.php.net/faq.php
+ http://pear.php.net/manual/
+
+Thanks for using go-pear!
+
+";
+ }
+
+ if (OS_WINDOWS && !$this->localInstall) {
+ $this->win32CreateRegEnv();
+ }
+ }
+
+ /**
+ * System::which() does not allow path exclusion
+ */
+ function which($program, $dont_search_in = false)
+ {
+ if (OS_WINDOWS) {
+ if ($_path = $this->safeGetEnv('Path')) {
+ $dirs = explode(';', $_path);
+ } else {
+ $dirs = explode(';', $this->safeGetEnv('PATH'));
+ }
+ foreach ($dirs as $i => $dir) {
+ $dirs[$i] = strtolower(realpath($dir));
+ }
+ if ($dont_search_in) {
+ $dont_search_in = strtolower(realpath($dont_search_in));
+ }
+ if ($dont_search_in &&
+ ($key = array_search($dont_search_in, $dirs)) !== false)
+ {
+ unset($dirs[$key]);
+ }
+
+ foreach ($dirs as $dir) {
+ $dir = str_replace('\\\\', '\\', $dir);
+ if (!strlen($dir)) {
+ continue;
+ }
+ if ($dir{strlen($dir) - 1} != '\\') {
+ $dir .= '\\';
+ }
+ $tmp = $dir . $program;
+ $info = pathinfo($tmp);
+ if (isset($info['extension']) && in_array(strtolower($info['extension']),
+ array('exe', 'com', 'bat', 'cmd'))) {
+ if (file_exists($tmp)) {
+ return strtolower($tmp);
+ }
+ } elseif (file_exists($ret = $tmp . '.exe') ||
+ file_exists($ret = $tmp . '.com') ||
+ file_exists($ret = $tmp . '.bat') ||
+ file_exists($ret = $tmp . '.cmd')) {
+ return strtolower($ret);
+ }
+ }
+ } else {
+ $dirs = explode(':', $this->safeGetEnv('PATH'));
+ if ($dont_search_in &&
+ ($key = array_search($dont_search_in, $dirs)) !== false)
+ {
+ unset($dirs[$key]);
+ }
+ foreach ($dirs as $dir) {
+ if (is_executable("$dir/$program")) {
+ return "$dir/$program";
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Not optimized, but seems to work, if some nice
+ * peardev will test it? :)
+ *
+ * @author Pierre-Alain Joye <paj@pearfr.org>
+ */
+ function alterPhpIni($pathIni='')
+ {
+ $foundAt = array();
+ $iniSep = OS_WINDOWS ? ';' : ':';
+
+ if ($pathIni=='') {
+ $pathIni = $this->getPhpiniPath();
+ }
+
+ $arrayIni = file($pathIni);
+ $i=0;
+ $found=0;
+
+ // Looks for each active include_path directives
+ foreach ($arrayIni as $iniLine) {
+ $iniLine = trim($iniLine);
+ $iniLine = str_replace(array("\n", "\r"), array('', ''), $iniLine);
+ if (preg_match("/^\s*include_path/", $iniLine)) {
+ $foundAt[] = $i;
+ $found++;
+ }
+ $i++;
+ }
+
+ if ($found) {
+ $includeLine = $arrayIni[$foundAt[0]];
+ list(, $currentPath) = explode('=', $includeLine);
+
+ $currentPath = trim($currentPath);
+ if (substr($currentPath,0,1) == '"') {
+ $currentPath = substr($currentPath, 1, strlen($currentPath) - 2);
+ }
+
+ $arrayPath = explode($iniSep, $currentPath);
+ $newPath = array();
+ if ($arrayPath[0]=='.') {
+ $newPath[0] = '.';
+ $newPath[1] = $this->php_dir;
+ array_shift($arrayPath);
+ } else {
+ $newPath[0] = $this->php_dir;
+ }
+
+ foreach ($arrayPath as $path) {
+ $newPath[]= $path;
+ }
+ } else {
+ $newPath = array();
+ $newPath[0] = '.';
+ $newPath[1] = $this->php_dir;
+ $foundAt[] = count($arrayIni); // add a new line if none is present
+ }
+ $nl = OS_WINDOWS ? "\r\n" : "\n";
+ $includepath = 'include_path="' . implode($iniSep,$newPath) . '"';
+ $newInclude = "$nl$nl;***** Added by go-pear$nl" .
+ $includepath .
+ $nl . ";*****" .
+ $nl . $nl;
+
+ $arrayIni[$foundAt[0]] = $newInclude;
+
+ for ($i=1; $i<$found; $i++) {
+ $arrayIni[$foundAt[$i]]=';' . trim($arrayIni[$foundAt[$i]]);
+ }
+
+ $newIni = implode("", $arrayIni);
+ if (!($fh = @fopen($pathIni, "wb+"))) {
+ $prefixIni = $this->prefix . DIRECTORY_SEPARATOR . "php.ini-gopear";
+ $fh = fopen($prefixIni, "wb+");
+ if (!$fh) {
+ echo "
+******************************************************************************
+WARNING: Cannot write to $pathIni nor in $this->prefix/php.ini-gopear. Please
+modify manually your php.ini by adding:
+
+$includepath
+
+";
+ return false;
+ } else {
+ fwrite($fh, $newIni, strlen($newIni));
+ fclose($fh);
+ echo "
+******************************************************************************
+WARNING: Cannot write to $pathIni, but php.ini was successfully created
+at <$this->prefix/php.ini-gopear>. Please replace the file <$pathIni> with
+<$prefixIni> or modify your php.ini by adding:
+
+$includepath
+
+";
+
+ }
+ } else {
+ fwrite($fh, $newIni, strlen($newIni));
+ fclose($fh);
+ echo "
+php.ini <$pathIni> include_path updated.
+";
+ }
+ return true;
+ }
+
+ /**
+ * Generates a registry addOn for Win32 platform
+ * This addon set PEAR environment variables
+ * @author Pierrre-Alain Joye
+ */
+ function win32CreateRegEnv()
+ {
+ $nl = "\r\n";
+ $reg ='REGEDIT4'.$nl.
+ '[HKEY_CURRENT_USER\Environment]'. $nl .
+ '"PHP_PEAR_SYSCONF_DIR"="' . addslashes($this->prefix) . '"' . $nl .
+ '"PHP_PEAR_INSTALL_DIR"="' . addslashes($this->php_dir) . '"' . $nl .
+ '"PHP_PEAR_DOC_DIR"="' . addslashes($this->doc_dir) . '"' . $nl .
+ '"PHP_PEAR_BIN_DIR"="' . addslashes($this->bin_dir) . '"' . $nl .
+ '"PHP_PEAR_DATA_DIR"="' . addslashes($this->data_dir) . '"' . $nl .
+ '"PHP_PEAR_PHP_BIN"="' . addslashes($this->php_bin) . '"' . $nl .
+ '"PHP_PEAR_TEST_DIR"="' . addslashes($this->test_dir) . '"' . $nl;
+
+ $fh = fopen($this->prefix . DIRECTORY_SEPARATOR . 'PEAR_ENV.reg', 'wb');
+ if($fh){
+ fwrite($fh, $reg, strlen($reg));
+ fclose($fh);
+ echo "
+
+* WINDOWS ENVIRONMENT VARIABLES *
+For convenience, a REG file is available under {$this->prefix}PEAR_ENV.reg .
+This file creates ENV variables for the current user.
+
+Double-click this file to add it to the current user registry.
+
+";
+ }
+ }
+
+ function displayHTMLProgress()
+ {
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Task/Common.php b/includes/pear/PEAR/Task/Common.php
new file mode 100644
index 0000000..0ca5ccf
--- /dev/null
+++ b/includes/pear/PEAR/Task/Common.php
@@ -0,0 +1,202 @@
+<?php
+/**
+ * PEAR_Task_Common, base class for installer tasks
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+/**#@+
+ * Error codes for task validation routines
+ */
+define('PEAR_TASK_ERROR_NOATTRIBS', 1);
+define('PEAR_TASK_ERROR_MISSING_ATTRIB', 2);
+define('PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE', 3);
+define('PEAR_TASK_ERROR_INVALID', 4);
+/**#@-*/
+define('PEAR_TASK_PACKAGE', 1);
+define('PEAR_TASK_INSTALL', 2);
+define('PEAR_TASK_PACKAGEANDINSTALL', 3);
+/**
+ * A task is an operation that manipulates the contents of a file.
+ *
+ * Simple tasks operate on 1 file. Multiple tasks are executed after all files have been
+ * processed and installed, and are designed to operate on all files containing the task.
+ * The Post-install script task simply takes advantage of the fact that it will be run
+ * after installation, replace is a simple task.
+ *
+ * Combining tasks is possible, but ordering is significant.
+ *
+ * <file name="test.php" role="php">
+ * <tasks:replace from="@data-dir@" to="data_dir" type="pear-config"/>
+ * <tasks:postinstallscript/>
+ * </file>
+ *
+ * This will first replace any instance of @data-dir@ in the test.php file
+ * with the path to the current data directory. Then, it will include the
+ * test.php file and run the script it contains to configure the package post-installation.
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ * @abstract
+ */
+class PEAR_Task_Common
+{
+ /**
+ * Valid types for this version are 'simple' and 'multiple'
+ *
+ * - simple tasks operate on the contents of a file and write out changes to disk
+ * - multiple tasks operate on the contents of many files and write out the
+ * changes directly to disk
+ *
+ * Child task classes must override this property.
+ * @access protected
+ */
+ var $type = 'simple';
+ /**
+ * Determines which install phase this task is executed under
+ */
+ var $phase = PEAR_TASK_INSTALL;
+ /**
+ * @access protected
+ */
+ var $config;
+ /**
+ * @access protected
+ */
+ var $registry;
+ /**
+ * @access protected
+ */
+ var $logger;
+ /**
+ * @access protected
+ */
+ var $installphase;
+ /**
+ * @param PEAR_Config
+ * @param PEAR_Common
+ */
+ function PEAR_Task_Common(&$config, &$logger, $phase)
+ {
+ $this->config = &$config;
+ $this->registry = &$config->getRegistry();
+ $this->logger = &$logger;
+ $this->installphase = $phase;
+ if ($this->type == 'multiple') {
+ $GLOBALS['_PEAR_TASK_POSTINSTANCES'][get_class($this)][] = &$this;
+ }
+ }
+
+ /**
+ * Validate the basic contents of a task tag.
+ * @param PEAR_PackageFile_v2
+ * @param array
+ * @param PEAR_Config
+ * @param array the entire parsed <file> tag
+ * @return true|array On error, return an array in format:
+ * array(PEAR_TASK_ERROR_???[, param1][, param2][, ...])
+ *
+ * For PEAR_TASK_ERROR_MISSING_ATTRIB, pass the attribute name in
+ * For PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, pass the attribute name and an array
+ * of legal values in
+ * @static
+ * @abstract
+ */
+ function validateXml($pkg, $xml, $config, $fileXml)
+ {
+ }
+
+ /**
+ * Initialize a task instance with the parameters
+ * @param array raw, parsed xml
+ * @param array attributes from the <file> tag containing this task
+ * @param string|null last installed version of this package
+ * @abstract
+ */
+ function init($xml, $fileAttributes, $lastVersion)
+ {
+ }
+
+ /**
+ * Begin a task processing session. All multiple tasks will be processed after each file
+ * has been successfully installed, all simple tasks should perform their task here and
+ * return any errors using the custom throwError() method to allow forward compatibility
+ *
+ * This method MUST NOT write out any changes to disk
+ * @param PEAR_PackageFile_v2
+ * @param string file contents
+ * @param string the eventual final file location (informational only)
+ * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail
+ * (use $this->throwError), otherwise return the new contents
+ * @abstract
+ */
+ function startSession($pkg, $contents, $dest)
+ {
+ }
+
+ /**
+ * This method is used to process each of the tasks for a particular multiple class
+ * type. Simple tasks need not implement this method.
+ * @param array an array of tasks
+ * @access protected
+ * @static
+ * @abstract
+ */
+ function run($tasks)
+ {
+ }
+
+ /**
+ * @static
+ * @final
+ */
+ function hasPostinstallTasks()
+ {
+ return isset($GLOBALS['_PEAR_TASK_POSTINSTANCES']);
+ }
+
+ /**
+ * @static
+ * @final
+ */
+ function runPostinstallTasks()
+ {
+ foreach ($GLOBALS['_PEAR_TASK_POSTINSTANCES'] as $class => $tasks) {
+ $err = call_user_func(array($class, 'run'),
+ $GLOBALS['_PEAR_TASK_POSTINSTANCES'][$class]);
+ if ($err) {
+ return PEAR_Task_Common::throwError($err);
+ }
+ }
+ unset($GLOBALS['_PEAR_TASK_POSTINSTANCES']);
+ }
+
+ /**
+ * Determines whether a role is a script
+ * @return bool
+ */
+ function isScript()
+ {
+ return $this->type == 'script';
+ }
+
+ function throwError($msg, $code = -1)
+ {
+ include_once 'PEAR.php';
+ return PEAR::raiseError($msg, $code);
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Task/Postinstallscript.php b/includes/pear/PEAR/Task/Postinstallscript.php
new file mode 100644
index 0000000..6281340
--- /dev/null
+++ b/includes/pear/PEAR/Task/Postinstallscript.php
@@ -0,0 +1,323 @@
+<?php
+/**
+ * <tasks:postinstallscript>
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+/**
+ * Base class
+ */
+require_once 'PEAR/Task/Common.php';
+/**
+ * Implements the postinstallscript file task.
+ *
+ * Note that post-install scripts are handled separately from installation, by the
+ * "pear run-scripts" command
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Task_Postinstallscript extends PEAR_Task_Common
+{
+ var $type = 'script';
+ var $_class;
+ var $_params;
+ var $_obj;
+ /**
+ *
+ * @var PEAR_PackageFile_v2
+ */
+ var $_pkg;
+ var $_contents;
+ var $phase = PEAR_TASK_INSTALL;
+
+ /**
+ * Validate the raw xml at parsing-time.
+ *
+ * This also attempts to validate the script to make sure it meets the criteria
+ * for a post-install script
+ * @param PEAR_PackageFile_v2
+ * @param array The XML contents of the <postinstallscript> tag
+ * @param PEAR_Config
+ * @param array the entire parsed <file> tag
+ * @static
+ */
+ function validateXml($pkg, $xml, $config, $fileXml)
+ {
+ if ($fileXml['role'] != 'php') {
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" must be role="php"');
+ }
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $file = $pkg->getFileContents($fileXml['name']);
+ if (PEAR::isError($file)) {
+ PEAR::popErrorHandling();
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" is not valid: ' .
+ $file->getMessage());
+ } elseif ($file === null) {
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" could not be retrieved for processing!');
+ } else {
+ $analysis = $pkg->analyzeSourceCode($file, true);
+ if (!$analysis) {
+ PEAR::popErrorHandling();
+ $warnings = '';
+ foreach ($pkg->getValidationWarnings() as $warn) {
+ $warnings .= $warn['message'] . "\n";
+ }
+ return array(PEAR_TASK_ERROR_INVALID, 'Analysis of post-install script "' .
+ $fileXml['name'] . '" failed: ' . $warnings);
+ }
+ if (count($analysis['declared_classes']) != 1) {
+ PEAR::popErrorHandling();
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" must declare exactly 1 class');
+ }
+ $class = $analysis['declared_classes'][0];
+ if ($class != str_replace(array('/', '.php'), array('_', ''),
+ $fileXml['name']) . '_postinstall') {
+ PEAR::popErrorHandling();
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" class "' . $class . '" must be named "' .
+ str_replace(array('/', '.php'), array('_', ''),
+ $fileXml['name']) . '_postinstall"');
+ }
+ if (!isset($analysis['declared_methods'][$class])) {
+ PEAR::popErrorHandling();
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" must declare methods init() and run()');
+ }
+ $methods = array('init' => 0, 'run' => 1);
+ foreach ($analysis['declared_methods'][$class] as $method) {
+ if (isset($methods[$method])) {
+ unset($methods[$method]);
+ }
+ }
+ if (count($methods)) {
+ PEAR::popErrorHandling();
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" must declare methods init() and run()');
+ }
+ }
+ PEAR::popErrorHandling();
+ $definedparams = array();
+ $tasksNamespace = $pkg->getTasksNs() . ':';
+ if (!isset($xml[$tasksNamespace . 'paramgroup']) && isset($xml['paramgroup'])) {
+ // in order to support the older betas, which did not expect internal tags
+ // to also use the namespace
+ $tasksNamespace = '';
+ }
+ if (isset($xml[$tasksNamespace . 'paramgroup'])) {
+ $params = $xml[$tasksNamespace . 'paramgroup'];
+ if (!is_array($params) || !isset($params[0])) {
+ $params = array($params);
+ }
+ foreach ($params as $param) {
+ if (!isset($param[$tasksNamespace . 'id'])) {
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" <paramgroup> must have ' .
+ 'an ' . $tasksNamespace . 'id> tag');
+ }
+ if (isset($param[$tasksNamespace . 'name'])) {
+ if (!in_array($param[$tasksNamespace . 'name'], $definedparams)) {
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" ' . $tasksNamespace .
+ 'paramgroup> id "' . $param[$tasksNamespace . 'id'] .
+ '" parameter "' . $param[$tasksNamespace . 'name'] .
+ '" has not been previously defined');
+ }
+ if (!isset($param[$tasksNamespace . 'conditiontype'])) {
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" ' . $tasksNamespace .
+ 'paramgroup> id "' . $param[$tasksNamespace . 'id'] .
+ '" must have a ' . $tasksNamespace .
+ 'conditiontype> tag containing either "=", ' .
+ '"!=", or "preg_match"');
+ }
+ if (!in_array($param[$tasksNamespace . 'conditiontype'],
+ array('=', '!=', 'preg_match'))) {
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" ' . $tasksNamespace .
+ 'paramgroup> id "' . $param[$tasksNamespace . 'id'] .
+ '" must have a ' . $tasksNamespace .
+ 'conditiontype> tag containing either "=", ' .
+ '"!=", or "preg_match"');
+ }
+ if (!isset($param[$tasksNamespace . 'value'])) {
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" ' . $tasksNamespace .
+ 'paramgroup> id "' . $param[$tasksNamespace . 'id'] .
+ '" must have a ' . $tasksNamespace .
+ 'value> tag containing expected parameter value');
+ }
+ }
+ if (isset($param[$tasksNamespace . 'instructions'])) {
+ if (!is_string($param[$tasksNamespace . 'instructions'])) {
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" ' . $tasksNamespace .
+ 'paramgroup> id "' . $param[$tasksNamespace . 'id'] .
+ '" ' . $tasksNamespace . 'instructions> must be simple text');
+ }
+ }
+ if (!isset($param[$tasksNamespace . 'param'])) {
+ continue; // <param> is no longer required
+ }
+ $subparams = $param[$tasksNamespace . 'param'];
+ if (!is_array($subparams) || !isset($subparams[0])) {
+ $subparams = array($subparams);
+ }
+ foreach ($subparams as $subparam) {
+ if (!isset($subparam[$tasksNamespace . 'name'])) {
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" parameter for ' .
+ $tasksNamespace . 'paramgroup> id "' .
+ $param[$tasksNamespace . 'id'] . '" must have ' .
+ 'a ' . $tasksNamespace . 'name> tag');
+ }
+ if (!preg_match('/[a-zA-Z0-9]+/',
+ $subparam[$tasksNamespace . 'name'])) {
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" parameter "' .
+ $subparam[$tasksNamespace . 'name'] .
+ '" for ' . $tasksNamespace . 'paramgroup> id "' .
+ $param[$tasksNamespace . 'id'] .
+ '" is not a valid name. Must contain only alphanumeric characters');
+ }
+ if (!isset($subparam[$tasksNamespace . 'prompt'])) {
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" parameter "' .
+ $subparam[$tasksNamespace . 'name'] .
+ '" for ' . $tasksNamespace . 'paramgroup> id "' .
+ $param[$tasksNamespace . 'id'] .
+ '" must have a ' . $tasksNamespace . 'prompt> tag');
+ }
+ if (!isset($subparam[$tasksNamespace . 'type'])) {
+ return array(PEAR_TASK_ERROR_INVALID, 'Post-install script "' .
+ $fileXml['name'] . '" parameter "' .
+ $subparam[$tasksNamespace . 'name'] .
+ '" for ' . $tasksNamespace . 'paramgroup> id "' .
+ $param[$tasksNamespace . 'id'] .
+ '" must have a ' . $tasksNamespace . 'type> tag');
+ }
+ $definedparams[] = $param[$tasksNamespace . 'id'] . '::' .
+ $subparam[$tasksNamespace . 'name'];
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Initialize a task instance with the parameters
+ * @param array raw, parsed xml
+ * @param array attributes from the <file> tag containing this task
+ * @param string|null last installed version of this package, if any (useful for upgrades)
+ */
+ function init($xml, $fileattribs, $lastversion)
+ {
+ $this->_class = str_replace('/', '_', $fileattribs['name']);
+ $this->_filename = $fileattribs['name'];
+ $this->_class = str_replace ('.php', '', $this->_class) . '_postinstall';
+ $this->_params = $xml;
+ $this->_lastversion = $lastversion;
+ }
+
+ /**
+ * Strip the tasks: namespace from internal params
+ *
+ * @access private
+ */
+ function _stripNamespace($params = null)
+ {
+ if ($params === null) {
+ $params = array();
+ if (!is_array($this->_params)) {
+ return;
+ }
+ foreach ($this->_params as $i => $param) {
+ if (is_array($param)) {
+ $param = $this->_stripNamespace($param);
+ }
+ $params[str_replace($this->_pkg->getTasksNs() . ':', '', $i)] = $param;
+ }
+ $this->_params = $params;
+ } else {
+ $newparams = array();
+ foreach ($params as $i => $param) {
+ if (is_array($param)) {
+ $param = $this->_stripNamespace($param);
+ }
+ $newparams[str_replace($this->_pkg->getTasksNs() . ':', '', $i)] = $param;
+ }
+ return $newparams;
+ }
+ }
+
+ /**
+ * Unlike other tasks, the installed file name is passed in instead of the file contents,
+ * because this task is handled post-installation
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * @param string file name
+ * @return bool|PEAR_Error false to skip this file, PEAR_Error to fail
+ * (use $this->throwError)
+ */
+ function startSession($pkg, $contents)
+ {
+ if ($this->installphase != PEAR_TASK_INSTALL) {
+ return false;
+ }
+ // remove the tasks: namespace if present
+ $this->_pkg = $pkg;
+ $this->_stripNamespace();
+ $this->logger->log(0, 'Including external post-installation script "' .
+ $contents . '" - any errors are in this script');
+ include_once $contents;
+ if (class_exists($this->_class)) {
+ $this->logger->log(0, 'Inclusion succeeded');
+ } else {
+ return $this->throwError('init of post-install script class "' . $this->_class
+ . '" failed');
+ }
+ $this->_obj = new $this->_class;
+ $this->logger->log(1, 'running post-install script "' . $this->_class . '->init()"');
+ PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+ $res = $this->_obj->init($this->config, $pkg, $this->_lastversion);
+ PEAR::popErrorHandling();
+ if ($res) {
+ $this->logger->log(0, 'init succeeded');
+ } else {
+ return $this->throwError('init of post-install script "' . $this->_class .
+ '->init()" failed');
+ }
+ $this->_contents = $contents;
+ return true;
+ }
+
+ /**
+ * No longer used
+ * @see PEAR_PackageFile_v2::runPostinstallScripts()
+ * @param array an array of tasks
+ * @param string install or upgrade
+ * @access protected
+ * @static
+ */
+ function run()
+ {
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Task/Postinstallscript/rw.php b/includes/pear/PEAR/Task/Postinstallscript/rw.php
new file mode 100644
index 0000000..c48db6c
--- /dev/null
+++ b/includes/pear/PEAR/Task/Postinstallscript/rw.php
@@ -0,0 +1,169 @@
+<?php
+/**
+ * <tasks:postinstallscript> - read/write version
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a10
+ */
+/**
+ * Base class
+ */
+require_once 'PEAR/Task/Postinstallscript.php';
+/**
+ * Abstracts the postinstallscript file task xml.
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a10
+ */
+class PEAR_Task_Postinstallscript_rw extends PEAR_Task_Postinstallscript
+{
+ /**
+ * parent package file object
+ *
+ * @var PEAR_PackageFile_v2_rw
+ */
+ var $_pkg;
+ /**
+ * Enter description here...
+ *
+ * @param PEAR_PackageFile_v2_rw $pkg
+ * @param PEAR_Config $config
+ * @param PEAR_Frontend $logger
+ * @param array $fileXml
+ * @return PEAR_Task_Postinstallscript_rw
+ */
+ function PEAR_Task_Postinstallscript_rw(&$pkg, &$config, &$logger, $fileXml)
+ {
+ parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE);
+ $this->_contents = $fileXml;
+ $this->_pkg = &$pkg;
+ $this->_params = array();
+ }
+
+ function validate()
+ {
+ return $this->validateXml($this->_pkg, $this->_params, $this->config, $this->_contents);
+ }
+
+ function getName()
+ {
+ return 'postinstallscript';
+ }
+
+ /**
+ * add a simple <paramgroup> to the post-install script
+ *
+ * Order is significant, so call this method in the same
+ * sequence the users should see the paramgroups. The $params
+ * parameter should either be the result of a call to {@link getParam()}
+ * or an array of calls to getParam().
+ *
+ * Use {@link addConditionTypeGroup()} to add a <paramgroup> containing
+ * a <conditiontype> tag
+ * @param string $id <paramgroup> id as seen by the script
+ * @param array|false $params array of getParam() calls, or false for no params
+ * @param string|false $instructions
+ */
+ function addParamGroup($id, $params = false, $instructions = false)
+ {
+ if ($params && isset($params[0]) && !isset($params[1])) {
+ $params = $params[0];
+ }
+ $stuff =
+ array(
+ $this->_pkg->getTasksNs() . ':id' => $id,
+ );
+ if ($instructions) {
+ $stuff[$this->_pkg->getTasksNs() . ':instructions'] = $instructions;
+ }
+ if ($params) {
+ $stuff[$this->_pkg->getTasksNs() . ':param'] = $params;
+ }
+ $this->_params[$this->_pkg->getTasksNs() . ':paramgroup'][] = $stuff;
+ }
+
+ /**
+ * add a complex <paramgroup> to the post-install script with conditions
+ *
+ * This inserts a <paramgroup> with
+ *
+ * Order is significant, so call this method in the same
+ * sequence the users should see the paramgroups. The $params
+ * parameter should either be the result of a call to {@link getParam()}
+ * or an array of calls to getParam().
+ *
+ * Use {@link addParamGroup()} to add a simple <paramgroup>
+ *
+ * @param string $id <paramgroup> id as seen by the script
+ * @param string $oldgroup <paramgroup> id of the section referenced by
+ * <conditiontype>
+ * @param string $param name of the <param> from the older section referenced
+ * by <contitiontype>
+ * @param string $value value to match of the parameter
+ * @param string $conditiontype one of '=', '!=', 'preg_match'
+ * @param array|false $params array of getParam() calls, or false for no params
+ * @param string|false $instructions
+ */
+ function addConditionTypeGroup($id, $oldgroup, $param, $value, $conditiontype = '=',
+ $params = false, $instructions = false)
+ {
+ if ($params && isset($params[0]) && !isset($params[1])) {
+ $params = $params[0];
+ }
+ $stuff = array(
+ $this->_pkg->getTasksNs() . ':id' => $id,
+ );
+ if ($instructions) {
+ $stuff[$this->_pkg->getTasksNs() . ':instructions'] = $instructions;
+ }
+ $stuff[$this->_pkg->getTasksNs() . ':name'] = $oldgroup . '::' . $param;
+ $stuff[$this->_pkg->getTasksNs() . ':conditiontype'] = $conditiontype;
+ $stuff[$this->_pkg->getTasksNs() . ':value'] = $value;
+ if ($params) {
+ $stuff[$this->_pkg->getTasksNs() . ':param'] = $params;
+ }
+ $this->_params[$this->_pkg->getTasksNs() . ':paramgroup'][] = $stuff;
+ }
+
+ function getXml()
+ {
+ return $this->_params;
+ }
+
+ /**
+ * Use to set up a param tag for use in creating a paramgroup
+ * @static
+ */
+ function getParam($name, $prompt, $type = 'string', $default = null)
+ {
+ if ($default !== null) {
+ return
+ array(
+ $this->_pkg->getTasksNs() . ':name' => $name,
+ $this->_pkg->getTasksNs() . ':prompt' => $prompt,
+ $this->_pkg->getTasksNs() . ':type' => $type,
+ $this->_pkg->getTasksNs() . ':default' => $default
+ );
+ }
+ return
+ array(
+ $this->_pkg->getTasksNs() . ':name' => $name,
+ $this->_pkg->getTasksNs() . ':prompt' => $prompt,
+ $this->_pkg->getTasksNs() . ':type' => $type,
+ );
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Task/Replace.php b/includes/pear/PEAR/Task/Replace.php
new file mode 100644
index 0000000..4a36b96
--- /dev/null
+++ b/includes/pear/PEAR/Task/Replace.php
@@ -0,0 +1,176 @@
+<?php
+/**
+ * <tasks:replace>
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+/**
+ * Base class
+ */
+require_once 'PEAR/Task/Common.php';
+/**
+ * Implements the replace file task.
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Task_Replace extends PEAR_Task_Common
+{
+ var $type = 'simple';
+ var $phase = PEAR_TASK_PACKAGEANDINSTALL;
+ var $_replacements;
+
+ /**
+ * Validate the raw xml at parsing-time.
+ * @param PEAR_PackageFile_v2
+ * @param array raw, parsed xml
+ * @param PEAR_Config
+ * @static
+ */
+ function validateXml($pkg, $xml, $config, $fileXml)
+ {
+ if (!isset($xml['attribs'])) {
+ return array(PEAR_TASK_ERROR_NOATTRIBS);
+ }
+ if (!isset($xml['attribs']['type'])) {
+ return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'type');
+ }
+ if (!isset($xml['attribs']['to'])) {
+ return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'to');
+ }
+ if (!isset($xml['attribs']['from'])) {
+ return array(PEAR_TASK_ERROR_MISSING_ATTRIB, 'from');
+ }
+ if ($xml['attribs']['type'] == 'pear-config') {
+ if (!in_array($xml['attribs']['to'], $config->getKeys())) {
+ return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'],
+ $config->getKeys());
+ }
+ } elseif ($xml['attribs']['type'] == 'php-const') {
+ if (defined($xml['attribs']['to'])) {
+ return true;
+ } else {
+ return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'],
+ array('valid PHP constant'));
+ }
+ } elseif ($xml['attribs']['type'] == 'package-info') {
+ if (in_array($xml['attribs']['to'],
+ array('name', 'summary', 'channel', 'notes', 'extends', 'description',
+ 'release_notes', 'license', 'release-license', 'license-uri',
+ 'version', 'api-version', 'state', 'api-state', 'release_date',
+ 'date', 'time'))) {
+ return true;
+ } else {
+ return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'to', $xml['attribs']['to'],
+ array('name', 'summary', 'channel', 'notes', 'extends', 'description',
+ 'release_notes', 'license', 'release-license', 'license-uri',
+ 'version', 'api-version', 'state', 'api-state', 'release_date',
+ 'date', 'time'));
+ }
+ } else {
+ return array(PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE, 'type', $xml['attribs']['type'],
+ array('pear-config', 'package-info', 'php-const'));
+ }
+ return true;
+ }
+
+ /**
+ * Initialize a task instance with the parameters
+ * @param array raw, parsed xml
+ * @param unused
+ */
+ function init($xml, $attribs)
+ {
+ $this->_replacements = isset($xml['attribs']) ? array($xml) : $xml;
+ }
+
+ /**
+ * Do a package.xml 1.0 replacement, with additional package-info fields available
+ *
+ * See validateXml() source for the complete list of allowed fields
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * @param string file contents
+ * @param string the eventual final file location (informational only)
+ * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail
+ * (use $this->throwError), otherwise return the new contents
+ */
+ function startSession($pkg, $contents, $dest)
+ {
+ $subst_from = $subst_to = array();
+ foreach ($this->_replacements as $a) {
+ $a = $a['attribs'];
+ $to = '';
+ if ($a['type'] == 'pear-config') {
+ if ($this->installphase == PEAR_TASK_PACKAGE) {
+ return false;
+ }
+ if ($a['to'] == 'master_server') {
+ $chan = $this->registry->getChannel($pkg->getChannel());
+ if (!PEAR::isError($chan)) {
+ $to = $chan->getServer();
+ } else {
+ $this->logger->log(0, "$dest: invalid pear-config replacement: $a[to]");
+ return false;
+ }
+ } else {
+ if ($this->config->isDefinedLayer('ftp')) {
+ // try the remote config file first
+ $to = $this->config->get($a['to'], 'ftp', $pkg->getChannel());
+ if (is_null($to)) {
+ // then default to local
+ $to = $this->config->get($a['to'], null, $pkg->getChannel());
+ }
+ } else {
+ $to = $this->config->get($a['to'], null, $pkg->getChannel());
+ }
+ }
+ if (is_null($to)) {
+ $this->logger->log(0, "$dest: invalid pear-config replacement: $a[to]");
+ return false;
+ }
+ } elseif ($a['type'] == 'php-const') {
+ if ($this->installphase == PEAR_TASK_PACKAGE) {
+ return false;
+ }
+ if (defined($a['to'])) {
+ $to = constant($a['to']);
+ } else {
+ $this->logger->log(0, "$dest: invalid php-const replacement: $a[to]");
+ return false;
+ }
+ } else {
+ if ($t = $pkg->packageInfo($a['to'])) {
+ $to = $t;
+ } else {
+ $this->logger->log(0, "$dest: invalid package-info replacement: $a[to]");
+ return false;
+ }
+ }
+ if (!is_null($to)) {
+ $subst_from[] = $a['from'];
+ $subst_to[] = $to;
+ }
+ }
+ $this->logger->log(3, "doing " . sizeof($subst_from) .
+ " substitution(s) for $dest");
+ if (sizeof($subst_from)) {
+ $contents = str_replace($subst_from, $subst_to, $contents);
+ }
+ return $contents;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Task/Replace/rw.php b/includes/pear/PEAR/Task/Replace/rw.php
new file mode 100644
index 0000000..28496d1
--- /dev/null
+++ b/includes/pear/PEAR/Task/Replace/rw.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * <tasks:replace> - read/write version
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a10
+ */
+/**
+ * Base class
+ */
+require_once 'PEAR/Task/Replace.php';
+/**
+ * Abstracts the replace task xml.
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a10
+ */
+class PEAR_Task_Replace_rw extends PEAR_Task_Replace
+{
+ function PEAR_Task_Replace_rw(&$pkg, &$config, &$logger, $fileXml)
+ {
+ parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE);
+ $this->_contents = $fileXml;
+ $this->_pkg = &$pkg;
+ $this->_params = array();
+ }
+
+ function validate()
+ {
+ return $this->validateXml($this->_pkg, $this->_params, $this->config, $this->_contents);
+ }
+
+ function setInfo($from, $to, $type)
+ {
+ $this->_params = array('attribs' => array('from' => $from, 'to' => $to, 'type' => $type));
+ }
+
+ function getName()
+ {
+ return 'replace';
+ }
+
+ function getXml()
+ {
+ return $this->_params;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Task/Unixeol.php b/includes/pear/PEAR/Task/Unixeol.php
new file mode 100644
index 0000000..2c20313
--- /dev/null
+++ b/includes/pear/PEAR/Task/Unixeol.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * <tasks:unixeol>
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+/**
+ * Base class
+ */
+require_once 'PEAR/Task/Common.php';
+/**
+ * Implements the unix line endings file task.
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Task_Unixeol extends PEAR_Task_Common
+{
+ var $type = 'simple';
+ var $phase = PEAR_TASK_PACKAGE;
+ var $_replacements;
+
+ /**
+ * Validate the raw xml at parsing-time.
+ * @param PEAR_PackageFile_v2
+ * @param array raw, parsed xml
+ * @param PEAR_Config
+ * @static
+ */
+ function validateXml($pkg, $xml, $config, $fileXml)
+ {
+ if ($xml != '') {
+ return array(PEAR_TASK_ERROR_INVALID, 'no attributes allowed');
+ }
+ return true;
+ }
+
+ /**
+ * Initialize a task instance with the parameters
+ * @param array raw, parsed xml
+ * @param unused
+ */
+ function init($xml, $attribs)
+ {
+ }
+
+ /**
+ * Replace all line endings with line endings customized for the current OS
+ *
+ * See validateXml() source for the complete list of allowed fields
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * @param string file contents
+ * @param string the eventual final file location (informational only)
+ * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail
+ * (use $this->throwError), otherwise return the new contents
+ */
+ function startSession($pkg, $contents, $dest)
+ {
+ $this->logger->log(3, "replacing all line endings with \\n in $dest");
+ return preg_replace("/\r\n|\n\r|\r|\n/", "\n", $contents);
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Task/Unixeol/rw.php b/includes/pear/PEAR/Task/Unixeol/rw.php
new file mode 100644
index 0000000..5348fac
--- /dev/null
+++ b/includes/pear/PEAR/Task/Unixeol/rw.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * <tasks:unixeol> - read/write version
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a10
+ */
+/**
+ * Base class
+ */
+require_once 'PEAR/Task/Unixeol.php';
+/**
+ * Abstracts the unixeol task xml.
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a10
+ */
+class PEAR_Task_Unixeol_rw extends PEAR_Task_Unixeol
+{
+ function PEAR_Task_Unixeol_rw(&$pkg, &$config, &$logger, $fileXml)
+ {
+ parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE);
+ $this->_contents = $fileXml;
+ $this->_pkg = &$pkg;
+ $this->_params = array();
+ }
+
+ function validate()
+ {
+ return true;
+ }
+
+ function getName()
+ {
+ return 'unixeol';
+ }
+
+ function getXml()
+ {
+ return '';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Task/Windowseol.php b/includes/pear/PEAR/Task/Windowseol.php
new file mode 100644
index 0000000..d2bd2c8
--- /dev/null
+++ b/includes/pear/PEAR/Task/Windowseol.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * <tasks:windowseol>
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+/**
+ * Base class
+ */
+require_once 'PEAR/Task/Common.php';
+/**
+ * Implements the windows line endsings file task.
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Task_Windowseol extends PEAR_Task_Common
+{
+ var $type = 'simple';
+ var $phase = PEAR_TASK_PACKAGE;
+ var $_replacements;
+
+ /**
+ * Validate the raw xml at parsing-time.
+ * @param PEAR_PackageFile_v2
+ * @param array raw, parsed xml
+ * @param PEAR_Config
+ * @static
+ */
+ function validateXml($pkg, $xml, $config, $fileXml)
+ {
+ if ($xml != '') {
+ return array(PEAR_TASK_ERROR_INVALID, 'no attributes allowed');
+ }
+ return true;
+ }
+
+ /**
+ * Initialize a task instance with the parameters
+ * @param array raw, parsed xml
+ * @param unused
+ */
+ function init($xml, $attribs)
+ {
+ }
+
+ /**
+ * Replace all line endings with windows line endings
+ *
+ * See validateXml() source for the complete list of allowed fields
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ * @param string file contents
+ * @param string the eventual final file location (informational only)
+ * @return string|false|PEAR_Error false to skip this file, PEAR_Error to fail
+ * (use $this->throwError), otherwise return the new contents
+ */
+ function startSession($pkg, $contents, $dest)
+ {
+ $this->logger->log(3, "replacing all line endings with \\r\\n in $dest");
+ return preg_replace("/\r\n|\n\r|\r|\n/", "\r\n", $contents);
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Task/Windowseol/rw.php b/includes/pear/PEAR/Task/Windowseol/rw.php
new file mode 100644
index 0000000..e86e4f0
--- /dev/null
+++ b/includes/pear/PEAR/Task/Windowseol/rw.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * <tasks:windowseol> - read/write version
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a10
+ */
+/**
+ * Base class
+ */
+require_once 'PEAR/Task/Windowseol.php';
+/**
+ * Abstracts the windowseol task xml.
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a10
+ */
+class PEAR_Task_Windowseol_rw extends PEAR_Task_Windowseol
+{
+ function PEAR_Task_Windowseol_rw(&$pkg, &$config, &$logger, $fileXml)
+ {
+ parent::PEAR_Task_Common($config, $logger, PEAR_TASK_PACKAGE);
+ $this->_contents = $fileXml;
+ $this->_pkg = &$pkg;
+ $this->_params = array();
+ }
+
+ function validate()
+ {
+ return true;
+ }
+
+ function getName()
+ {
+ return 'windowseol';
+ }
+
+ function getXml()
+ {
+ return '';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Validate.php b/includes/pear/PEAR/Validate.php
new file mode 100644
index 0000000..3c2471b
--- /dev/null
+++ b/includes/pear/PEAR/Validate.php
@@ -0,0 +1,629 @@
+<?php
+/**
+ * PEAR_Validate
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+/**#@+
+ * Constants for install stage
+ */
+define('PEAR_VALIDATE_INSTALLING', 1);
+define('PEAR_VALIDATE_UNINSTALLING', 2); // this is not bit-mapped like the others
+define('PEAR_VALIDATE_NORMAL', 3);
+define('PEAR_VALIDATE_DOWNLOADING', 4); // this is not bit-mapped like the others
+define('PEAR_VALIDATE_PACKAGING', 7);
+/**#@-*/
+require_once 'PEAR/Common.php';
+require_once 'PEAR/Validator/PECL.php';
+
+/**
+ * Validation class for package.xml - channel-level advanced validation
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_Validate
+{
+ var $packageregex = _PEAR_COMMON_PACKAGE_NAME_PREG;
+ /**
+ * @var PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ */
+ var $_packagexml;
+ /**
+ * @var int one of the PEAR_VALIDATE_* constants
+ */
+ var $_state = PEAR_VALIDATE_NORMAL;
+ /**
+ * Format: ('error' => array('field' => name, 'reason' => reason), 'warning' => same)
+ * @var array
+ * @access private
+ */
+ var $_failures = array('error' => array(), 'warning' => array());
+
+ /**
+ * Override this method to handle validation of normal package names
+ * @param string
+ * @return bool
+ * @access protected
+ */
+ function _validPackageName($name)
+ {
+ return (bool) preg_match('/^' . $this->packageregex . '\\z/', $name);
+ }
+
+ /**
+ * @param string package name to validate
+ * @param string name of channel-specific validation package
+ * @final
+ */
+ function validPackageName($name, $validatepackagename = false)
+ {
+ if ($validatepackagename) {
+ if (strtolower($name) == strtolower($validatepackagename)) {
+ return (bool) preg_match('/^[a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*\\z/', $name);
+ }
+ }
+ return $this->_validPackageName($name);
+ }
+
+ /**
+ * This validates a bundle name, and bundle names must conform
+ * to the PEAR naming convention, so the method is final and static.
+ * @param string
+ * @final
+ * @static
+ */
+ function validGroupName($name)
+ {
+ return (bool) preg_match('/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '\\z/', $name);
+ }
+
+ /**
+ * Determine whether $state represents a valid stability level
+ * @param string
+ * @return bool
+ * @static
+ * @final
+ */
+ function validState($state)
+ {
+ return in_array($state, array('snapshot', 'devel', 'alpha', 'beta', 'stable'));
+ }
+
+ /**
+ * Get a list of valid stability levels
+ * @return array
+ * @static
+ * @final
+ */
+ function getValidStates()
+ {
+ return array('snapshot', 'devel', 'alpha', 'beta', 'stable');
+ }
+
+ /**
+ * Determine whether a version is a properly formatted version number that can be used
+ * by version_compare
+ * @param string
+ * @return bool
+ * @static
+ * @final
+ */
+ function validVersion($ver)
+ {
+ return (bool) preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver);
+ }
+
+ /**
+ * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+ */
+ function setPackageFile(&$pf)
+ {
+ $this->_packagexml = &$pf;
+ }
+
+ /**
+ * @access private
+ */
+ function _addFailure($field, $reason)
+ {
+ $this->_failures['errors'][] = array('field' => $field, 'reason' => $reason);
+ }
+
+ /**
+ * @access private
+ */
+ function _addWarning($field, $reason)
+ {
+ $this->_failures['warnings'][] = array('field' => $field, 'reason' => $reason);
+ }
+
+ function getFailures()
+ {
+ $failures = $this->_failures;
+ $this->_failures = array('warnings' => array(), 'errors' => array());
+ return $failures;
+ }
+
+ /**
+ * @param int one of the PEAR_VALIDATE_* constants
+ */
+ function validate($state = null)
+ {
+ if (!isset($this->_packagexml)) {
+ return false;
+ }
+ if ($state !== null) {
+ $this->_state = $state;
+ }
+ $this->_failures = array('warnings' => array(), 'errors' => array());
+ $this->validatePackageName();
+ $this->validateVersion();
+ $this->validateMaintainers();
+ $this->validateDate();
+ $this->validateSummary();
+ $this->validateDescription();
+ $this->validateLicense();
+ $this->validateNotes();
+ if ($this->_packagexml->getPackagexmlVersion() == '1.0') {
+ $this->validateState();
+ $this->validateFilelist();
+ } elseif ($this->_packagexml->getPackagexmlVersion() == '2.0' ||
+ $this->_packagexml->getPackagexmlVersion() == '2.1') {
+ $this->validateTime();
+ $this->validateStability();
+ $this->validateDeps();
+ $this->validateMainFilelist();
+ $this->validateReleaseFilelist();
+ //$this->validateGlobalTasks();
+ $this->validateChangelog();
+ }
+ return !((bool) count($this->_failures['errors']));
+ }
+
+ /**
+ * @access protected
+ */
+ function validatePackageName()
+ {
+ if ($this->_state == PEAR_VALIDATE_PACKAGING ||
+ $this->_state == PEAR_VALIDATE_NORMAL) {
+ if (($this->_packagexml->getPackagexmlVersion() == '2.0' ||
+ $this->_packagexml->getPackagexmlVersion() == '2.1') &&
+ $this->_packagexml->getExtends()) {
+ $version = $this->_packagexml->getVersion() . '';
+ $name = $this->_packagexml->getPackage();
+ $test = array_shift($a = explode('.', $version));
+ if ($test == '0') {
+ return true;
+ }
+ $vlen = strlen($test);
+ $majver = substr($name, strlen($name) - $vlen);
+ while ($majver && !is_numeric($majver{0})) {
+ $majver = substr($majver, 1);
+ }
+ if ($majver != $test) {
+ $this->_addWarning('package', "package $name extends package " .
+ $this->_packagexml->getExtends() . ' and so the name should ' .
+ 'have a postfix equal to the major version like "' .
+ $this->_packagexml->getExtends() . $test . '"');
+ return true;
+ } elseif (substr($name, 0, strlen($name) - $vlen) !=
+ $this->_packagexml->getExtends()) {
+ $this->_addWarning('package', "package $name extends package " .
+ $this->_packagexml->getExtends() . ' and so the name must ' .
+ 'be an extension like "' . $this->_packagexml->getExtends() .
+ $test . '"');
+ return true;
+ }
+ }
+ }
+ if (!$this->validPackageName($this->_packagexml->getPackage())) {
+ $this->_addFailure('name', 'package name "' .
+ $this->_packagexml->getPackage() . '" is invalid');
+ return false;
+ }
+ }
+
+ /**
+ * @access protected
+ */
+ function validateVersion()
+ {
+ if ($this->_state != PEAR_VALIDATE_PACKAGING) {
+ if (!$this->validVersion($this->_packagexml->getVersion())) {
+ $this->_addFailure('version',
+ 'Invalid version number "' . $this->_packagexml->getVersion() . '"');
+ }
+ return false;
+ }
+ $version = $this->_packagexml->getVersion();
+ $versioncomponents = explode('.', $version);
+ if (count($versioncomponents) != 3) {
+ $this->_addWarning('version',
+ 'A version number should have 3 decimals (x.y.z)');
+ return true;
+ }
+ $name = $this->_packagexml->getPackage();
+ // version must be based upon state
+ switch ($this->_packagexml->getState()) {
+ case 'snapshot' :
+ return true;
+ case 'devel' :
+ if ($versioncomponents[0] . 'a' == '0a') {
+ return true;
+ }
+ if ($versioncomponents[0] == 0) {
+ $versioncomponents[0] = '0';
+ $this->_addWarning('version',
+ 'version "' . $version . '" should be "' .
+ implode('.' ,$versioncomponents) . '"');
+ } else {
+ $this->_addWarning('version',
+ 'packages with devel stability must be < version 1.0.0');
+ }
+ return true;
+ break;
+ case 'alpha' :
+ case 'beta' :
+ // check for a package that extends a package,
+ // like Foo and Foo2
+ if ($this->_state == PEAR_VALIDATE_PACKAGING) {
+ if (substr($versioncomponents[2], 1, 2) == 'rc') {
+ $this->_addFailure('version', 'Release Candidate versions ' .
+ 'must have capital RC, not lower-case rc');
+ return false;
+ }
+ }
+ if (!$this->_packagexml->getExtends()) {
+ if ($versioncomponents[0] == '1') {
+ if ($versioncomponents[2]{0} == '0') {
+ if ($versioncomponents[2] == '0') {
+ // version 1.*.0000
+ $this->_addWarning('version',
+ 'version 1.' . $versioncomponents[1] .
+ '.0 probably should not be alpha or beta');
+ return true;
+ } elseif (strlen($versioncomponents[2]) > 1) {
+ // version 1.*.0RC1 or 1.*.0beta24 etc.
+ return true;
+ } else {
+ // version 1.*.0
+ $this->_addWarning('version',
+ 'version 1.' . $versioncomponents[1] .
+ '.0 probably should not be alpha or beta');
+ return true;
+ }
+ } else {
+ $this->_addWarning('version',
+ 'bugfix versions (1.3.x where x > 0) probably should ' .
+ 'not be alpha or beta');
+ return true;
+ }
+ } elseif ($versioncomponents[0] != '0') {
+ $this->_addWarning('version',
+ 'major versions greater than 1 are not allowed for packages ' .
+ 'without an <extends> tag or an identical postfix (foo2 v2.0.0)');
+ return true;
+ }
+ if ($versioncomponents[0] . 'a' == '0a') {
+ return true;
+ }
+ if ($versioncomponents[0] == 0) {
+ $versioncomponents[0] = '0';
+ $this->_addWarning('version',
+ 'version "' . $version . '" should be "' .
+ implode('.' ,$versioncomponents) . '"');
+ }
+ } else {
+ $vlen = strlen($versioncomponents[0] . '');
+ $majver = substr($name, strlen($name) - $vlen);
+ while ($majver && !is_numeric($majver{0})) {
+ $majver = substr($majver, 1);
+ }
+ if (($versioncomponents[0] != 0) && $majver != $versioncomponents[0]) {
+ $this->_addWarning('version', 'first version number "' .
+ $versioncomponents[0] . '" must match the postfix of ' .
+ 'package name "' . $name . '" (' .
+ $majver . ')');
+ return true;
+ }
+ if ($versioncomponents[0] == $majver) {
+ if ($versioncomponents[2]{0} == '0') {
+ if ($versioncomponents[2] == '0') {
+ // version 2.*.0000
+ $this->_addWarning('version',
+ "version $majver." . $versioncomponents[1] .
+ '.0 probably should not be alpha or beta');
+ return false;
+ } elseif (strlen($versioncomponents[2]) > 1) {
+ // version 2.*.0RC1 or 2.*.0beta24 etc.
+ return true;
+ } else {
+ // version 2.*.0
+ $this->_addWarning('version',
+ "version $majver." . $versioncomponents[1] .
+ '.0 cannot be alpha or beta');
+ return true;
+ }
+ } else {
+ $this->_addWarning('version',
+ "bugfix versions ($majver.x.y where y > 0) should " .
+ 'not be alpha or beta');
+ return true;
+ }
+ } elseif ($versioncomponents[0] != '0') {
+ $this->_addWarning('version',
+ "only versions 0.x.y and $majver.x.y are allowed for alpha/beta releases");
+ return true;
+ }
+ if ($versioncomponents[0] . 'a' == '0a') {
+ return true;
+ }
+ if ($versioncomponents[0] == 0) {
+ $versioncomponents[0] = '0';
+ $this->_addWarning('version',
+ 'version "' . $version . '" should be "' .
+ implode('.' ,$versioncomponents) . '"');
+ }
+ }
+ return true;
+ break;
+ case 'stable' :
+ if ($versioncomponents[0] == '0') {
+ $this->_addWarning('version', 'versions less than 1.0.0 cannot ' .
+ 'be stable');
+ return true;
+ }
+ if (!is_numeric($versioncomponents[2])) {
+ if (preg_match('/\d+(rc|a|alpha|b|beta)\d*/i',
+ $versioncomponents[2])) {
+ $this->_addWarning('version', 'version "' . $version . '" or any ' .
+ 'RC/beta/alpha version cannot be stable');
+ return true;
+ }
+ }
+ // check for a package that extends a package,
+ // like Foo and Foo2
+ if ($this->_packagexml->getExtends()) {
+ $vlen = strlen($versioncomponents[0] . '');
+ $majver = substr($name, strlen($name) - $vlen);
+ while ($majver && !is_numeric($majver{0})) {
+ $majver = substr($majver, 1);
+ }
+ if (($versioncomponents[0] != 0) && $majver != $versioncomponents[0]) {
+ $this->_addWarning('version', 'first version number "' .
+ $versioncomponents[0] . '" must match the postfix of ' .
+ 'package name "' . $name . '" (' .
+ $majver . ')');
+ return true;
+ }
+ } elseif ($versioncomponents[0] > 1) {
+ $this->_addWarning('version', 'major version x in x.y.z may not be greater than ' .
+ '1 for any package that does not have an <extends> tag');
+ }
+ return true;
+ break;
+ default :
+ return false;
+ break;
+ }
+ }
+
+ /**
+ * @access protected
+ */
+ function validateMaintainers()
+ {
+ // maintainers can only be truly validated server-side for most channels
+ // but allow this customization for those who wish it
+ return true;
+ }
+
+ /**
+ * @access protected
+ */
+ function validateDate()
+ {
+ if ($this->_state == PEAR_VALIDATE_NORMAL ||
+ $this->_state == PEAR_VALIDATE_PACKAGING) {
+
+ if (!preg_match('/(\d\d\d\d)\-(\d\d)\-(\d\d)/',
+ $this->_packagexml->getDate(), $res) ||
+ count($res) < 4
+ || !checkdate($res[2], $res[3], $res[1])
+ ) {
+ $this->_addFailure('date', 'invalid release date "' .
+ $this->_packagexml->getDate() . '"');
+ return false;
+ }
+
+ if ($this->_state == PEAR_VALIDATE_PACKAGING &&
+ $this->_packagexml->getDate() != date('Y-m-d')) {
+ $this->_addWarning('date', 'Release Date "' .
+ $this->_packagexml->getDate() . '" is not today');
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @access protected
+ */
+ function validateTime()
+ {
+ if (!$this->_packagexml->getTime()) {
+ // default of no time value set
+ return true;
+ }
+
+ // packager automatically sets time, so only validate if pear validate is called
+ if ($this->_state = PEAR_VALIDATE_NORMAL) {
+ if (!preg_match('/\d\d:\d\d:\d\d/',
+ $this->_packagexml->getTime())) {
+ $this->_addFailure('time', 'invalid release time "' .
+ $this->_packagexml->getTime() . '"');
+ return false;
+ }
+
+ $result = preg_match('|\d{2}\:\d{2}\:\d{2}|', $this->_packagexml->getTime(), $matches);
+ if ($result === false || empty($matches)) {
+ $this->_addFailure('time', 'invalid release time "' .
+ $this->_packagexml->getTime() . '"');
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @access protected
+ */
+ function validateState()
+ {
+ // this is the closest to "final" php4 can get
+ if (!PEAR_Validate::validState($this->_packagexml->getState())) {
+ if (strtolower($this->_packagexml->getState() == 'rc')) {
+ $this->_addFailure('state', 'RC is not a state, it is a version ' .
+ 'postfix, use ' . $this->_packagexml->getVersion() . 'RC1, state beta');
+ }
+ $this->_addFailure('state', 'invalid release state "' .
+ $this->_packagexml->getState() . '", must be one of: ' .
+ implode(', ', PEAR_Validate::getValidStates()));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @access protected
+ */
+ function validateStability()
+ {
+ $ret = true;
+ $packagestability = $this->_packagexml->getState();
+ $apistability = $this->_packagexml->getState('api');
+ if (!PEAR_Validate::validState($packagestability)) {
+ $this->_addFailure('state', 'invalid release stability "' .
+ $this->_packagexml->getState() . '", must be one of: ' .
+ implode(', ', PEAR_Validate::getValidStates()));
+ $ret = false;
+ }
+ $apistates = PEAR_Validate::getValidStates();
+ array_shift($apistates); // snapshot is not allowed
+ if (!in_array($apistability, $apistates)) {
+ $this->_addFailure('state', 'invalid API stability "' .
+ $this->_packagexml->getState('api') . '", must be one of: ' .
+ implode(', ', $apistates));
+ $ret = false;
+ }
+ return $ret;
+ }
+
+ /**
+ * @access protected
+ */
+ function validateSummary()
+ {
+ return true;
+ }
+
+ /**
+ * @access protected
+ */
+ function validateDescription()
+ {
+ return true;
+ }
+
+ /**
+ * @access protected
+ */
+ function validateLicense()
+ {
+ return true;
+ }
+
+ /**
+ * @access protected
+ */
+ function validateNotes()
+ {
+ return true;
+ }
+
+ /**
+ * for package.xml 2.0 only - channels can't use package.xml 1.0
+ * @access protected
+ */
+ function validateDependencies()
+ {
+ return true;
+ }
+
+ /**
+ * for package.xml 1.0 only
+ * @access private
+ */
+ function _validateFilelist()
+ {
+ return true; // placeholder for now
+ }
+
+ /**
+ * for package.xml 2.0 only
+ * @access protected
+ */
+ function validateMainFilelist()
+ {
+ return true; // placeholder for now
+ }
+
+ /**
+ * for package.xml 2.0 only
+ * @access protected
+ */
+ function validateReleaseFilelist()
+ {
+ return true; // placeholder for now
+ }
+
+ /**
+ * @access protected
+ */
+ function validateChangelog()
+ {
+ return true;
+ }
+
+ /**
+ * @access protected
+ */
+ function validateFilelist()
+ {
+ return true;
+ }
+
+ /**
+ * @access protected
+ */
+ function validateDeps()
+ {
+ return true;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR/Validator/PECL.php b/includes/pear/PEAR/Validator/PECL.php
new file mode 100644
index 0000000..7e0769a
--- /dev/null
+++ b/includes/pear/PEAR/Validator/PECL.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Channel Validator for the pecl.php.net channel
+ *
+ * PHP 4 and PHP 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a5
+ */
+/**
+ * This is the parent class for all validators
+ */
+require_once 'PEAR/Validate.php';
+/**
+ * Channel Validator for the pecl.php.net channel
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a5
+ */
+class PEAR_Validator_PECL extends PEAR_Validate
+{
+ function validateVersion()
+ {
+ if ($this->_state == PEAR_VALIDATE_PACKAGING) {
+ $version = $this->_packagexml->getVersion();
+ $versioncomponents = explode('.', $version);
+ $last = array_pop($versioncomponents);
+ if (substr($last, 1, 2) == 'rc') {
+ $this->_addFailure('version', 'Release Candidate versions must have ' .
+ 'upper-case RC, not lower-case rc');
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function validatePackageName()
+ {
+ $ret = parent::validatePackageName();
+ if ($this->_packagexml->getPackageType() == 'extsrc' ||
+ $this->_packagexml->getPackageType() == 'zendextsrc') {
+ if (strtolower($this->_packagexml->getPackage()) !=
+ strtolower($this->_packagexml->getProvidesExtension())) {
+ $this->_addWarning('providesextension', 'package name "' .
+ $this->_packagexml->getPackage() . '" is different from extension name "' .
+ $this->_packagexml->getProvidesExtension() . '"');
+ }
+ }
+ return $ret;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/Warning.php b/includes/pear/PEAR/Warning.php
new file mode 100644
index 0000000..17ac905
--- /dev/null
+++ b/includes/pear/PEAR/Warning.php
@@ -0,0 +1,379 @@
+<?php
+// +----------------------------------------------------------------------+
+// | PEAR_Warning |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 2004 The PEAR Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 3.0 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/3_0.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Authors: Greg Beaver <cellog@php.net> |
+// +----------------------------------------------------------------------+
+//
+// $Id$
+require_once 'PEAR/Exception.php';
+
+/**
+ * Exception class for internal PEAR_Warning exceptions
+ * @package PEAR
+ */
+class PEAR_WarningException extends PEAR_Exception {}
+
+interface PEAR_WarningInterface
+{
+ /**
+ * Get the severity of this warning ('warning', 'notice')
+ * @return string
+ */
+ public function getLevel();
+}
+
+/**
+ * Warning mechanism for PEAR PHP5-only packages.
+ *
+ * For users:
+ *
+ * Unlike PEAR_ErrorStack, PEAR_Warning is designed to be used on a transactional basis.
+ *
+ * <code>
+ * <?php
+ * require_once 'PEAR/Warning.php';
+ * require_once 'PEAR/Exception.php';
+ * class Mypackage_Exception extends PEAR_Exception {}
+ * PEAR_Warning::begin();
+ * $c = new Somepackage;
+ * $c->doSomethingComplex();
+ * if (PEAR_Warning::hasWarnings()) {
+ * $warnings = PEAR_Warning::end();
+ * throw new Mypackage_Exception('unclean doSomethingComplex', $warnings);
+ * } else {
+ * $c->doSomethingElse();
+ * }
+ * ?>
+ * </code>
+ *
+ * Only warnings that occur between ::begin() and ::end() will be processed. Remember,
+ * a warning is a non-fatal error, exceptions will be used for unrecoverable errors in
+ * all PEAR packages, and you should follow this model to be safe!
+ *
+ * For developers:
+ *
+ * This class can be used globally or locally. For global use, a
+ * series of static methods have been provided. The class is designed
+ * for lazy loading, and so the following code will work, and increase
+ * efficiency on production servers:
+ *
+ * <code>
+ * <?php
+ * if (class_exists('PEAR_Warning')) {
+ * PEAR_Warning::add(1, 'mypackage', 'possible mis-spelling of something');
+ * }
+ * ?>
+ * </code>
+ *
+ * This means that PEAR_Warning can literally be used without the need for
+ * require_once 'PEAR/Warning.php';!
+ *
+ * You can also pass in an exception class as a warning
+ *
+ * <code>
+ * <?php
+ * class MyPackage_Warning extends PEAR_Exception {}
+ * PEAR_Warning::add(new MyPackage_Warning('some info'));
+ * ?>
+ * </code>
+ *
+ * An interface is provided to allow for severity differentiation
+ *
+ * <code>
+ * <?php
+ * class MyPackage_Warning extends PEAR_Exception implements PEAR_WarningInterface
+ * {
+ * private $_level = 'warning';
+ * function __construct($message, $level = 'warning', $p1 = null, $p2 = null)
+ * {
+ * $this->_level = $level;
+ * parent::__construct($message, $p1, $p2);
+ * }
+ *
+ * public function getLevel()
+ * {
+ * return $this->_level;
+ * }
+ * }
+ * PEAR_Warning::add(new MyPackage_Warning('some info', 'notice'));
+ * ?>
+ * </code>
+ *
+ * This can be used with {@link setErrorHandling()} to ignore warnings of different severities
+ * for complex error situations.
+ *
+ * For local situations like an internal warning system for a parser that may become the cause
+ * of a single PEAR_Exception, PEAR_Warning can also be instantiated and used without any connection
+ * to the global warning stack.
+ * @package PEAR
+ */
+class PEAR_Warning
+{
+ /**
+ * properties used for global warning stacks
+ */
+ protected static $_hasWarnings = false;
+
+ protected static $warnings = array();
+ protected static $go = false;
+ protected static $levels = array('warning', 'notice');
+
+ private static $_observers = array();
+ private static $_uniqueid = 0;
+ /**
+ * properties used for instantiation of private warning stack
+ */
+ private $_warnings = array();
+ private $_go = false;
+ private $_context;
+
+ /**
+ * Begin tracking all global warnings
+ */
+ static public function begin()
+ {
+ if (class_exists('PEAR_ErrorStack')) {
+ PEAR_ErrorStack::setPEARWarningCallback(array('PEAR_Warning', '_catch'));
+ }
+ self::$go = true;
+ self::$_hasWarnings = false;
+ }
+
+ /**
+ * @return bool
+ */
+ static public function hasWarnings()
+ {
+ return self::$_hasWarnings;
+ }
+
+ /**
+ * Stop tracking global warnings
+ * @return array an array of all warnings in array and PEAR_Exception format
+ * suitable for use as a PEAR_Exception cause
+ */
+ static public function end()
+ {
+ if (class_exists('PEAR_ErrorStack')) {
+ PEAR_ErrorStack::setPEARWarningCallback(false);
+ }
+ self::$go = false;
+ self::$_hasWarnings = false;
+ $a = self::$warnings;
+ self::$warnings = array();
+ return $a;
+ }
+
+ /**
+ * @param mixed A valid callback that accepts either a
+ * PEAR_Exception or PEAR_ErrorStack-style array
+ * @param string The name of the observer. Use this if you want
+ * to remove it later with removeObserver().
+ * {@link getUniqueId()} can be used to generate a label
+ */
+ public static function addObserver($callback, $label = 'default')
+ {
+ self::$_observers[$label] = $callback;
+ }
+
+ /**
+ * @param mixed observer ID
+ */
+ public static function removeObserver($label = 'default')
+ {
+ unset(self::$_observers[$label]);
+ }
+
+ /**
+ * @return int unique identifier for an observer
+ */
+ public static function getUniqueId()
+ {
+ return self::$_uniqueid++;
+ }
+
+ /**
+ * Set the warning levels that should be captured by the warning mechanism
+ *
+ * WARNING: no error checking or spell checking.
+ * @param array
+ */
+ public static function setErrorHandling($levels)
+ {
+ self::$_levels = $levels;
+ }
+
+ /**
+ * Add a warning to the global warning stack.
+ *
+ * Note: if you want file/line context, use an exception object
+ * @param PEAR_Exception|string|int Either pass in an exception to use as the warning, or an
+ * error code or some other error class differentiation technique
+ * @param string Package is required if $codeOrException is not a PEAR_Exception object
+ * @param string Error message, use %param% to do automatic parameter replacement from $params
+ * @param array Error parameters
+ * @param string Error level, use the English name
+
+ * @throws PEAR_WarningException if $codeOrException is not a PEAR_Exception and $package is not set
+ */
+ static public function add($codeOrException, $package = '', $msg = '', $params = array(),
+ $level = 'warning')
+ {
+ if ($codeOrException instanceof PEAR_Exception) {
+ if ($codeOrException instanceof PEAR_WarningInterface) {
+ if (in_array($codeOrException->getLevel(), self::$levels)) {
+ self::_signal($codeOrException);
+ }
+ } else {
+ self::_signal($codeOrException);
+ }
+ } else {
+ if (empty($package)) {
+ throw new PEAR_WarningException('Package must be set for a non-exception warning');
+ }
+ if (in_array($level, self::$levels)) {
+ $warning = self::_formatWarning($codeOrException, $package, $level, $msg, $params);
+ self::_signal($warning);
+ }
+ }
+ if (self::$go) {
+ self::$_hasWarnings = true;
+ self::$warnings[] = $warning;
+ }
+ }
+
+ /**
+ * @param string the package name, or other context information that can be used
+ * to differentiate this warning from warnings thrown by other packages
+ * @throws PEAR_WarningException if $context is not a string
+ */
+ public function __construct($context)
+ {
+ if (!is_string($context)) {
+ throw new PEAR_WarningException('$context constructor argument must be a string');
+ }
+ $this->_context = $context;
+ }
+
+ /**
+ * Local stack function for adding a warning - note that package is not needed, as it is
+ * defined in the constructor.
+ *
+ * Note: if you want file/line context, use an exception object
+ * @param PEAR_Exception|string|int Either pass in an exception to use as the warning, or an
+ * error code or some other error class differentiation technique
+ * @param string Error message, use %param% to do automatic parameter replacement from $params
+ * @param array Error parameters
+ * @param string Error level, use the English name
+ */
+ public function localAdd($code, $msg = '', $params = array(), $level = 'warning')
+ {
+ if ($codeOrException instanceof PEAR_Exception) {
+ $this->_warnings[] = $codeOrException;
+ } else {
+ $warning = self::_formatWarning($codeOrException, $this->_context, $level, $msg);
+ $this->_warnings[] = $warning;
+ }
+ }
+
+ /**
+ * Begin a local warning stack session
+ */
+ public function localBegin()
+ {
+ $this->_warnings = array();
+ $this->_go = true;
+ }
+
+ /**
+ * End a local warning stack session
+ * @return array
+ */
+ public function localEnd()
+ {
+ $a = $this->_warnings;
+ $this->_warnings = array();
+ $this->_go = false;
+ return $a;
+ }
+
+ /**
+ * Do not use this function directly - it should only be used by PEAR_ErrorStack
+ * @access private
+ */
+ static public function _catch($err)
+ {
+ self::_signal($err);
+ }
+
+ private static function _signal($warning)
+ {
+ foreach (self::$_observers as $func) {
+ if (is_callable($func)) {
+ call_user_func($func, $this);
+ continue;
+ }
+ settype($func, 'array');
+ switch ($func[0]) {
+ case PEAR_EXCEPTION_PRINT :
+ $f = (isset($func[1])) ? $func[1] : '%s';
+ printf($f, $this->getMessage());
+ break;
+ case PEAR_EXCEPTION_TRIGGER :
+ $f = (isset($func[1])) ? $func[1] : E_USER_NOTICE;
+ trigger_error($this->getMessage(), $f);
+ break;
+ case PEAR_EXCEPTION_DIE :
+ $f = (isset($func[1])) ? $func[1] : '%s';
+ die(printf($f, $this->getMessage()));
+ break;
+ default:
+ trigger_error('invalid observer type', E_USER_WARNING);
+ }
+ }
+ }
+
+ static private function _formatWarning($code, $package, $level, $msg, $params, $backtrace)
+ {
+ return array('package' => $package,
+ 'code' => $code,
+ 'level' => $level,
+ 'message' => self::_formatMessage($msg, $params),
+ 'params' => $params);
+ }
+
+ static private function _formatMessage($msg, $params)
+ {
+ if (count($params)) {
+ foreach ($params as $name => $val) {
+ if (strpos($msg, '%' . $name . '%') !== false) {
+ if (is_array($val)) {
+ // don't pass in an array that you expect to display unless it is 1-dimensional!
+ $val = implode(', ', $val);
+ }
+ if (is_object($val)) {
+ if (method_exists($val, '__toString')) {
+ $val = $val->__toString();
+ } else {
+ $val = 'Object';
+ }
+ }
+ $msg = str_replace('%' . $name . '%', $val, $msg);
+ }
+ }
+ }
+ return $msg;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/PEAR/XMLParser.php b/includes/pear/PEAR/XMLParser.php
new file mode 100644
index 0000000..a70988b
--- /dev/null
+++ b/includes/pear/PEAR/XMLParser.php
@@ -0,0 +1,253 @@
+<?php
+/**
+ * PEAR_XMLParser
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @author Stephan Schmidt (original XML_Unserializer code)
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 1.4.0a1
+ */
+
+/**
+ * Parser for any xml file
+ * @category pear
+ * @package PEAR
+ * @author Greg Beaver <cellog@php.net>
+ * @author Stephan Schmidt (original XML_Unserializer code)
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/PEAR
+ * @since Class available since Release 1.4.0a1
+ */
+class PEAR_XMLParser
+{
+ /**
+ * unserilialized data
+ * @var string $_serializedData
+ */
+ var $_unserializedData = null;
+
+ /**
+ * name of the root tag
+ * @var string $_root
+ */
+ var $_root = null;
+
+ /**
+ * stack for all data that is found
+ * @var array $_dataStack
+ */
+ var $_dataStack = array();
+
+ /**
+ * stack for all values that are generated
+ * @var array $_valStack
+ */
+ var $_valStack = array();
+
+ /**
+ * current tag depth
+ * @var int $_depth
+ */
+ var $_depth = 0;
+
+ /**
+ * The XML encoding to use
+ * @var string $encoding
+ */
+ var $encoding = 'ISO-8859-1';
+
+ /**
+ * @return array
+ */
+ function getData()
+ {
+ return $this->_unserializedData;
+ }
+
+ /**
+ * @param string xml content
+ * @return true|PEAR_Error
+ */
+ function parse($data)
+ {
+ if (!extension_loaded('xml')) {
+ include_once 'PEAR.php';
+ return PEAR::raiseError("XML Extension not found", 1);
+ }
+ $this->_dataStack = $this->_valStack = array();
+ $this->_depth = 0;
+
+ if (
+ strpos($data, 'encoding="UTF-8"')
+ || strpos($data, 'encoding="utf-8"')
+ || strpos($data, "encoding='UTF-8'")
+ || strpos($data, "encoding='utf-8'")
+ ) {
+ $this->encoding = 'UTF-8';
+ }
+
+ if (version_compare(phpversion(), '5.0.0', 'lt') && $this->encoding == 'UTF-8') {
+ $data = utf8_decode($data);
+ $this->encoding = 'ISO-8859-1';
+ }
+
+ $xp = xml_parser_create($this->encoding);
+ xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, 0);
+ xml_set_object($xp, $this);
+ xml_set_element_handler($xp, 'startHandler', 'endHandler');
+ xml_set_character_data_handler($xp, 'cdataHandler');
+ if (!xml_parse($xp, $data)) {
+ $msg = xml_error_string(xml_get_error_code($xp));
+ $line = xml_get_current_line_number($xp);
+ xml_parser_free($xp);
+ include_once 'PEAR.php';
+ return PEAR::raiseError("XML Error: '$msg' on line '$line'", 2);
+ }
+ xml_parser_free($xp);
+ return true;
+ }
+
+ /**
+ * Start element handler for XML parser
+ *
+ * @access private
+ * @param object $parser XML parser object
+ * @param string $element XML element
+ * @param array $attribs attributes of XML tag
+ * @return void
+ */
+ function startHandler($parser, $element, $attribs)
+ {
+ $this->_depth++;
+ $this->_dataStack[$this->_depth] = null;
+
+ $val = array(
+ 'name' => $element,
+ 'value' => null,
+ 'type' => 'string',
+ 'childrenKeys' => array(),
+ 'aggregKeys' => array()
+ );
+
+ if (count($attribs) > 0) {
+ $val['children'] = array();
+ $val['type'] = 'array';
+ $val['children']['attribs'] = $attribs;
+ }
+
+ array_push($this->_valStack, $val);
+ }
+
+ /**
+ * post-process data
+ *
+ * @param string $data
+ * @param string $element element name
+ */
+ function postProcess($data, $element)
+ {
+ return trim($data);
+ }
+
+ /**
+ * End element handler for XML parser
+ *
+ * @access private
+ * @param object XML parser object
+ * @param string
+ * @return void
+ */
+ function endHandler($parser, $element)
+ {
+ $value = array_pop($this->_valStack);
+ $data = $this->postProcess($this->_dataStack[$this->_depth], $element);
+
+ // adjust type of the value
+ switch (strtolower($value['type'])) {
+ // unserialize an array
+ case 'array':
+ if ($data !== '') {
+ $value['children']['_content'] = $data;
+ }
+
+ $value['value'] = isset($value['children']) ? $value['children'] : array();
+ break;
+
+ /*
+ * unserialize a null value
+ */
+ case 'null':
+ $data = null;
+ break;
+
+ /*
+ * unserialize any scalar value
+ */
+ default:
+ settype($data, $value['type']);
+ $value['value'] = $data;
+ break;
+ }
+
+ $parent = array_pop($this->_valStack);
+ if ($parent === null) {
+ $this->_unserializedData = &$value['value'];
+ $this->_root = &$value['name'];
+ return true;
+ }
+
+ // parent has to be an array
+ if (!isset($parent['children']) || !is_array($parent['children'])) {
+ $parent['children'] = array();
+ if ($parent['type'] != 'array') {
+ $parent['type'] = 'array';
+ }
+ }
+
+ if (!empty($value['name'])) {
+ // there already has been a tag with this name
+ if (in_array($value['name'], $parent['childrenKeys'])) {
+ // no aggregate has been created for this tag
+ if (!in_array($value['name'], $parent['aggregKeys'])) {
+ if (isset($parent['children'][$value['name']])) {
+ $parent['children'][$value['name']] = array($parent['children'][$value['name']]);
+ } else {
+ $parent['children'][$value['name']] = array();
+ }
+ array_push($parent['aggregKeys'], $value['name']);
+ }
+ array_push($parent['children'][$value['name']], $value['value']);
+ } else {
+ $parent['children'][$value['name']] = &$value['value'];
+ array_push($parent['childrenKeys'], $value['name']);
+ }
+ } else {
+ array_push($parent['children'],$value['value']);
+ }
+ array_push($this->_valStack, $parent);
+
+ $this->_depth--;
+ }
+
+ /**
+ * Handler for character data
+ *
+ * @access private
+ * @param object XML parser object
+ * @param string CDATA
+ * @return void
+ */
+ function cdataHandler($parser, $cdata)
+ {
+ $this->_dataStack[$this->_depth] .= $cdata;
+ }
+} \ No newline at end of file
diff --git a/includes/pear/PEAR5.php b/includes/pear/PEAR5.php
new file mode 100644
index 0000000..4286067
--- /dev/null
+++ b/includes/pear/PEAR5.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * This is only meant for PHP 5 to get rid of certain strict warning
+ * that doesn't get hidden since it's in the shutdown function
+ */
+class PEAR5
+{
+ /**
+ * If you have a class that's mostly/entirely static, and you need static
+ * properties, you can use this method to simulate them. Eg. in your method(s)
+ * do this: $myVar = &PEAR5::getStaticProperty('myclass', 'myVar');
+ * You MUST use a reference, or they will not persist!
+ *
+ * @access public
+ * @param string $class The calling classname, to prevent clashes
+ * @param string $var The variable to retrieve.
+ * @return mixed A reference to the variable. If not set it will be
+ * auto initialised to NULL.
+ */
+ static function &getStaticProperty($class, $var)
+ {
+ static $properties;
+ if (!isset($properties[$class])) {
+ $properties[$class] = array();
+ }
+
+ if (!array_key_exists($var, $properties[$class])) {
+ $properties[$class][$var] = null;
+ }
+
+ return $properties[$class][$var];
+ }
+} \ No newline at end of file
diff --git a/includes/pear/Structures/Graph.php b/includes/pear/Structures/Graph.php
new file mode 100644
index 0000000..3757f15
--- /dev/null
+++ b/includes/pear/Structures/Graph.php
@@ -0,0 +1,154 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
+// +-----------------------------------------------------------------------------+
+// | Copyright (c) 2003 Sérgio Gonçalves Carvalho |
+// +-----------------------------------------------------------------------------+
+// | This file is part of Structures_Graph. |
+// | |
+// | Structures_Graph is free software; you can redistribute it and/or modify |
+// | it under the terms of the GNU Lesser General Public License as published by |
+// | the Free Software Foundation; either version 2.1 of the License, or |
+// | (at your option) any later version. |
+// | |
+// | Structures_Graph is distributed in the hope that it will be useful, |
+// | but WITHOUT ANY WARRANTY; without even the implied warranty of |
+// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
+// | GNU Lesser General Public License for more details. |
+// | |
+// | You should have received a copy of the GNU Lesser General Public License |
+// | along with Structures_Graph; if not, write to the Free Software |
+// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
+// | 02111-1307 USA |
+// +-----------------------------------------------------------------------------+
+// | Author: Sérgio Carvalho <sergio.carvalho@portugalmail.com> |
+// +-----------------------------------------------------------------------------+
+//
+/**
+ * The Graph.php file contains the definition of the Structures_Graph class
+ *
+ * @see Structures_Graph
+ * @package Structures_Graph
+ */
+
+/* dependencies {{{ */
+/** PEAR base classes */
+require_once 'PEAR.php';
+/** Graph Node */
+require_once 'Structures/Graph/Node.php';
+/* }}} */
+
+define('STRUCTURES_GRAPH_ERROR_GENERIC', 100);
+
+/* class Structures_Graph {{{ */
+/**
+ * The Structures_Graph class represents a graph data structure.
+ *
+ * A Graph is a data structure composed by a set of nodes, connected by arcs.
+ * Graphs may either be directed or undirected. In a directed graph, arcs are
+ * directional, and can be traveled only one way. In an undirected graph, arcs
+ * are bidirectional, and can be traveled both ways.
+ *
+ * @author Sérgio Carvalho <sergio.carvalho@portugalmail.com>
+ * @copyright (c) 2004 by Sérgio Carvalho
+ * @package Structures_Graph
+ */
+/* }}} */
+class Structures_Graph {
+ /* fields {{{ */
+ /**
+ * @access private
+ */
+ var $_nodes = array();
+ /**
+ * @access private
+ */
+ var $_directed = false;
+ /* }}} */
+
+ /* Constructor {{{ */
+ /**
+ *
+ * Constructor
+ *
+ * @param boolean Set to true if the graph is directed. Set to false if it is not directed. (Optional, defaults to true)
+ * @access public
+ */
+ function Structures_Graph($directed = true) {
+ $this->_directed = $directed;
+ }
+ /* }}} */
+
+ /* isDirected {{{ */
+ /**
+ *
+ * Return true if a graph is directed
+ *
+ * @return boolean true if the graph is directed
+ * @access public
+ */
+ function isDirected() {
+ return (boolean) $this->_directed;
+ }
+ /* }}} */
+
+ /* addNode {{{ */
+ /**
+ *
+ * Add a Node to the Graph
+ *
+ * @param Structures_Graph_Node The node to be added.
+ * @access public
+ */
+ function addNode(&$newNode) {
+ // We only add nodes
+ if (!is_a($newNode, 'Structures_Graph_Node')) return Pear::raiseError('Structures_Graph::addNode received an object that is not a Structures_Graph_Node', STRUCTURES_GRAPH_ERROR_GENERIC);
+ // Graphs are node *sets*, so duplicates are forbidden. We allow nodes that are exactly equal, but disallow equal references.
+ foreach($this->_nodes as $key => $node) {
+ /*
+ ZE1 equality operators choke on the recursive cycle introduced by the _graph field in the Node object.
+ So, we'll check references the hard way (change $this->_nodes[$key] and check if the change reflects in
+ $node)
+ */
+ $savedData = $this->_nodes[$key];
+ $referenceIsEqualFlag = false;
+ $this->_nodes[$key] = true;
+ if ($node === true) {
+ $this->_nodes[$key] = false;
+ if ($node === false) $referenceIsEqualFlag = true;
+ }
+ $this->_nodes[$key] = $savedData;
+ if ($referenceIsEqualFlag) return Pear::raiseError('Structures_Graph::addNode received an object that is a duplicate for this dataset', STRUCTURES_GRAPH_ERROR_GENERIC);
+ }
+ $this->_nodes[] =& $newNode;
+ $newNode->setGraph($this);
+ }
+ /* }}} */
+
+ /* removeNode (unimplemented) {{{ */
+ /**
+ *
+ * Remove a Node from the Graph
+ *
+ * @todo This is unimplemented
+ * @param Structures_Graph_Node The node to be removed from the graph
+ * @access public
+ */
+ function removeNode(&$node) {
+ }
+ /* }}} */
+
+ /* getNodes {{{ */
+ /**
+ *
+ * Return the node set, in no particular order. For ordered node sets, use a Graph Manipulator insted.
+ *
+ * @access public
+ * @see Structures_Graph_Manipulator_TopologicalSorter
+ * @return array The set of nodes in this graph
+ */
+ function &getNodes() {
+ return $this->_nodes;
+ }
+ /* }}} */
+}
+?>
diff --git a/includes/pear/Structures/Graph/Manipulator/AcyclicTest.php b/includes/pear/Structures/Graph/Manipulator/AcyclicTest.php
new file mode 100644
index 0000000..fc1ba92
--- /dev/null
+++ b/includes/pear/Structures/Graph/Manipulator/AcyclicTest.php
@@ -0,0 +1,136 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
+// +-----------------------------------------------------------------------------+
+// | Copyright (c) 2003 Sérgio Gonçalves Carvalho |
+// +-----------------------------------------------------------------------------+
+// | This file is part of Structures_Graph. |
+// | |
+// | Structures_Graph is free software; you can redistribute it and/or modify |
+// | it under the terms of the GNU Lesser General Public License as published by |
+// | the Free Software Foundation; either version 2.1 of the License, or |
+// | (at your option) any later version. |
+// | |
+// | Structures_Graph is distributed in the hope that it will be useful, |
+// | but WITHOUT ANY WARRANTY; without even the implied warranty of |
+// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
+// | GNU Lesser General Public License for more details. |
+// | |
+// | You should have received a copy of the GNU Lesser General Public License |
+// | along with Structures_Graph; if not, write to the Free Software |
+// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
+// | 02111-1307 USA |
+// +-----------------------------------------------------------------------------+
+// | Author: Sérgio Carvalho <sergio.carvalho@portugalmail.com> |
+// +-----------------------------------------------------------------------------+
+//
+/**
+ * This file contains the definition of the Structures_Graph_Manipulator_AcyclicTest graph manipulator.
+ *
+ * @see Structures_Graph_Manipulator_AcyclicTest
+ * @package Structures_Graph
+ */
+
+/* dependencies {{{ */
+/** */
+require_once 'PEAR.php';
+/** */
+require_once 'Structures/Graph.php';
+/** */
+require_once 'Structures/Graph/Node.php';
+/* }}} */
+
+/* class Structures_Graph_Manipulator_AcyclicTest {{{ */
+/**
+ * The Structures_Graph_Manipulator_AcyclicTest is a graph manipulator
+ * which tests whether a graph contains a cycle.
+ *
+ * The definition of an acyclic graph used in this manipulator is that of a
+ * DAG. The graph must be directed, or else it is considered cyclic, even when
+ * there are no arcs.
+ *
+ * @author Sérgio Carvalho <sergio.carvalho@portugalmail.com>
+ * @copyright (c) 2004 by Sérgio Carvalho
+ * @package Structures_Graph
+ */
+class Structures_Graph_Manipulator_AcyclicTest {
+ /* _nonVisitedInDegree {{{ */
+ /**
+ *
+ * This is a variant of Structures_Graph::inDegree which does
+ * not count nodes marked as visited.
+ *
+ * @access private
+ * @return integer Number of non-visited nodes that link to this one
+ */
+ function _nonVisitedInDegree(&$node) {
+ $result = 0;
+ $graphNodes =& $node->_graph->getNodes();
+ foreach (array_keys($graphNodes) as $key) {
+ if ((!$graphNodes[$key]->getMetadata('acyclic-test-visited')) && $graphNodes[$key]->connectsTo($node)) $result++;
+ }
+ return $result;
+
+ }
+ /* }}} */
+
+ /* _isAcyclic {{{ */
+ /**
+ * @access private
+ */
+ function _isAcyclic(&$graph) {
+ // Mark every node as not visited
+ $nodes =& $graph->getNodes();
+ $nodeKeys = array_keys($nodes);
+ $refGenerator = array();
+ foreach($nodeKeys as $key) {
+ $refGenerator[] = false;
+ $nodes[$key]->setMetadata('acyclic-test-visited', $refGenerator[sizeof($refGenerator) - 1]);
+ }
+
+ // Iteratively peel off leaf nodes
+ do {
+ // Find out which nodes are leafs (excluding visited nodes)
+ $leafNodes = array();
+ foreach($nodeKeys as $key) {
+ if ((!$nodes[$key]->getMetadata('acyclic-test-visited')) && Structures_Graph_Manipulator_AcyclicTest::_nonVisitedInDegree($nodes[$key]) == 0) {
+ $leafNodes[] =& $nodes[$key];
+ }
+ }
+ // Mark leafs as visited
+ for ($i=sizeof($leafNodes) - 1; $i>=0; $i--) {
+ $visited =& $leafNodes[$i]->getMetadata('acyclic-test-visited');
+ $visited = true;
+ $leafNodes[$i]->setMetadata('acyclic-test-visited', $visited);
+ }
+ } while (sizeof($leafNodes) > 0);
+
+ // If graph is a DAG, there should be no non-visited nodes. Let's try to prove otherwise
+ $result = true;
+ foreach($nodeKeys as $key) if (!$nodes[$key]->getMetadata('acyclic-test-visited')) $result = false;
+
+ // Cleanup visited marks
+ foreach($nodeKeys as $key) $nodes[$key]->unsetMetadata('acyclic-test-visited');
+
+ return $result;
+ }
+ /* }}} */
+
+ /* isAcyclic {{{ */
+ /**
+ *
+ * isAcyclic returns true if a graph contains no cycles, false otherwise.
+ *
+ * @return boolean true iff graph is acyclic
+ * @access public
+ */
+ function isAcyclic(&$graph) {
+ // We only test graphs
+ if (!is_a($graph, 'Structures_Graph')) return Pear::raiseError('Structures_Graph_Manipulator_AcyclicTest::isAcyclic received an object that is not a Structures_Graph', STRUCTURES_GRAPH_ERROR_GENERIC);
+ if (!$graph->isDirected()) return false; // Only directed graphs may be acyclic
+
+ return Structures_Graph_Manipulator_AcyclicTest::_isAcyclic($graph);
+ }
+ /* }}} */
+}
+/* }}} */
+?>
diff --git a/includes/pear/Structures/Graph/Manipulator/TopologicalSorter.php b/includes/pear/Structures/Graph/Manipulator/TopologicalSorter.php
new file mode 100644
index 0000000..98a9fa0
--- /dev/null
+++ b/includes/pear/Structures/Graph/Manipulator/TopologicalSorter.php
@@ -0,0 +1,153 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
+// +-----------------------------------------------------------------------------+
+// | Copyright (c) 2003 Sérgio Gonçalves Carvalho |
+// +-----------------------------------------------------------------------------+
+// | This file is part of Structures_Graph. |
+// | |
+// | Structures_Graph is free software; you can redistribute it and/or modify |
+// | it under the terms of the GNU Lesser General Public License as published by |
+// | the Free Software Foundation; either version 2.1 of the License, or |
+// | (at your option) any later version. |
+// | |
+// | Structures_Graph is distributed in the hope that it will be useful, |
+// | but WITHOUT ANY WARRANTY; without even the implied warranty of |
+// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
+// | GNU Lesser General Public License for more details. |
+// | |
+// | You should have received a copy of the GNU Lesser General Public License |
+// | along with Structures_Graph; if not, write to the Free Software |
+// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
+// | 02111-1307 USA |
+// +-----------------------------------------------------------------------------+
+// | Author: Sérgio Carvalho <sergio.carvalho@portugalmail.com> |
+// +-----------------------------------------------------------------------------+
+//
+/**
+ * This file contains the definition of the Structures_Graph_Manipulator_TopologicalSorter class.
+ *
+ * @see Structures_Graph_Manipulator_TopologicalSorter
+ * @package Structures_Graph
+ */
+
+/* dependencies {{{ */
+/** */
+require_once 'PEAR.php';
+/** */
+require_once 'Structures/Graph.php';
+/** */
+require_once 'Structures/Graph/Node.php';
+/** */
+require_once 'Structures/Graph/Manipulator/AcyclicTest.php';
+/* }}} */
+
+/* class Structures_Graph_Manipulator_TopologicalSorter {{{ */
+/**
+ * The Structures_Graph_Manipulator_TopologicalSorter is a manipulator
+ * which is able to return the set of nodes in a graph, sorted by topological
+ * order.
+ *
+ * A graph may only be sorted topologically iff it's a DAG. You can test it
+ * with the Structures_Graph_Manipulator_AcyclicTest.
+ *
+ * @author Sérgio Carvalho <sergio.carvalho@portugalmail.com>
+ * @copyright (c) 2004 by Sérgio Carvalho
+ * @see Structures_Graph_Manipulator_AcyclicTest
+ * @package Structures_Graph
+ */
+class Structures_Graph_Manipulator_TopologicalSorter {
+ /* _nonVisitedInDegree {{{ */
+ /**
+ *
+ * This is a variant of Structures_Graph::inDegree which does
+ * not count nodes marked as visited.
+ *
+ * @access private
+ * @return integer Number of non-visited nodes that link to this one
+ */
+ function _nonVisitedInDegree(&$node) {
+ $result = 0;
+ $graphNodes =& $node->_graph->getNodes();
+ foreach (array_keys($graphNodes) as $key) {
+ if ((!$graphNodes[$key]->getMetadata('topological-sort-visited')) && $graphNodes[$key]->connectsTo($node)) $result++;
+ }
+ return $result;
+
+ }
+ /* }}} */
+
+ /* _sort {{{ */
+ /**
+ * @access private
+ */
+ function _sort(&$graph) {
+ // Mark every node as not visited
+ $nodes =& $graph->getNodes();
+ $nodeKeys = array_keys($nodes);
+ $refGenerator = array();
+ foreach($nodeKeys as $key) {
+ $refGenerator[] = false;
+ $nodes[$key]->setMetadata('topological-sort-visited', $refGenerator[sizeof($refGenerator) - 1]);
+ }
+
+ // Iteratively peel off leaf nodes
+ $topologicalLevel = 0;
+ do {
+ // Find out which nodes are leafs (excluding visited nodes)
+ $leafNodes = array();
+ foreach($nodeKeys as $key) {
+ if ((!$nodes[$key]->getMetadata('topological-sort-visited')) && Structures_Graph_Manipulator_TopologicalSorter::_nonVisitedInDegree($nodes[$key]) == 0) {
+ $leafNodes[] =& $nodes[$key];
+ }
+ }
+ // Mark leafs as visited
+ $refGenerator[] = $topologicalLevel;
+ for ($i=sizeof($leafNodes) - 1; $i>=0; $i--) {
+ $visited =& $leafNodes[$i]->getMetadata('topological-sort-visited');
+ $visited = true;
+ $leafNodes[$i]->setMetadata('topological-sort-visited', $visited);
+ $leafNodes[$i]->setMetadata('topological-sort-level', $refGenerator[sizeof($refGenerator) - 1]);
+ }
+ $topologicalLevel++;
+ } while (sizeof($leafNodes) > 0);
+
+ // Cleanup visited marks
+ foreach($nodeKeys as $key) $nodes[$key]->unsetMetadata('topological-sort-visited');
+ }
+ /* }}} */
+
+ /* sort {{{ */
+ /**
+ *
+ * sort returns the graph's nodes, sorted by topological order.
+ *
+ * The result is an array with
+ * as many entries as topological levels. Each entry in this array is an array of nodes within
+ * the given topological level.
+ *
+ * @return array The graph's nodes, sorted by topological order.
+ * @access public
+ */
+ function sort(&$graph) {
+ // We only sort graphs
+ if (!is_a($graph, 'Structures_Graph')) return Pear::raiseError('Structures_Graph_Manipulator_TopologicalSorter::sort received an object that is not a Structures_Graph', STRUCTURES_GRAPH_ERROR_GENERIC);
+ if (!Structures_Graph_Manipulator_AcyclicTest::isAcyclic($graph)) return Pear::raiseError('Structures_Graph_Manipulator_TopologicalSorter::sort received an graph that has cycles', STRUCTURES_GRAPH_ERROR_GENERIC);
+
+ Structures_Graph_Manipulator_TopologicalSorter::_sort($graph);
+ $result = array();
+
+ // Fill out result array
+ $nodes =& $graph->getNodes();
+ $nodeKeys = array_keys($nodes);
+ foreach($nodeKeys as $key) {
+ if (!array_key_exists($nodes[$key]->getMetadata('topological-sort-level'), $result)) $result[$nodes[$key]->getMetadata('topological-sort-level')] = array();
+ $result[$nodes[$key]->getMetadata('topological-sort-level')][] =& $nodes[$key];
+ $nodes[$key]->unsetMetadata('topological-sort-level');
+ }
+
+ return $result;
+ }
+ /* }}} */
+}
+/* }}} */
+?>
diff --git a/includes/pear/Structures/Graph/Node.php b/includes/pear/Structures/Graph/Node.php
new file mode 100644
index 0000000..95afa2b
--- /dev/null
+++ b/includes/pear/Structures/Graph/Node.php
@@ -0,0 +1,342 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
+// +-----------------------------------------------------------------------------+
+// | Copyright (c) 2003 Sérgio Gonçalves Carvalho |
+// +-----------------------------------------------------------------------------+
+// | This file is part of Structures_Graph. |
+// | |
+// | Structures_Graph is free software; you can redistribute it and/or modify |
+// | it under the terms of the GNU Lesser General Public License as published by |
+// | the Free Software Foundation; either version 2.1 of the License, or |
+// | (at your option) any later version. |
+// | |
+// | Structures_Graph is distributed in the hope that it will be useful, |
+// | but WITHOUT ANY WARRANTY; without even the implied warranty of |
+// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
+// | GNU Lesser General Public License for more details. |
+// | |
+// | You should have received a copy of the GNU Lesser General Public License |
+// | along with Structures_Graph; if not, write to the Free Software |
+// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
+// | 02111-1307 USA |
+// +-----------------------------------------------------------------------------+
+// | Author: Sérgio Carvalho <sergio.carvalho@portugalmail.com> |
+// +-----------------------------------------------------------------------------+
+//
+/**
+ * This file contains the definition of the Structures_Graph_Node class
+ *
+ * @see Structures_Graph_Node
+ * @package Structures_Graph
+ */
+
+/* dependencies {{{ */
+/** */
+require_once 'PEAR.php';
+/** */
+require_once 'Structures/Graph.php';
+/* }}} */
+
+/* class Structures_Graph_Node {{{ */
+/**
+ * The Structures_Graph_Node class represents a Node that can be member of a
+ * graph node set.
+ *
+ * A graph node can contain data. Under this API, the node contains default data,
+ * and key index data. It behaves, thus, both as a regular data node, and as a
+ * dictionary (or associative array) node.
+ *
+ * Regular data is accessed via getData and setData. Key indexed data is accessed
+ * via getMetadata and setMetadata.
+ *
+ * @author Sérgio Carvalho <sergio.carvalho@portugalmail.com>
+ * @copyright (c) 2004 by Sérgio Carvalho
+ * @package Structures_Graph
+ */
+/* }}} */
+class Structures_Graph_Node {
+ /* fields {{{ */
+ /**
+ * @access private
+ */
+ var $_data = null;
+ /** @access private */
+ var $_metadata = array();
+ /** @access private */
+ var $_arcs = array();
+ /** @access private */
+ var $_graph = null;
+ /* }}} */
+
+ /* Constructor {{{ */
+ /**
+ *
+ * Constructor
+ *
+ * @access public
+ */
+ function Structures_Graph_Node() {
+ }
+ /* }}} */
+
+ /* getGraph {{{ */
+ /**
+ *
+ * Node graph getter
+ *
+ * @return Structures_Graph Graph where node is stored
+ * @access public
+ */
+ function &getGraph() {
+ return $this->_graph;
+ }
+ /* }}} */
+
+ /* setGraph {{{ */
+ /**
+ *
+ * Node graph setter. This method should not be called directly. Use Graph::addNode instead.
+ *
+ * @param Structures_Graph Set the graph for this node.
+ * @see Structures_Graph::addNode()
+ * @access public
+ */
+ function setGraph(&$graph) {
+ $this->_graph =& $graph;
+ }
+ /* }}} */
+
+ /* getData {{{ */
+ /**
+ *
+ * Node data getter.
+ *
+ * Each graph node can contain a reference to one variable. This is the getter for that reference.
+ *
+ * @return mixed Data stored in node
+ * @access public
+ */
+ function &getData() {
+ return $this->_data;
+ }
+ /* }}} */
+
+ /* setData {{{ */
+ /**
+ *
+ * Node data setter
+ *
+ * Each graph node can contain a reference to one variable. This is the setter for that reference.
+ *
+ * @return mixed Data to store in node
+ * @access public
+ */
+ function setData($data) {
+ $this->_data =& $data;
+ }
+ /* }}} */
+
+ /* metadataKeyExists {{{ */
+ /**
+ *
+ * Test for existence of metadata under a given key.
+ *
+ * Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an
+ * associative array or in a dictionary. This method tests whether a given metadata key exists for this node.
+ *
+ * @param string Key to test
+ * @return boolean
+ * @access public
+ */
+ function metadataKeyExists($key) {
+ return array_key_exists($key, $this->_metadata);
+ }
+ /* }}} */
+
+ /* getMetadata {{{ */
+ /**
+ *
+ * Node metadata getter
+ *
+ * Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an
+ * associative array or in a dictionary. This method gets the data under the given key. If the key does
+ * not exist, an error will be thrown, so testing using metadataKeyExists might be needed.
+ *
+ * @param string Key
+ * @param boolean nullIfNonexistent (defaults to false).
+ * @return mixed Metadata Data stored in node under given key
+ * @see metadataKeyExists
+ * @access public
+ */
+ function &getMetadata($key, $nullIfNonexistent = false) {
+ if (array_key_exists($key, $this->_metadata)) {
+ return $this->_metadata[$key];
+ } else {
+ if ($nullIfNonexistent) {
+ $a = null;
+ return $a;
+ } else {
+ $a = Pear::raiseError('Structures_Graph_Node::getMetadata: Requested key does not exist', STRUCTURES_GRAPH_ERROR_GENERIC);
+ return $a;
+ }
+ }
+ }
+ /* }}} */
+
+ /* unsetMetadata {{{ */
+ /**
+ *
+ * Delete metadata by key
+ *
+ * Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an
+ * associative array or in a dictionary. This method removes any data that might be stored under the provided key.
+ * If the key does not exist, no error is thrown, so it is safe using this method without testing for key existence.
+ *
+ * @param string Key
+ * @access public
+ */
+ function unsetMetadata($key) {
+ if (array_key_exists($key, $this->_metadata)) unset($this->_metadata[$key]);
+ }
+ /* }}} */
+
+ /* setMetadata {{{ */
+ /**
+ *
+ * Node metadata setter
+ *
+ * Each graph node can contain multiple 'metadata' entries, each stored under a different key, as in an
+ * associative array or in a dictionary. This method stores data under the given key. If the key already exists,
+ * previously stored data is discarded.
+ *
+ * @param string Key
+ * @param mixed Data
+ * @access public
+ */
+ function setMetadata($key, $data) {
+ $this->_metadata[$key] =& $data;
+ }
+ /* }}} */
+
+ /* _connectTo {{{ */
+ /** @access private */
+ function _connectTo(&$destinationNode) {
+ $this->_arcs[] =& $destinationNode;
+ }
+ /* }}} */
+
+ /* connectTo {{{ */
+ /**
+ *
+ * Connect this node to another one.
+ *
+ * If the graph is not directed, the reverse arc, connecting $destinationNode to $this is also created.
+ *
+ * @param Structures_Graph_Node Node to connect to
+ * @access public
+ */
+ function connectTo(&$destinationNode) {
+ // We only connect to nodes
+ if (!is_a($destinationNode, 'Structures_Graph_Node')) return Pear::raiseError('Structures_Graph_Node::connectTo received an object that is not a Structures_Graph_Node', STRUCTURES_GRAPH_ERROR_GENERIC);
+ // Nodes must already be in graphs to be connected
+ if ($this->_graph == null) return Pear::raiseError('Structures_Graph_Node::connectTo Tried to connect a node that is not in a graph', STRUCTURES_GRAPH_ERROR_GENERIC);
+ if ($destinationNode->getGraph() == null) return Pear::raiseError('Structures_Graph_Node::connectTo Tried to connect to a node that is not in a graph', STRUCTURES_GRAPH_ERROR_GENERIC);
+ // Connect here
+ $this->_connectTo($destinationNode);
+ // If graph is undirected, connect back
+ if (!$this->_graph->isDirected()) {
+ $destinationNode->_connectTo($this);
+ }
+ }
+ /* }}} */
+
+ /* getNeighbours {{{ */
+ /**
+ *
+ * Return nodes connected to this one.
+ *
+ * @return array Array of nodes
+ * @access public
+ */
+ function getNeighbours() {
+ return $this->_arcs;
+ }
+ /* }}} */
+
+ /* connectsTo {{{ */
+ /**
+ *
+ * Test wether this node has an arc to the target node
+ *
+ * @return boolean True if the two nodes are connected
+ * @access public
+ */
+ function connectsTo(&$target) {
+ if (version_compare(PHP_VERSION, '5.0.0') >= 0) {
+ return in_array($target, $this->getNeighbours(), true);
+ }
+
+ $copy = $target;
+ $arcKeys = array_keys($this->_arcs);
+ foreach($arcKeys as $key) {
+ /* ZE1 chokes on this expression:
+ if ($target === $arc) return true;
+ so, we'll use more convoluted stuff
+ */
+ $arc =& $this->_arcs[$key];
+ $target = true;
+ if ($arc === true) {
+ $target = false;
+ if ($arc === false) {
+ $target = $copy;
+ return true;
+ }
+ }
+ }
+ $target = $copy;
+ return false;
+ }
+ /* }}} */
+
+ /* inDegree {{{ */
+ /**
+ *
+ * Calculate the in degree of the node.
+ *
+ * The indegree for a node is the number of arcs entering the node. For non directed graphs,
+ * the indegree is equal to the outdegree.
+ *
+ * @return integer In degree of the node
+ * @access public
+ */
+ function inDegree() {
+ if ($this->_graph == null) return 0;
+ if (!$this->_graph->isDirected()) return $this->outDegree();
+ $result = 0;
+ $graphNodes =& $this->_graph->getNodes();
+ foreach (array_keys($graphNodes) as $key) {
+ if ($graphNodes[$key]->connectsTo($this)) $result++;
+ }
+ return $result;
+
+ }
+ /* }}} */
+
+ /* outDegree {{{ */
+ /**
+ *
+ * Calculate the out degree of the node.
+ *
+ * The outdegree for a node is the number of arcs exiting the node. For non directed graphs,
+ * the outdegree is always equal to the indegree.
+ *
+ * @return integer Out degree of the node
+ * @access public
+ */
+ function outDegree() {
+ if ($this->_graph == null) return 0;
+ return sizeof($this->_arcs);
+ }
+ /* }}} */
+}
+?>
diff --git a/includes/pear/System.php b/includes/pear/System.php
new file mode 100644
index 0000000..b419605
--- /dev/null
+++ b/includes/pear/System.php
@@ -0,0 +1,624 @@
+<?php
+/**
+ * File/Directory manipulation
+ *
+ * PHP versions 4 and 5
+ *
+ * @category pear
+ * @package System
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @copyright 1997-2009 The Authors
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**
+ * base class
+ */
+require_once 'PEAR.php';
+require_once 'Console/Getopt.php';
+
+$GLOBALS['_System_temp_files'] = array();
+
+/**
+* System offers cross plattform compatible system functions
+*
+* Static functions for different operations. Should work under
+* Unix and Windows. The names and usage has been taken from its respectively
+* GNU commands. The functions will return (bool) false on error and will
+* trigger the error with the PHP trigger_error() function (you can silence
+* the error by prefixing a '@' sign after the function call, but this
+* is not recommended practice. Instead use an error handler with
+* {@link set_error_handler()}).
+*
+* Documentation on this class you can find in:
+* http://pear.php.net/manual/
+*
+* Example usage:
+* if (!@System::rm('-r file1 dir1')) {
+* print "could not delete file1 or dir1";
+* }
+*
+* In case you need to to pass file names with spaces,
+* pass the params as an array:
+*
+* System::rm(array('-r', $file1, $dir1));
+*
+* @category pear
+* @package System
+* @author Tomas V.V. Cox <cox@idecnet.com>
+* @copyright 1997-2006 The PHP Group
+* @license http://opensource.org/licenses/bsd-license.php New BSD License
+* @version Release: @package_version@
+* @link http://pear.php.net/package/PEAR
+* @since Class available since Release 0.1
+* @static
+*/
+class System
+{
+ /**
+ * returns the commandline arguments of a function
+ *
+ * @param string $argv the commandline
+ * @param string $short_options the allowed option short-tags
+ * @param string $long_options the allowed option long-tags
+ * @return array the given options and there values
+ */
+ public static function _parseArgs($argv, $short_options, $long_options = null)
+ {
+ if (!is_array($argv) && $argv !== null) {
+ /*
+ // Quote all items that are a short option
+ $av = preg_split('/(\A| )--?[a-z0-9]+[ =]?((?<!\\\\)((,\s*)|((?<!,)\s+))?)/i', $argv, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
+ $offset = 0;
+ foreach ($av as $a) {
+ $b = trim($a[0]);
+ if ($b{0} == '"' || $b{0} == "'") {
+ continue;
+ }
+
+ $escape = escapeshellarg($b);
+ $pos = $a[1] + $offset;
+ $argv = substr_replace($argv, $escape, $pos, strlen($b));
+ $offset += 2;
+ }
+ */
+
+ // Find all items, quoted or otherwise
+ preg_match_all("/(?:[\"'])(.*?)(?:['\"])|([^\s]+)/", $argv, $av);
+ $argv = $av[1];
+ foreach ($av[2] as $k => $a) {
+ if (empty($a)) {
+ continue;
+ }
+ $argv[$k] = trim($a) ;
+ }
+ }
+ return Console_Getopt::getopt2($argv, $short_options, $long_options);
+ }
+
+ /**
+ * Output errors with PHP trigger_error(). You can silence the errors
+ * with prefixing a "@" sign to the function call: @System::mkdir(..);
+ *
+ * @param mixed $error a PEAR error or a string with the error message
+ * @return bool false
+ */
+ public static function raiseError($error)
+ {
+ if (PEAR::isError($error)) {
+ $error = $error->getMessage();
+ }
+ trigger_error($error, E_USER_WARNING);
+ return false;
+ }
+
+ /**
+ * Creates a nested array representing the structure of a directory
+ *
+ * System::_dirToStruct('dir1', 0) =>
+ * Array
+ * (
+ * [dirs] => Array
+ * (
+ * [0] => dir1
+ * )
+ *
+ * [files] => Array
+ * (
+ * [0] => dir1/file2
+ * [1] => dir1/file3
+ * )
+ * )
+ * @param string $sPath Name of the directory
+ * @param integer $maxinst max. deep of the lookup
+ * @param integer $aktinst starting deep of the lookup
+ * @param bool $silent if true, do not emit errors.
+ * @return array the structure of the dir
+ */
+ public static function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false)
+ {
+ $struct = array('dirs' => array(), 'files' => array());
+ if (($dir = @opendir($sPath)) === false) {
+ if (!$silent) {
+ System::raiseError("Could not open dir $sPath");
+ }
+ return $struct; // XXX could not open error
+ }
+
+ $struct['dirs'][] = $sPath = realpath($sPath); // XXX don't add if '.' or '..' ?
+ $list = array();
+ while (false !== ($file = readdir($dir))) {
+ if ($file != '.' && $file != '..') {
+ $list[] = $file;
+ }
+ }
+
+ closedir($dir);
+ natsort($list);
+ if ($aktinst < $maxinst || $maxinst == 0) {
+ foreach ($list as $val) {
+ $path = $sPath . DIRECTORY_SEPARATOR . $val;
+ if (is_dir($path) && !is_link($path)) {
+ $tmp = System::_dirToStruct($path, $maxinst, $aktinst+1, $silent);
+ $struct = array_merge_recursive($struct, $tmp);
+ } else {
+ $struct['files'][] = $path;
+ }
+ }
+ }
+
+ return $struct;
+ }
+
+ /**
+ * Creates a nested array representing the structure of a directory and files
+ *
+ * @param array $files Array listing files and dirs
+ * @return array
+ * @see System::_dirToStruct()
+ */
+ public static function _multipleToStruct($files)
+ {
+ $struct = array('dirs' => array(), 'files' => array());
+ settype($files, 'array');
+ foreach ($files as $file) {
+ if (is_dir($file) && !is_link($file)) {
+ $tmp = System::_dirToStruct($file, 0);
+ $struct = array_merge_recursive($tmp, $struct);
+ } else {
+ if (!in_array($file, $struct['files'])) {
+ $struct['files'][] = $file;
+ }
+ }
+ }
+ return $struct;
+ }
+
+ /**
+ * The rm command for removing files.
+ * Supports multiple files and dirs and also recursive deletes
+ *
+ * @param string $args the arguments for rm
+ * @return mixed PEAR_Error or true for success
+ */
+ public static function rm($args)
+ {
+ $opts = System::_parseArgs($args, 'rf'); // "f" does nothing but I like it :-)
+ if (PEAR::isError($opts)) {
+ return System::raiseError($opts);
+ }
+ foreach ($opts[0] as $opt) {
+ if ($opt[0] == 'r') {
+ $do_recursive = true;
+ }
+ }
+ $ret = true;
+ if (isset($do_recursive)) {
+ $struct = System::_multipleToStruct($opts[1]);
+ foreach ($struct['files'] as $file) {
+ if (!@unlink($file)) {
+ $ret = false;
+ }
+ }
+
+ rsort($struct['dirs']);
+ foreach ($struct['dirs'] as $dir) {
+ if (!@rmdir($dir)) {
+ $ret = false;
+ }
+ }
+ } else {
+ foreach ($opts[1] as $file) {
+ $delete = (is_dir($file)) ? 'rmdir' : 'unlink';
+ if (!@$delete($file)) {
+ $ret = false;
+ }
+ }
+ }
+ return $ret;
+ }
+
+ /**
+ * Make directories.
+ *
+ * The -p option will create parent directories
+ * @param string $args the name of the director(y|ies) to create
+ * @return bool True for success
+ */
+ public static function mkDir($args)
+ {
+ $opts = System::_parseArgs($args, 'pm:');
+ if (PEAR::isError($opts)) {
+ return System::raiseError($opts);
+ }
+
+ $mode = 0777; // default mode
+ foreach ($opts[0] as $opt) {
+ if ($opt[0] == 'p') {
+ $create_parents = true;
+ } elseif ($opt[0] == 'm') {
+ // if the mode is clearly an octal number (starts with 0)
+ // convert it to decimal
+ if (strlen($opt[1]) && $opt[1]{0} == '0') {
+ $opt[1] = octdec($opt[1]);
+ } else {
+ // convert to int
+ $opt[1] += 0;
+ }
+ $mode = $opt[1];
+ }
+ }
+
+ $ret = true;
+ if (isset($create_parents)) {
+ foreach ($opts[1] as $dir) {
+ $dirstack = array();
+ while ((!file_exists($dir) || !is_dir($dir)) &&
+ $dir != DIRECTORY_SEPARATOR) {
+ array_unshift($dirstack, $dir);
+ $dir = dirname($dir);
+ }
+
+ while ($newdir = array_shift($dirstack)) {
+ if (!is_writeable(dirname($newdir))) {
+ $ret = false;
+ break;
+ }
+
+ if (!mkdir($newdir, $mode)) {
+ $ret = false;
+ }
+ }
+ }
+ } else {
+ foreach($opts[1] as $dir) {
+ if ((@file_exists($dir) || !is_dir($dir)) && !mkdir($dir, $mode)) {
+ $ret = false;
+ }
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Concatenate files
+ *
+ * Usage:
+ * 1) $var = System::cat('sample.txt test.txt');
+ * 2) System::cat('sample.txt test.txt > final.txt');
+ * 3) System::cat('sample.txt test.txt >> final.txt');
+ *
+ * Note: as the class use fopen, urls should work also (test that)
+ *
+ * @param string $args the arguments
+ * @return boolean true on success
+ */
+ public static function &cat($args)
+ {
+ $ret = null;
+ $files = array();
+ if (!is_array($args)) {
+ $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
+ }
+
+ $count_args = count($args);
+ for ($i = 0; $i < $count_args; $i++) {
+ if ($args[$i] == '>') {
+ $mode = 'wb';
+ $outputfile = $args[$i+1];
+ break;
+ } elseif ($args[$i] == '>>') {
+ $mode = 'ab+';
+ $outputfile = $args[$i+1];
+ break;
+ } else {
+ $files[] = $args[$i];
+ }
+ }
+ $outputfd = false;
+ if (isset($mode)) {
+ if (!$outputfd = fopen($outputfile, $mode)) {
+ $err = System::raiseError("Could not open $outputfile");
+ return $err;
+ }
+ $ret = true;
+ }
+ foreach ($files as $file) {
+ if (!$fd = fopen($file, 'r')) {
+ System::raiseError("Could not open $file");
+ continue;
+ }
+ while ($cont = fread($fd, 2048)) {
+ if (is_resource($outputfd)) {
+ fwrite($outputfd, $cont);
+ } else {
+ $ret .= $cont;
+ }
+ }
+ fclose($fd);
+ }
+ if (is_resource($outputfd)) {
+ fclose($outputfd);
+ }
+ return $ret;
+ }
+
+ /**
+ * Creates temporary files or directories. This function will remove
+ * the created files when the scripts finish its execution.
+ *
+ * Usage:
+ * 1) $tempfile = System::mktemp("prefix");
+ * 2) $tempdir = System::mktemp("-d prefix");
+ * 3) $tempfile = System::mktemp();
+ * 4) $tempfile = System::mktemp("-t /var/tmp prefix");
+ *
+ * prefix -> The string that will be prepended to the temp name
+ * (defaults to "tmp").
+ * -d -> A temporary dir will be created instead of a file.
+ * -t -> The target dir where the temporary (file|dir) will be created. If
+ * this param is missing by default the env vars TMP on Windows or
+ * TMPDIR in Unix will be used. If these vars are also missing
+ * c:\windows\temp or /tmp will be used.
+ *
+ * @param string $args The arguments
+ * @return mixed the full path of the created (file|dir) or false
+ * @see System::tmpdir()
+ */
+ public static function mktemp($args = null)
+ {
+ static $first_time = true;
+ $opts = System::_parseArgs($args, 't:d');
+ if (PEAR::isError($opts)) {
+ return System::raiseError($opts);
+ }
+
+ foreach ($opts[0] as $opt) {
+ if ($opt[0] == 'd') {
+ $tmp_is_dir = true;
+ } elseif ($opt[0] == 't') {
+ $tmpdir = $opt[1];
+ }
+ }
+
+ $prefix = (isset($opts[1][0])) ? $opts[1][0] : 'tmp';
+ if (!isset($tmpdir)) {
+ $tmpdir = System::tmpdir();
+ }
+
+ if (!System::mkDir(array('-p', $tmpdir))) {
+ return false;
+ }
+
+ $tmp = tempnam($tmpdir, $prefix);
+ if (isset($tmp_is_dir)) {
+ unlink($tmp); // be careful possible race condition here
+ if (!mkdir($tmp, 0700)) {
+ return System::raiseError("Unable to create temporary directory $tmpdir");
+ }
+ }
+
+ $GLOBALS['_System_temp_files'][] = $tmp;
+ if (isset($tmp_is_dir)) {
+ //$GLOBALS['_System_temp_files'][] = dirname($tmp);
+ }
+
+ if ($first_time) {
+ PEAR::registerShutdownFunc(array('System', '_removeTmpFiles'));
+ $first_time = false;
+ }
+
+ return $tmp;
+ }
+
+ /**
+ * Remove temporary files created my mkTemp. This function is executed
+ * at script shutdown time
+ */
+ public static function _removeTmpFiles()
+ {
+ if (count($GLOBALS['_System_temp_files'])) {
+ $delete = $GLOBALS['_System_temp_files'];
+ array_unshift($delete, '-r');
+ System::rm($delete);
+ $GLOBALS['_System_temp_files'] = array();
+ }
+ }
+
+ /**
+ * Get the path of the temporal directory set in the system
+ * by looking in its environments variables.
+ * Note: php.ini-recommended removes the "E" from the variables_order setting,
+ * making unavaible the $_ENV array, that s why we do tests with _ENV
+ *
+ * @return string The temporary directory on the system
+ */
+ public static function tmpdir()
+ {
+ if (OS_WINDOWS) {
+ if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) {
+ return $var;
+ }
+ if ($var = isset($_ENV['TEMP']) ? $_ENV['TEMP'] : getenv('TEMP')) {
+ return $var;
+ }
+ if ($var = isset($_ENV['USERPROFILE']) ? $_ENV['USERPROFILE'] : getenv('USERPROFILE')) {
+ return $var;
+ }
+ if ($var = isset($_ENV['windir']) ? $_ENV['windir'] : getenv('windir')) {
+ return $var;
+ }
+ return getenv('SystemRoot') . '\temp';
+ }
+ if ($var = isset($_ENV['TMPDIR']) ? $_ENV['TMPDIR'] : getenv('TMPDIR')) {
+ return $var;
+ }
+ return realpath('/tmp');
+ }
+
+ /**
+ * The "which" command (show the full path of a command)
+ *
+ * @param string $program The command to search for
+ * @param mixed $fallback Value to return if $program is not found
+ *
+ * @return mixed A string with the full path or false if not found
+ * @author Stig Bakken <ssb@php.net>
+ */
+ public static function which($program, $fallback = false)
+ {
+ // enforce API
+ if (!is_string($program) || '' == $program) {
+ return $fallback;
+ }
+
+ // full path given
+ if (basename($program) != $program) {
+ $path_elements[] = dirname($program);
+ $program = basename($program);
+ } else {
+ // Honor safe mode
+ if (!ini_get('safe_mode') || !$path = ini_get('safe_mode_exec_dir')) {
+ $path = getenv('PATH');
+ if (!$path) {
+ $path = getenv('Path'); // some OSes are just stupid enough to do this
+ }
+ }
+ $path_elements = explode(PATH_SEPARATOR, $path);
+ }
+
+ if (OS_WINDOWS) {
+ $exe_suffixes = getenv('PATHEXT')
+ ? explode(PATH_SEPARATOR, getenv('PATHEXT'))
+ : array('.exe','.bat','.cmd','.com');
+ // allow passing a command.exe param
+ if (strpos($program, '.') !== false) {
+ array_unshift($exe_suffixes, '');
+ }
+ // is_executable() is not available on windows for PHP4
+ $pear_is_executable = (function_exists('is_executable')) ? 'is_executable' : 'is_file';
+ } else {
+ $exe_suffixes = array('');
+ $pear_is_executable = 'is_executable';
+ }
+
+ foreach ($exe_suffixes as $suff) {
+ foreach ($path_elements as $dir) {
+ $file = $dir . DIRECTORY_SEPARATOR . $program . $suff;
+ if (@$pear_is_executable($file)) {
+ return $file;
+ }
+ }
+ }
+ return $fallback;
+ }
+
+ /**
+ * The "find" command
+ *
+ * Usage:
+ *
+ * System::find($dir);
+ * System::find("$dir -type d");
+ * System::find("$dir -type f");
+ * System::find("$dir -name *.php");
+ * System::find("$dir -name *.php -name *.htm*");
+ * System::find("$dir -maxdepth 1");
+ *
+ * Params implmented:
+ * $dir -> Start the search at this directory
+ * -type d -> return only directories
+ * -type f -> return only files
+ * -maxdepth <n> -> max depth of recursion
+ * -name <pattern> -> search pattern (bash style). Multiple -name param allowed
+ *
+ * @param mixed Either array or string with the command line
+ * @return array Array of found files
+ */
+ public static function find($args)
+ {
+ if (!is_array($args)) {
+ $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
+ }
+ $dir = realpath(array_shift($args));
+ if (!$dir) {
+ return array();
+ }
+ $patterns = array();
+ $depth = 0;
+ $do_files = $do_dirs = true;
+ $args_count = count($args);
+ for ($i = 0; $i < $args_count; $i++) {
+ switch ($args[$i]) {
+ case '-type':
+ if (in_array($args[$i+1], array('d', 'f'))) {
+ if ($args[$i+1] == 'd') {
+ $do_files = false;
+ } else {
+ $do_dirs = false;
+ }
+ }
+ $i++;
+ break;
+ case '-name':
+ $name = preg_quote($args[$i+1], '#');
+ // our magic characters ? and * have just been escaped,
+ // so now we change the escaped versions to PCRE operators
+ $name = strtr($name, array('\?' => '.', '\*' => '.*'));
+ $patterns[] = '('.$name.')';
+ $i++;
+ break;
+ case '-maxdepth':
+ $depth = $args[$i+1];
+ break;
+ }
+ }
+ $path = System::_dirToStruct($dir, $depth, 0, true);
+ if ($do_files && $do_dirs) {
+ $files = array_merge($path['files'], $path['dirs']);
+ } elseif ($do_dirs) {
+ $files = $path['dirs'];
+ } else {
+ $files = $path['files'];
+ }
+ if (count($patterns)) {
+ $dsq = preg_quote(DIRECTORY_SEPARATOR, '#');
+ $pattern = '#(^|'.$dsq.')'.implode('|', $patterns).'($|'.$dsq.')#';
+ $ret = array();
+ $files_count = count($files);
+ for ($i = 0; $i < $files_count; $i++) {
+ // only search in the part of the file below the current directory
+ $filepart = basename($files[$i]);
+ if (preg_match($pattern, $filepart)) {
+ $ret[] = $files[$i];
+ }
+ }
+ return $ret;
+ }
+ return $files;
+ }
+}
diff --git a/includes/pear/System/Command.php b/includes/pear/System/Command.php
new file mode 100644
index 0000000..f32e8a1
--- /dev/null
+++ b/includes/pear/System/Command.php
@@ -0,0 +1,598 @@
+<?php
+// {{{ license
+
+// +----------------------------------------------------------------------+
+// | PHP Version 4.0 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Author: Anders Johannsen <anders@johannsen.com> |
+// | Author: Dan Allen <dan@mojavelinux.com>
+// +----------------------------------------------------------------------+
+
+// $Id$
+
+// }}}
+// {{{ includes
+
+require_once 'PEAR.php';
+require_once 'System.php';
+
+// }}}
+// {{{ constants
+
+define('SYSTEM_COMMAND_OK', 1);
+define('SYSTEM_COMMAND_ERROR', -1);
+define('SYSTEM_COMMAND_NO_SHELL', -2);
+define('SYSTEM_COMMAND_INVALID_SHELL', -3);
+define('SYSTEM_COMMAND_TMPDIR_ERROR', -4);
+define('SYSTEM_COMMAND_INVALID_OPERATOR', -5);
+define('SYSTEM_COMMAND_INVALID_COMMAND', -6);
+define('SYSTEM_COMMAND_OPERATOR_PLACEMENT',-7);
+define('SYSTEM_COMMAND_COMMAND_PLACEMENT', -8);
+define('SYSTEM_COMMAND_NOHUP_MISSING', -9);
+define('SYSTEM_COMMAND_NO_OUTPUT', -10);
+define('SYSTEM_COMMAND_STDERR', -11);
+define('SYSTEM_COMMAND_NONZERO_EXIT', -12);
+
+// }}}
+
+// {{{ class System_Command
+
+/**
+ * The System_Command:: class implements an abstraction for various ways
+ * of executing commands (directly using the backtick operator,
+ * as a background task after the script has terminated using
+ * register_shutdown_function() or as a detached process using nohup).
+ *
+ * @author Anders Johannsen <anders@johannsen.com>
+ * @author Dan Allen <dan@mojavelinux.com>
+ * @version $Revision$
+ */
+
+// }}}
+class System_Command {
+ // {{{ properties
+
+ /**
+ * Array of settings used when creating the shell command
+ *
+ * @var array
+ * @access private
+ */
+ var $options = array();
+
+ /**
+ * Array of available shells to use to execute the command
+ *
+ * @var array
+ * @access private
+ */
+ var $shells = array();
+
+ /**
+ * Array of available control operators used between commands
+ *
+ * @var array
+ * @access private
+ */
+ var $controlOperators = array();
+
+ /**
+ * The system command to be executed
+ *
+ * @var string
+ * @access private
+ */
+ var $systemCommand = null;
+
+ /**
+ * Previously added part to the command string
+ *
+ * @var string
+ * @access private
+ */
+ var $previousElement = null;
+
+ /**
+ * Directory for writing stderr output
+ *
+ * @var string
+ * @access private
+ */
+ var $tmpDir = null;
+
+ /**
+ * To allow the pear error object to accumulate when building
+ * the command, we use the command status to keep track when
+ * a pear error is raised
+ *
+ * @var int
+ * @access private
+ */
+ var $commandStatus = 0;
+
+ /**
+ * Hold initialization PEAR_Error
+ *
+ * @var object
+ * @access private
+ **/
+ var $_initError = null;
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * Class constructor
+ *
+ * Defines all necessary constants and sets defaults
+ *
+ * @access public
+ */
+ function System_Command($in_shell = null)
+ {
+ // Defining constants
+ $this->options = array(
+ 'SEQUENCE' => true,
+ 'SHUTDOWN' => false,
+ 'SHELL' => $this->which($in_shell),
+ 'OUTPUT' => true,
+ 'NOHUP' => false,
+ 'BACKGROUND' => false,
+ 'STDERR' => false,
+ 'AUTORESET' => false
+ );
+
+ // prepare the available control operators
+ $this->controlOperators = array(
+ 'PIPE' => '|',
+ 'AND' => '&&',
+ 'OR' => '||',
+ 'GROUP' => ';',
+ 'LFIFO' => '<',
+ 'RFIFO' => '>',
+ );
+
+ // List of allowed/available shells
+ $this->shells = array(
+ 'sh',
+ 'bash',
+ 'zsh',
+ 'tcsh',
+ 'csh',
+ 'ash',
+ 'sash',
+ 'esh',
+ 'ksh'
+ );
+
+ // Find the first available shell
+ if (empty($this->options['SHELL'])) {
+ foreach ($this->shells as $shell) {
+ if ($this->options['SHELL'] = $this->which($shell)) {
+ break;
+ }
+ }
+
+ // see if we still have no shell
+ if (empty($this->options['SHELL'])) {
+ $this->_initError =& PEAR::raiseError(null, SYSTEM_COMMAND_NO_SHELL, null, E_USER_WARNING, null, 'System_Command_Error', true);
+ return;
+ }
+ }
+
+ // Caputre a temporary directory for capturing stderr from commands
+ $this->tmpDir = System::tmpdir();
+ if (!System::mkDir("-p {$this->tmpDir}")) {
+ $this->_initError =& PEAR::raiseError(null, SYSTEM_COMMAND_TMPDIR_ERROR, null, E_USER_WARNING, null, 'System_Command_Error', true);
+ return;
+ }
+ }
+
+ // }}}
+ // {{{ setOption()
+
+ /**
+ * Sets the value for an option. Each option should be set to true
+ * or false; except the 'SHELL' option which should be a string
+ * naming a shell. The options are:
+ *
+ * 'SEQUENCE' Allow a sequence command or not (right now this is always on);
+ *
+ * 'SHUTDOWN' Execute commands via a shutdown function;
+ *
+ * 'SHELL' Path to shell;
+ *
+ * 'OUTPUT' Output stdout from process;
+ *
+ * 'NOHUP' Use nohup to detach process;
+ *
+ * 'BACKGROUND' Run as a background process with &;
+ *
+ * 'STDERR' Output on stderr will raise an error, even if
+ * the command's exit value is zero. The output from
+ * stderr can be retrieved using the getDebugInfo()
+ * method of the Pear_ERROR object returned by
+ * execute().;
+ *
+ * 'AUTORESET' Automatically call reset() after a successful
+ * call of execute();
+ *
+ * @param string $in_option is a case-sensitive string,
+ * corresponding to the option
+ * that should be changed
+ * @param mixed $in_setting is the new value for the option
+ * @access public
+ * @return bool true if succes, else false
+ */
+ function setOption($in_option, $in_setting)
+ {
+ if ($this->_initError) {
+ return $this->_initError;
+ }
+
+ $option = strtoupper($in_option);
+
+ if (!isset($this->options[$option])) {
+ PEAR::raiseError(null, SYSTEM_COMMAND_ERROR, null, E_USER_NOTICE, null, 'System_Command_Error', true);
+ return false;
+ }
+
+ switch ($option) {
+ case 'OUTPUT':
+ case 'SHUTDOWN':
+ case 'SEQUENCE':
+ case 'BACKGROUND':
+ case 'STDERR':
+ $this->options[$option] = !empty($in_setting);
+ return true;
+ break;
+
+ case 'SHELL':
+ if (($shell = $this->which($in_setting)) !== false) {
+ $this->options[$option] = $shell;
+ return true;
+ }
+ else {
+ PEAR::raiseError(null, SYSTEM_COMMAND_NO_SHELL, null, E_USER_NOTICE, $in_setting, 'System_Command_Error', true);
+ return false;
+ }
+ break;
+
+ case 'NOHUP':
+ if (empty($in_setting)) {
+ $this->options[$option] = false;
+ }
+ else if ($location = $this->which('nohup')) {
+ $this->options[$option] = $location;
+ }
+ else {
+ PEAR::raiseError(null, SYSTEM_COMMAND_NOHUP_MISSING, null, E_USER_NOTICE, null, 'System_Command_Error', true);
+ return false;
+ }
+ break;
+ }
+ }
+
+ // }}}
+ // {{{ pushCommand()
+
+ /**
+ * Used to push a command onto the running command to be executed
+ *
+ * @param string $in_command binary to be run
+ * @param string $in_argument either an option or argument value, to be handled appropriately
+ * @param string $in_argument
+ * @param ...
+ *
+ * @access public
+ * @return boolean true on success {or System_Command_Error Exception}
+ */
+ function pushCommand($in_command)
+ {
+ if ($this->_initError) {
+ return $this->_initError;
+ }
+
+ if (!is_null($this->previousElement) && !in_array($this->previousElement, $this->controlOperators)) {
+ $this->commandStatus = -1;
+ $error = PEAR::raiseError(null, SYSTEM_COMMAND_COMMAND_PLACEMENT, null, E_USER_WARNING, null, 'System_Command_Error', true);
+ }
+
+ // check for error here
+ $command = escapeshellcmd($this->which($in_command));
+ if ($command === false) {
+ $error = PEAR::raiseError(null, SYSTEM_COMMAND_INVALID_COMMAND, null, E_USER_WARNING, null, 'System_Command_Error', true);
+ }
+
+ $argv = func_get_args();
+ array_shift($argv);
+ foreach($argv as $arg) {
+ if (strpos($arg, '-') === 0) {
+ $command .= ' ' . $arg;
+ }
+ elseif ($arg != '') {
+ $command .= ' ' . escapeshellarg($arg);
+ }
+ }
+
+ $this->previousElement = $command;
+ $this->systemCommand .= $command;
+
+ return isset($error) ? $error : true;
+ }
+
+ // }}}
+ // {{{ pushOperator()
+
+ /**
+ * Used to push an operator onto the running command to be executed
+ *
+ * @param string $in_operator Either string reprentation of operator or system character
+ *
+ * @access public
+ * @return boolean true on success {or System_Command_Error Exception}
+ */
+ function pushOperator($in_operator)
+ {
+ if ($this->_initError) {
+ return $this->_initError;
+ }
+
+ $operator = isset($this->controlOperators[$in_operator]) ? $this->controlOperators[$in_operator] : $in_operator;
+
+ if (is_null($this->previousElement) || in_array($this->previousElement, $this->controlOperators)) {
+ $this->commandStatus = -1;
+ $error = PEAR::raiseError(null, SYSTEM_COMMAND_OPERATOR_PLACEMENT, null, E_USER_WARNING, null, 'System_Command_Error', true);
+ }
+ elseif (!in_array($operator, $this->controlOperators)) {
+ $this->commandStatus = -1;
+ $error = PEAR::raiseError(null, SYSTEM_COMMAND_INVALID_OPERATOR, null, E_USER_WARNING, $operator, 'System_Command_Error', true);
+ }
+
+ $this->previousElement = $operator;
+ $this->systemCommand .= ' ' . $operator . ' ';
+ return isset($error) ? $error : true;
+ }
+
+ // }}}
+ // {{{ execute()
+
+ /**
+ * Executes the code according to given options
+ *
+ * @return bool true if success {or System_Command_Exception}
+ *
+ * @access public
+ */
+ function execute()
+ {
+ if ($this->_initError) {
+ return $this->_initError;
+ }
+
+ // if the command is empty or if the last element was a control operator, we can't continue
+ if (is_null($this->previousElement) || $this->commandStatus == -1 || in_array($this->previousElement, $this->controlOperators)) {
+ return PEAR::raiseError(null, SYSTEM_COMMAND_INVALID_COMMAND, null, E_USER_WARNING, $this->systemCommand, 'System_Command_Error', true);
+ }
+
+ // Warning about impossible mix of options
+ if (!empty($this->options['OUTPUT'])) {
+ if (!empty($this->options['SHUTDOWN']) || !empty($this->options['NOHUP'])) {
+ return PEAR::raiseError(null, SYSTEM_COMMAND_NO_OUTPUT, null, E_USER_WARNING, null, 'System_Command_Error', true);
+ }
+ }
+
+ // if this is not going to stdout, then redirect to /dev/null
+ if (empty($this->options['OUTPUT'])) {
+ $this->systemCommand .= ' >/dev/null';
+ }
+
+ $suffix = '';
+ // run a command immune to hangups, with output to a non-tty
+ if (!empty($this->options['NOHUP'])) {
+ $this->systemCommand = $this->options['NOHUP'] . $this->systemCommand;
+ }
+ // run a background process (only if not nohup)
+ elseif (!empty($this->options['BACKGROUND'])) {
+ $suffix = ' &';
+ }
+
+ // Register to be run on shutdown
+ if (!empty($this->options['SHUTDOWN'])) {
+ $line = "system(\"{$this->systemCommand}$suffix\");";
+ $function = create_function('', $line);
+ register_shutdown_function($function);
+ if ($this->options['AUTORESET']) {
+ $this->reset();
+ }
+ return true;
+ }
+ else {
+ // send stderr to a file so that we can reap the error message
+ $tmpFile = tempnam($this->tmpDir, 'System_Command-');
+ $this->systemCommand .= ' 2>' . $tmpFile . $suffix;
+ $shellPipe = $this->which('echo') . ' ' . escapeshellarg($this->systemCommand) . ' | ' . $this->options['SHELL'];
+ exec($shellPipe, $result, $returnVal);
+
+ if ($returnVal !== 0) {
+ // command returned nonzero; that's always an error
+ $return = PEAR::raiseError(null, SYSTEM_COMMAND_NONZERO_EXIT, null, E_USER_WARNING, null, 'System_Command_Error', true);
+ }
+ else if (!$this->options['STDERR']) {
+ // caller does not care about stderr; return success
+ $return = implode("\n", $result);
+ }
+ else {
+ // our caller cares about stderr; check stderr output
+ clearstatcache();
+ if (filesize($tmpFile) > 0) {
+ // the command actually wrote to stderr
+ $stderr_output = file_get_contents($tmpFile);
+ $return = PEAR::raiseError(null, SYSTEM_COMMAND_STDERR, null, E_USER_WARNING, $stderr_output, 'System_Command_Error', true);
+ } else {
+ // total success; return stdout gathered by exec()
+ $return = implode("\n", $result);
+ }
+ }
+
+ if ((!PEAR::isError($return)) && ($this->options['AUTORESET'])) {
+ $this->reset();
+ }
+
+ unlink($tmpFile);
+ return $return;
+ }
+ }
+
+ // }}}
+ // {{{ which()
+
+ /**
+ * Functionality similiar to unix 'which'. Searches the path
+ * for the specified program.
+ *
+ * @param $cmd name of the executable to search for
+ *
+ * @access private
+ * @return string returns the full path if found, false if not
+ */
+ function which($in_cmd)
+ {
+ // only pass non-empty strings to System::which()
+ if (!is_string($in_cmd) || '' === $in_cmd) {
+ return(false);
+ }
+
+ // explicitly pass false as fallback value
+ return System::which($in_cmd, false);
+ }
+
+ // }}}
+ // {{{ reset()
+
+ /**
+ * Prepare for a new command to be built
+ *
+ * @access public
+ * @return void
+ */
+ function reset()
+ {
+ $this->previousElement = null;
+ $this->systemCommand = null;
+ $this->commandStatus = 0;
+ }
+
+ // }}}
+ // {{{ errorMessage()
+
+ /**
+ * Return a textual error message for a System_Command error code
+ *
+ * @param integer error code
+ *
+ * @return string error message, or false if the error code was
+ * not recognized
+ */
+ function errorMessage($in_value)
+ {
+ static $errorMessages;
+ if (!isset($errorMessages)) {
+ $errorMessages = array(
+ SYSTEM_COMMAND_OK => 'no error',
+ SYSTEM_COMMAND_ERROR => 'unknown error',
+ SYSTEM_COMMAND_NO_SHELL => 'no shell found',
+ SYSTEM_COMMAND_INVALID_SHELL => 'invalid shell',
+ SYSTEM_COMMAND_TMPDIR_ERROR => 'could not create temporary directory',
+ SYSTEM_COMMAND_INVALID_OPERATOR => 'control operator invalid',
+ SYSTEM_COMMAND_INVALID_COMMAND => 'invalid system command',
+ SYSTEM_COMMAND_OPERATOR_PLACEMENT => 'invalid placement of control operator',
+ SYSTEM_COMMAND_COMMAND_PLACEMENT => 'invalid placement of command',
+ SYSTEM_COMMAND_NOHUP_MISSING => 'nohup not found on system',
+ SYSTEM_COMMAND_NO_OUTPUT => 'output not allowed',
+ SYSTEM_COMMAND_STDERR => 'command wrote to stderr',
+ SYSTEM_COMMAND_NONZERO_EXIT => 'non-zero exit value from command',
+ );
+ }
+
+ if (System_Command::isError($in_value)) {
+ $in_value = $in_value->getCode();
+ }
+
+ return isset($errorMessages[$in_value]) ? $errorMessages[$in_value] : $errorMessages[SYSTEM_COMMAND_ERROR];
+ }
+
+ // }}}
+ // {{{ isError()
+
+ /**
+ * Tell whether a result code from a System_Command method is an error
+ *
+ * @param int result code
+ *
+ * @return bool whether $in_value is an error
+ *
+ * @access public
+ */
+ function isError($in_value)
+ {
+ return (is_object($in_value) &&
+ (strtolower(get_class($in_value)) == 'system_command_error' ||
+ is_subclass_of($in_value, 'system_command_error')));
+ }
+
+ // }}}
+}
+
+// {{{ class System_Command_Error
+
+/**
+ * System_Command_Error constructor.
+ *
+ * @param mixed System_Command error code, or string with error message.
+ * @param integer what "error mode" to operate in
+ * @param integer what error level to use for $mode & PEAR_ERROR_TRIGGER
+ * @param mixed additional debug info, such as the last query
+ *
+ * @access public
+ *
+ * @see PEAR_Error
+ */
+
+// }}}
+class System_Command_Error extends PEAR_Error
+{
+ // {{{ properties
+
+ /**
+ * Message in front of the error message
+ * @var string $error_message_prefix
+ */
+ var $error_message_prefix = 'System_Command Error: ';
+
+ // }}}
+ // {{{ constructor
+
+ function System_Command_Error($code = SYSTEM_COMMAND_ERROR, $mode = PEAR_ERROR_RETURN,
+ $level = E_USER_NOTICE, $debuginfo = null)
+ {
+ if (is_int($code)) {
+ $this->PEAR_Error(System_Command::errorMessage($code), $code, $mode, $level, $debuginfo);
+ } else {
+ $this->PEAR_Error("Invalid error code: $code", SYSTEM_COMMAND_ERROR, $mode, $level, $debuginfo);
+ }
+ }
+
+ // }}}
+}
+?>
diff --git a/includes/pear/Text/Diff.php b/includes/pear/Text/Diff.php
new file mode 100644
index 0000000..4b3de1b
--- /dev/null
+++ b/includes/pear/Text/Diff.php
@@ -0,0 +1,453 @@
+<?php
+/**
+ * General API for generating and formatting diffs - the differences between
+ * two sequences of strings.
+ *
+ * The original PHP version of this code was written by Geoffrey T. Dairiki
+ * <dairiki@dairiki.org>, and is used/adapted with his permission.
+ *
+ * $Horde: framework/Text_Diff/Diff.php,v 1.11.2.12 2009/01/06 15:23:41 jan Exp $
+ *
+ * Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org>
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
+ *
+ * @package Text_Diff
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ */
+class Text_Diff {
+
+ /**
+ * Array of changes.
+ *
+ * @var array
+ */
+ var $_edits;
+
+ /**
+ * Computes diffs between sequences of strings.
+ *
+ * @param string $engine Name of the diffing engine to use. 'auto'
+ * will automatically select the best.
+ * @param array $params Parameters to pass to the diffing engine.
+ * Normally an array of two arrays, each
+ * containing the lines from a file.
+ */
+ function Text_Diff($engine, $params)
+ {
+ // Backward compatibility workaround.
+ if (!is_string($engine)) {
+ $params = array($engine, $params);
+ $engine = 'auto';
+ }
+
+ if ($engine == 'auto') {
+ $engine = extension_loaded('xdiff') ? 'xdiff' : 'native';
+ } else {
+ $engine = basename($engine);
+ }
+
+ require_once 'Text/Diff/Engine/' . $engine . '.php';
+ $class = 'Text_Diff_Engine_' . $engine;
+ $diff_engine = new $class();
+
+ $this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params);
+ }
+
+ /**
+ * Returns the array of differences.
+ */
+ function getDiff()
+ {
+ return $this->_edits;
+ }
+
+ /**
+ * returns the number of new (added) lines in a given diff.
+ *
+ * @since Text_Diff 1.1.0
+ * @since Horde 3.2
+ *
+ * @return integer The number of new lines
+ */
+ function countAddedLines()
+ {
+ $count = 0;
+ foreach ($this->_edits as $edit) {
+ if (is_a($edit, 'Text_Diff_Op_add') ||
+ is_a($edit, 'Text_Diff_Op_change')) {
+ $count += $edit->nfinal();
+ }
+ }
+ return $count;
+ }
+
+ /**
+ * Returns the number of deleted (removed) lines in a given diff.
+ *
+ * @since Text_Diff 1.1.0
+ * @since Horde 3.2
+ *
+ * @return integer The number of deleted lines
+ */
+ function countDeletedLines()
+ {
+ $count = 0;
+ foreach ($this->_edits as $edit) {
+ if (is_a($edit, 'Text_Diff_Op_delete') ||
+ is_a($edit, 'Text_Diff_Op_change')) {
+ $count += $edit->norig();
+ }
+ }
+ return $count;
+ }
+
+ /**
+ * Computes a reversed diff.
+ *
+ * Example:
+ * <code>
+ * $diff = new Text_Diff($lines1, $lines2);
+ * $rev = $diff->reverse();
+ * </code>
+ *
+ * @return Text_Diff A Diff object representing the inverse of the
+ * original diff. Note that we purposely don't return a
+ * reference here, since this essentially is a clone()
+ * method.
+ */
+ function reverse()
+ {
+ if (version_compare(zend_version(), '2', '>')) {
+ $rev = clone($this);
+ } else {
+ $rev = $this;
+ }
+ $rev->_edits = array();
+ foreach ($this->_edits as $edit) {
+ $rev->_edits[] = $edit->reverse();
+ }
+ return $rev;
+ }
+
+ /**
+ * Checks for an empty diff.
+ *
+ * @return boolean True if two sequences were identical.
+ */
+ function isEmpty()
+ {
+ foreach ($this->_edits as $edit) {
+ if (!is_a($edit, 'Text_Diff_Op_copy')) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Computes the length of the Longest Common Subsequence (LCS).
+ *
+ * This is mostly for diagnostic purposes.
+ *
+ * @return integer The length of the LCS.
+ */
+ function lcs()
+ {
+ $lcs = 0;
+ foreach ($this->_edits as $edit) {
+ if (is_a($edit, 'Text_Diff_Op_copy')) {
+ $lcs += count($edit->orig);
+ }
+ }
+ return $lcs;
+ }
+
+ /**
+ * Gets the original set of lines.
+ *
+ * This reconstructs the $from_lines parameter passed to the constructor.
+ *
+ * @return array The original sequence of strings.
+ */
+ function getOriginal()
+ {
+ $lines = array();
+ foreach ($this->_edits as $edit) {
+ if ($edit->orig) {
+ array_splice($lines, count($lines), 0, $edit->orig);
+ }
+ }
+ return $lines;
+ }
+
+ /**
+ * Gets the final set of lines.
+ *
+ * This reconstructs the $to_lines parameter passed to the constructor.
+ *
+ * @return array The sequence of strings.
+ */
+ function getFinal()
+ {
+ $lines = array();
+ foreach ($this->_edits as $edit) {
+ if ($edit->final) {
+ array_splice($lines, count($lines), 0, $edit->final);
+ }
+ }
+ return $lines;
+ }
+
+ /**
+ * Removes trailing newlines from a line of text. This is meant to be used
+ * with array_walk().
+ *
+ * @param string $line The line to trim.
+ * @param integer $key The index of the line in the array. Not used.
+ */
+ function trimNewlines(&$line, $key)
+ {
+ $line = str_replace(array("\n", "\r"), '', $line);
+ }
+
+ /**
+ * Determines the location of the system temporary directory.
+ *
+ * @static
+ *
+ * @access protected
+ *
+ * @return string A directory name which can be used for temp files.
+ * Returns false if one could not be found.
+ */
+ function _getTempDir()
+ {
+ $tmp_locations = array('/tmp', '/var/tmp', 'c:\WUTemp', 'c:\temp',
+ 'c:\windows\temp', 'c:\winnt\temp');
+
+ /* Try PHP's upload_tmp_dir directive. */
+ $tmp = ini_get('upload_tmp_dir');
+
+ /* Otherwise, try to determine the TMPDIR environment variable. */
+ if (!strlen($tmp)) {
+ $tmp = getenv('TMPDIR');
+ }
+
+ /* If we still cannot determine a value, then cycle through a list of
+ * preset possibilities. */
+ while (!strlen($tmp) && count($tmp_locations)) {
+ $tmp_check = array_shift($tmp_locations);
+ if (@is_dir($tmp_check)) {
+ $tmp = $tmp_check;
+ }
+ }
+
+ /* If it is still empty, we have failed, so return false; otherwise
+ * return the directory determined. */
+ return strlen($tmp) ? $tmp : false;
+ }
+
+ /**
+ * Checks a diff for validity.
+ *
+ * This is here only for debugging purposes.
+ */
+ function _check($from_lines, $to_lines)
+ {
+ if (serialize($from_lines) != serialize($this->getOriginal())) {
+ trigger_error("Reconstructed original doesn't match", E_USER_ERROR);
+ }
+ if (serialize($to_lines) != serialize($this->getFinal())) {
+ trigger_error("Reconstructed final doesn't match", E_USER_ERROR);
+ }
+
+ $rev = $this->reverse();
+ if (serialize($to_lines) != serialize($rev->getOriginal())) {
+ trigger_error("Reversed original doesn't match", E_USER_ERROR);
+ }
+ if (serialize($from_lines) != serialize($rev->getFinal())) {
+ trigger_error("Reversed final doesn't match", E_USER_ERROR);
+ }
+
+ $prevtype = null;
+ foreach ($this->_edits as $edit) {
+ if ($prevtype == get_class($edit)) {
+ trigger_error("Edit sequence is non-optimal", E_USER_ERROR);
+ }
+ $prevtype = get_class($edit);
+ }
+
+ return true;
+ }
+
+}
+
+/**
+ * @package Text_Diff
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ */
+class Text_MappedDiff extends Text_Diff {
+
+ /**
+ * Computes a diff between sequences of strings.
+ *
+ * This can be used to compute things like case-insensitve diffs, or diffs
+ * which ignore changes in white-space.
+ *
+ * @param array $from_lines An array of strings.
+ * @param array $to_lines An array of strings.
+ * @param array $mapped_from_lines This array should have the same size
+ * number of elements as $from_lines. The
+ * elements in $mapped_from_lines and
+ * $mapped_to_lines are what is actually
+ * compared when computing the diff.
+ * @param array $mapped_to_lines This array should have the same number
+ * of elements as $to_lines.
+ */
+ function Text_MappedDiff($from_lines, $to_lines,
+ $mapped_from_lines, $mapped_to_lines)
+ {
+ assert(count($from_lines) == count($mapped_from_lines));
+ assert(count($to_lines) == count($mapped_to_lines));
+
+ parent::Text_Diff($mapped_from_lines, $mapped_to_lines);
+
+ $xi = $yi = 0;
+ for ($i = 0; $i < count($this->_edits); $i++) {
+ $orig = &$this->_edits[$i]->orig;
+ if (is_array($orig)) {
+ $orig = array_slice($from_lines, $xi, count($orig));
+ $xi += count($orig);
+ }
+
+ $final = &$this->_edits[$i]->final;
+ if (is_array($final)) {
+ $final = array_slice($to_lines, $yi, count($final));
+ $yi += count($final);
+ }
+ }
+ }
+
+}
+
+/**
+ * @package Text_Diff
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ *
+ * @access private
+ */
+class Text_Diff_Op {
+
+ var $orig;
+ var $final;
+
+ function &reverse()
+ {
+ trigger_error('Abstract method', E_USER_ERROR);
+ }
+
+ function norig()
+ {
+ return $this->orig ? count($this->orig) : 0;
+ }
+
+ function nfinal()
+ {
+ return $this->final ? count($this->final) : 0;
+ }
+
+}
+
+/**
+ * @package Text_Diff
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ *
+ * @access private
+ */
+class Text_Diff_Op_copy extends Text_Diff_Op {
+
+ function Text_Diff_Op_copy($orig, $final = false)
+ {
+ if (!is_array($final)) {
+ $final = $orig;
+ }
+ $this->orig = $orig;
+ $this->final = $final;
+ }
+
+ function &reverse()
+ {
+ $reverse = &new Text_Diff_Op_copy($this->final, $this->orig);
+ return $reverse;
+ }
+
+}
+
+/**
+ * @package Text_Diff
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ *
+ * @access private
+ */
+class Text_Diff_Op_delete extends Text_Diff_Op {
+
+ function Text_Diff_Op_delete($lines)
+ {
+ $this->orig = $lines;
+ $this->final = false;
+ }
+
+ function &reverse()
+ {
+ $reverse = &new Text_Diff_Op_add($this->orig);
+ return $reverse;
+ }
+
+}
+
+/**
+ * @package Text_Diff
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ *
+ * @access private
+ */
+class Text_Diff_Op_add extends Text_Diff_Op {
+
+ function Text_Diff_Op_add($lines)
+ {
+ $this->final = $lines;
+ $this->orig = false;
+ }
+
+ function &reverse()
+ {
+ $reverse = &new Text_Diff_Op_delete($this->final);
+ return $reverse;
+ }
+
+}
+
+/**
+ * @package Text_Diff
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ *
+ * @access private
+ */
+class Text_Diff_Op_change extends Text_Diff_Op {
+
+ function Text_Diff_Op_change($orig, $final)
+ {
+ $this->orig = $orig;
+ $this->final = $final;
+ }
+
+ function &reverse()
+ {
+ $reverse = &new Text_Diff_Op_change($this->final, $this->orig);
+ return $reverse;
+ }
+
+}
diff --git a/includes/pear/Text/Diff/Engine/native.php b/includes/pear/Text/Diff/Engine/native.php
new file mode 100644
index 0000000..84168c0
--- /dev/null
+++ b/includes/pear/Text/Diff/Engine/native.php
@@ -0,0 +1,438 @@
+<?php
+/**
+ * Class used internally by Text_Diff to actually compute the diffs.
+ *
+ * This class is implemented using native PHP code.
+ *
+ * The algorithm used here is mostly lifted from the perl module
+ * Algorithm::Diff (version 1.06) by Ned Konz, which is available at:
+ * http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip
+ *
+ * More ideas are taken from: http://www.ics.uci.edu/~eppstein/161/960229.html
+ *
+ * Some ideas (and a bit of code) are taken from analyze.c, of GNU
+ * diffutils-2.7, which can be found at:
+ * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz
+ *
+ * Some ideas (subdivision by NCHUNKS > 2, and some optimizations) are from
+ * Geoffrey T. Dairiki <dairiki@dairiki.org>. The original PHP version of this
+ * code was written by him, and is used/adapted with his permission.
+ *
+ * $Horde: framework/Text_Diff/Diff/Engine/native.php,v 1.7.2.5 2009/01/06 15:23:41 jan Exp $
+ *
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
+ *
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ * @package Text_Diff
+ */
+class Text_Diff_Engine_native {
+
+ function diff($from_lines, $to_lines)
+ {
+ array_walk($from_lines, array('Text_Diff', 'trimNewlines'));
+ array_walk($to_lines, array('Text_Diff', 'trimNewlines'));
+
+ $n_from = count($from_lines);
+ $n_to = count($to_lines);
+
+ $this->xchanged = $this->ychanged = array();
+ $this->xv = $this->yv = array();
+ $this->xind = $this->yind = array();
+ unset($this->seq);
+ unset($this->in_seq);
+ unset($this->lcs);
+
+ // Skip leading common lines.
+ for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
+ if ($from_lines[$skip] !== $to_lines[$skip]) {
+ break;
+ }
+ $this->xchanged[$skip] = $this->ychanged[$skip] = false;
+ }
+
+ // Skip trailing common lines.
+ $xi = $n_from; $yi = $n_to;
+ for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
+ if ($from_lines[$xi] !== $to_lines[$yi]) {
+ break;
+ }
+ $this->xchanged[$xi] = $this->ychanged[$yi] = false;
+ }
+
+ // Ignore lines which do not exist in both files.
+ for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
+ $xhash[$from_lines[$xi]] = 1;
+ }
+ for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
+ $line = $to_lines[$yi];
+ if (($this->ychanged[$yi] = empty($xhash[$line]))) {
+ continue;
+ }
+ $yhash[$line] = 1;
+ $this->yv[] = $line;
+ $this->yind[] = $yi;
+ }
+ for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
+ $line = $from_lines[$xi];
+ if (($this->xchanged[$xi] = empty($yhash[$line]))) {
+ continue;
+ }
+ $this->xv[] = $line;
+ $this->xind[] = $xi;
+ }
+
+ // Find the LCS.
+ $this->_compareseq(0, count($this->xv), 0, count($this->yv));
+
+ // Merge edits when possible.
+ $this->_shiftBoundaries($from_lines, $this->xchanged, $this->ychanged);
+ $this->_shiftBoundaries($to_lines, $this->ychanged, $this->xchanged);
+
+ // Compute the edit operations.
+ $edits = array();
+ $xi = $yi = 0;
+ while ($xi < $n_from || $yi < $n_to) {
+ assert($yi < $n_to || $this->xchanged[$xi]);
+ assert($xi < $n_from || $this->ychanged[$yi]);
+
+ // Skip matching "snake".
+ $copy = array();
+ while ($xi < $n_from && $yi < $n_to
+ && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
+ $copy[] = $from_lines[$xi++];
+ ++$yi;
+ }
+ if ($copy) {
+ $edits[] = &new Text_Diff_Op_copy($copy);
+ }
+
+ // Find deletes & adds.
+ $delete = array();
+ while ($xi < $n_from && $this->xchanged[$xi]) {
+ $delete[] = $from_lines[$xi++];
+ }
+
+ $add = array();
+ while ($yi < $n_to && $this->ychanged[$yi]) {
+ $add[] = $to_lines[$yi++];
+ }
+
+ if ($delete && $add) {
+ $edits[] = &new Text_Diff_Op_change($delete, $add);
+ } elseif ($delete) {
+ $edits[] = &new Text_Diff_Op_delete($delete);
+ } elseif ($add) {
+ $edits[] = &new Text_Diff_Op_add($add);
+ }
+ }
+
+ return $edits;
+ }
+
+ /**
+ * Divides the Largest Common Subsequence (LCS) of the sequences (XOFF,
+ * XLIM) and (YOFF, YLIM) into NCHUNKS approximately equally sized
+ * segments.
+ *
+ * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an array of
+ * NCHUNKS+1 (X, Y) indexes giving the diving points between sub
+ * sequences. The first sub-sequence is contained in (X0, X1), (Y0, Y1),
+ * the second in (X1, X2), (Y1, Y2) and so on. Note that (X0, Y0) ==
+ * (XOFF, YOFF) and (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM).
+ *
+ * This function assumes that the first lines of the specified portions of
+ * the two files do not match, and likewise that the last lines do not
+ * match. The caller must trim matching lines from the beginning and end
+ * of the portions it is going to specify.
+ */
+ function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks)
+ {
+ $flip = false;
+
+ if ($xlim - $xoff > $ylim - $yoff) {
+ /* Things seems faster (I'm not sure I understand why) when the
+ * shortest sequence is in X. */
+ $flip = true;
+ list ($xoff, $xlim, $yoff, $ylim)
+ = array($yoff, $ylim, $xoff, $xlim);
+ }
+
+ if ($flip) {
+ for ($i = $ylim - 1; $i >= $yoff; $i--) {
+ $ymatches[$this->xv[$i]][] = $i;
+ }
+ } else {
+ for ($i = $ylim - 1; $i >= $yoff; $i--) {
+ $ymatches[$this->yv[$i]][] = $i;
+ }
+ }
+
+ $this->lcs = 0;
+ $this->seq[0]= $yoff - 1;
+ $this->in_seq = array();
+ $ymids[0] = array();
+
+ $numer = $xlim - $xoff + $nchunks - 1;
+ $x = $xoff;
+ for ($chunk = 0; $chunk < $nchunks; $chunk++) {
+ if ($chunk > 0) {
+ for ($i = 0; $i <= $this->lcs; $i++) {
+ $ymids[$i][$chunk - 1] = $this->seq[$i];
+ }
+ }
+
+ $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $chunk) / $nchunks);
+ for (; $x < $x1; $x++) {
+ $line = $flip ? $this->yv[$x] : $this->xv[$x];
+ if (empty($ymatches[$line])) {
+ continue;
+ }
+ $matches = $ymatches[$line];
+ reset($matches);
+ while (list(, $y) = each($matches)) {
+ if (empty($this->in_seq[$y])) {
+ $k = $this->_lcsPos($y);
+ assert($k > 0);
+ $ymids[$k] = $ymids[$k - 1];
+ break;
+ }
+ }
+ while (list(, $y) = each($matches)) {
+ if ($y > $this->seq[$k - 1]) {
+ assert($y <= $this->seq[$k]);
+ /* Optimization: this is a common case: next match is
+ * just replacing previous match. */
+ $this->in_seq[$this->seq[$k]] = false;
+ $this->seq[$k] = $y;
+ $this->in_seq[$y] = 1;
+ } elseif (empty($this->in_seq[$y])) {
+ $k = $this->_lcsPos($y);
+ assert($k > 0);
+ $ymids[$k] = $ymids[$k - 1];
+ }
+ }
+ }
+ }
+
+ $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
+ $ymid = $ymids[$this->lcs];
+ for ($n = 0; $n < $nchunks - 1; $n++) {
+ $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks);
+ $y1 = $ymid[$n] + 1;
+ $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
+ }
+ $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
+
+ return array($this->lcs, $seps);
+ }
+
+ function _lcsPos($ypos)
+ {
+ $end = $this->lcs;
+ if ($end == 0 || $ypos > $this->seq[$end]) {
+ $this->seq[++$this->lcs] = $ypos;
+ $this->in_seq[$ypos] = 1;
+ return $this->lcs;
+ }
+
+ $beg = 1;
+ while ($beg < $end) {
+ $mid = (int)(($beg + $end) / 2);
+ if ($ypos > $this->seq[$mid]) {
+ $beg = $mid + 1;
+ } else {
+ $end = $mid;
+ }
+ }
+
+ assert($ypos != $this->seq[$end]);
+
+ $this->in_seq[$this->seq[$end]] = false;
+ $this->seq[$end] = $ypos;
+ $this->in_seq[$ypos] = 1;
+ return $end;
+ }
+
+ /**
+ * Finds LCS of two sequences.
+ *
+ * The results are recorded in the vectors $this->{x,y}changed[], by
+ * storing a 1 in the element for each line that is an insertion or
+ * deletion (ie. is not in the LCS).
+ *
+ * The subsequence of file 0 is (XOFF, XLIM) and likewise for file 1.
+ *
+ * Note that XLIM, YLIM are exclusive bounds. All line numbers are
+ * origin-0 and discarded lines are not counted.
+ */
+ function _compareseq ($xoff, $xlim, $yoff, $ylim)
+ {
+ /* Slide down the bottom initial diagonal. */
+ while ($xoff < $xlim && $yoff < $ylim
+ && $this->xv[$xoff] == $this->yv[$yoff]) {
+ ++$xoff;
+ ++$yoff;
+ }
+
+ /* Slide up the top initial diagonal. */
+ while ($xlim > $xoff && $ylim > $yoff
+ && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
+ --$xlim;
+ --$ylim;
+ }
+
+ if ($xoff == $xlim || $yoff == $ylim) {
+ $lcs = 0;
+ } else {
+ /* This is ad hoc but seems to work well. $nchunks =
+ * sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); $nchunks =
+ * max(2,min(8,(int)$nchunks)); */
+ $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
+ list($lcs, $seps)
+ = $this->_diag($xoff, $xlim, $yoff, $ylim, $nchunks);
+ }
+
+ if ($lcs == 0) {
+ /* X and Y sequences have no common subsequence: mark all
+ * changed. */
+ while ($yoff < $ylim) {
+ $this->ychanged[$this->yind[$yoff++]] = 1;
+ }
+ while ($xoff < $xlim) {
+ $this->xchanged[$this->xind[$xoff++]] = 1;
+ }
+ } else {
+ /* Use the partitions to split this problem into subproblems. */
+ reset($seps);
+ $pt1 = $seps[0];
+ while ($pt2 = next($seps)) {
+ $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
+ $pt1 = $pt2;
+ }
+ }
+ }
+
+ /**
+ * Adjusts inserts/deletes of identical lines to join changes as much as
+ * possible.
+ *
+ * We do something when a run of changed lines include a line at one end
+ * and has an excluded, identical line at the other. We are free to
+ * choose which identical line is included. `compareseq' usually chooses
+ * the one at the beginning, but usually it is cleaner to consider the
+ * following identical line to be the "change".
+ *
+ * This is extracted verbatim from analyze.c (GNU diffutils-2.7).
+ */
+ function _shiftBoundaries($lines, &$changed, $other_changed)
+ {
+ $i = 0;
+ $j = 0;
+
+ assert('count($lines) == count($changed)');
+ $len = count($lines);
+ $other_len = count($other_changed);
+
+ while (1) {
+ /* Scan forward to find the beginning of another run of
+ * changes. Also keep track of the corresponding point in the
+ * other file.
+ *
+ * Throughout this code, $i and $j are adjusted together so that
+ * the first $i elements of $changed and the first $j elements of
+ * $other_changed both contain the same number of zeros (unchanged
+ * lines).
+ *
+ * Furthermore, $j is always kept so that $j == $other_len or
+ * $other_changed[$j] == false. */
+ while ($j < $other_len && $other_changed[$j]) {
+ $j++;
+ }
+
+ while ($i < $len && ! $changed[$i]) {
+ assert('$j < $other_len && ! $other_changed[$j]');
+ $i++; $j++;
+ while ($j < $other_len && $other_changed[$j]) {
+ $j++;
+ }
+ }
+
+ if ($i == $len) {
+ break;
+ }
+
+ $start = $i;
+
+ /* Find the end of this run of changes. */
+ while (++$i < $len && $changed[$i]) {
+ continue;
+ }
+
+ do {
+ /* Record the length of this run of changes, so that we can
+ * later determine whether the run has grown. */
+ $runlength = $i - $start;
+
+ /* Move the changed region back, so long as the previous
+ * unchanged line matches the last changed one. This merges
+ * with previous changed regions. */
+ while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) {
+ $changed[--$start] = 1;
+ $changed[--$i] = false;
+ while ($start > 0 && $changed[$start - 1]) {
+ $start--;
+ }
+ assert('$j > 0');
+ while ($other_changed[--$j]) {
+ continue;
+ }
+ assert('$j >= 0 && !$other_changed[$j]');
+ }
+
+ /* Set CORRESPONDING to the end of the changed run, at the
+ * last point where it corresponds to a changed run in the
+ * other file. CORRESPONDING == LEN means no such point has
+ * been found. */
+ $corresponding = $j < $other_len ? $i : $len;
+
+ /* Move the changed region forward, so long as the first
+ * changed line matches the following unchanged one. This
+ * merges with following changed regions. Do this second, so
+ * that if there are no merges, the changed region is moved
+ * forward as far as possible. */
+ while ($i < $len && $lines[$start] == $lines[$i]) {
+ $changed[$start++] = false;
+ $changed[$i++] = 1;
+ while ($i < $len && $changed[$i]) {
+ $i++;
+ }
+
+ assert('$j < $other_len && ! $other_changed[$j]');
+ $j++;
+ if ($j < $other_len && $other_changed[$j]) {
+ $corresponding = $i;
+ while ($j < $other_len && $other_changed[$j]) {
+ $j++;
+ }
+ }
+ }
+ } while ($runlength != $i - $start);
+
+ /* If possible, move the fully-merged run of changes back to a
+ * corresponding run in the other file. */
+ while ($corresponding < $i) {
+ $changed[--$start] = 1;
+ $changed[--$i] = 0;
+ assert('$j > 0');
+ while ($other_changed[--$j]) {
+ continue;
+ }
+ assert('$j >= 0 && !$other_changed[$j]');
+ }
+ }
+ }
+
+}
diff --git a/includes/pear/Text/Diff/Engine/shell.php b/includes/pear/Text/Diff/Engine/shell.php
new file mode 100644
index 0000000..7f858cb
--- /dev/null
+++ b/includes/pear/Text/Diff/Engine/shell.php
@@ -0,0 +1,164 @@
+<?php
+/**
+ * Class used internally by Diff to actually compute the diffs.
+ *
+ * This class uses the Unix `diff` program via shell_exec to compute the
+ * differences between the two input arrays.
+ *
+ * $Horde: framework/Text_Diff/Diff/Engine/shell.php,v 1.6.2.4 2009/01/06 15:23:41 jan Exp $
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
+ *
+ * @author Milian Wolff <mail@milianw.de>
+ * @package Text_Diff
+ * @since 0.3.0
+ */
+class Text_Diff_Engine_shell {
+
+ /**
+ * Path to the diff executable
+ *
+ * @var string
+ */
+ var $_diffCommand = 'diff';
+
+ /**
+ * Returns the array of differences.
+ *
+ * @param array $from_lines lines of text from old file
+ * @param array $to_lines lines of text from new file
+ *
+ * @return array all changes made (array with Text_Diff_Op_* objects)
+ */
+ function diff($from_lines, $to_lines)
+ {
+ array_walk($from_lines, array('Text_Diff', 'trimNewlines'));
+ array_walk($to_lines, array('Text_Diff', 'trimNewlines'));
+
+ $temp_dir = Text_Diff::_getTempDir();
+
+ // Execute gnu diff or similar to get a standard diff file.
+ $from_file = tempnam($temp_dir, 'Text_Diff');
+ $to_file = tempnam($temp_dir, 'Text_Diff');
+ $fp = fopen($from_file, 'w');
+ fwrite($fp, implode("\n", $from_lines));
+ fclose($fp);
+ $fp = fopen($to_file, 'w');
+ fwrite($fp, implode("\n", $to_lines));
+ fclose($fp);
+ $diff = shell_exec($this->_diffCommand . ' ' . $from_file . ' ' . $to_file);
+ unlink($from_file);
+ unlink($to_file);
+
+ if (is_null($diff)) {
+ // No changes were made
+ return array(new Text_Diff_Op_copy($from_lines));
+ }
+
+ $from_line_no = 1;
+ $to_line_no = 1;
+ $edits = array();
+
+ // Get changed lines by parsing something like:
+ // 0a1,2
+ // 1,2c4,6
+ // 1,5d6
+ preg_match_all('#^(\d+)(?:,(\d+))?([adc])(\d+)(?:,(\d+))?$#m', $diff,
+ $matches, PREG_SET_ORDER);
+
+ foreach ($matches as $match) {
+ if (!isset($match[5])) {
+ // This paren is not set every time (see regex).
+ $match[5] = false;
+ }
+
+ if ($match[3] == 'a') {
+ $from_line_no--;
+ }
+
+ if ($match[3] == 'd') {
+ $to_line_no--;
+ }
+
+ if ($from_line_no < $match[1] || $to_line_no < $match[4]) {
+ // copied lines
+ assert('$match[1] - $from_line_no == $match[4] - $to_line_no');
+ array_push($edits,
+ new Text_Diff_Op_copy(
+ $this->_getLines($from_lines, $from_line_no, $match[1] - 1),
+ $this->_getLines($to_lines, $to_line_no, $match[4] - 1)));
+ }
+
+ switch ($match[3]) {
+ case 'd':
+ // deleted lines
+ array_push($edits,
+ new Text_Diff_Op_delete(
+ $this->_getLines($from_lines, $from_line_no, $match[2])));
+ $to_line_no++;
+ break;
+
+ case 'c':
+ // changed lines
+ array_push($edits,
+ new Text_Diff_Op_change(
+ $this->_getLines($from_lines, $from_line_no, $match[2]),
+ $this->_getLines($to_lines, $to_line_no, $match[5])));
+ break;
+
+ case 'a':
+ // added lines
+ array_push($edits,
+ new Text_Diff_Op_add(
+ $this->_getLines($to_lines, $to_line_no, $match[5])));
+ $from_line_no++;
+ break;
+ }
+ }
+
+ if (!empty($from_lines)) {
+ // Some lines might still be pending. Add them as copied
+ array_push($edits,
+ new Text_Diff_Op_copy(
+ $this->_getLines($from_lines, $from_line_no,
+ $from_line_no + count($from_lines) - 1),
+ $this->_getLines($to_lines, $to_line_no,
+ $to_line_no + count($to_lines) - 1)));
+ }
+
+ return $edits;
+ }
+
+ /**
+ * Get lines from either the old or new text
+ *
+ * @access private
+ *
+ * @param array &$text_lines Either $from_lines or $to_lines
+ * @param int &$line_no Current line number
+ * @param int $end Optional end line, when we want to chop more
+ * than one line.
+ *
+ * @return array The chopped lines
+ */
+ function _getLines(&$text_lines, &$line_no, $end = false)
+ {
+ if (!empty($end)) {
+ $lines = array();
+ // We can shift even more
+ while ($line_no <= $end) {
+ array_push($lines, array_shift($text_lines));
+ $line_no++;
+ }
+ } else {
+ $lines = array(array_shift($text_lines));
+ $line_no++;
+ }
+
+ return $lines;
+ }
+
+}
diff --git a/includes/pear/Text/Diff/Engine/string.php b/includes/pear/Text/Diff/Engine/string.php
new file mode 100644
index 0000000..9352e60
--- /dev/null
+++ b/includes/pear/Text/Diff/Engine/string.php
@@ -0,0 +1,250 @@
+<?php
+/**
+ * Parses unified or context diffs output from eg. the diff utility.
+ *
+ * Example:
+ * <code>
+ * $patch = file_get_contents('example.patch');
+ * $diff = new Text_Diff('string', array($patch));
+ * $renderer = new Text_Diff_Renderer_inline();
+ * echo $renderer->render($diff);
+ * </code>
+ *
+ * $Horde: framework/Text_Diff/Diff/Engine/string.php,v 1.5.2.7 2009/07/24 13:04:43 jan Exp $
+ *
+ * Copyright 2005 Örjan Persson <o@42mm.org>
+ * Copyright 2005-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
+ *
+ * @author Örjan Persson <o@42mm.org>
+ * @package Text_Diff
+ * @since 0.2.0
+ */
+class Text_Diff_Engine_string {
+
+ /**
+ * Parses a unified or context diff.
+ *
+ * First param contains the whole diff and the second can be used to force
+ * a specific diff type. If the second parameter is 'autodetect', the
+ * diff will be examined to find out which type of diff this is.
+ *
+ * @param string $diff The diff content.
+ * @param string $mode The diff mode of the content in $diff. One of
+ * 'context', 'unified', or 'autodetect'.
+ *
+ * @return array List of all diff operations.
+ */
+ function diff($diff, $mode = 'autodetect')
+ {
+ // Detect line breaks.
+ $lnbr = "\n";
+ if (strpos($diff, "\r\n") !== false) {
+ $lnbr = "\r\n";
+ } elseif (strpos($diff, "\r") !== false) {
+ $lnbr = "\r";
+ }
+
+ // Make sure we have a line break at the EOF.
+ if (substr($diff, -strlen($lnbr)) != $lnbr) {
+ $diff .= $lnbr;
+ }
+
+ if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') {
+ return PEAR::raiseError('Type of diff is unsupported');
+ }
+
+ if ($mode == 'autodetect') {
+ $context = strpos($diff, '***');
+ $unified = strpos($diff, '---');
+ if ($context === $unified) {
+ return PEAR::raiseError('Type of diff could not be detected');
+ } elseif ($context === false || $unified === false) {
+ $mode = $context !== false ? 'context' : 'unified';
+ } else {
+ $mode = $context < $unified ? 'context' : 'unified';
+ }
+ }
+
+ // Split by new line and remove the diff header, if there is one.
+ $diff = explode($lnbr, $diff);
+ if (($mode == 'context' && strpos($diff[0], '***') === 0) ||
+ ($mode == 'unified' && strpos($diff[0], '---') === 0)) {
+ array_shift($diff);
+ array_shift($diff);
+ }
+
+ if ($mode == 'context') {
+ return $this->parseContextDiff($diff);
+ } else {
+ return $this->parseUnifiedDiff($diff);
+ }
+ }
+
+ /**
+ * Parses an array containing the unified diff.
+ *
+ * @param array $diff Array of lines.
+ *
+ * @return array List of all diff operations.
+ */
+ function parseUnifiedDiff($diff)
+ {
+ $edits = array();
+ $end = count($diff) - 1;
+ for ($i = 0; $i < $end;) {
+ $diff1 = array();
+ switch (substr($diff[$i], 0, 1)) {
+ case ' ':
+ do {
+ $diff1[] = substr($diff[$i], 1);
+ } while (++$i < $end && substr($diff[$i], 0, 1) == ' ');
+ $edits[] = new Text_Diff_Op_copy($diff1);
+ break;
+
+ case '+':
+ // get all new lines
+ do {
+ $diff1[] = substr($diff[$i], 1);
+ } while (++$i < $end && substr($diff[$i], 0, 1) == '+');
+ $edits[] = new Text_Diff_Op_add($diff1);
+ break;
+
+ case '-':
+ // get changed or removed lines
+ $diff2 = array();
+ do {
+ $diff1[] = substr($diff[$i], 1);
+ } while (++$i < $end && substr($diff[$i], 0, 1) == '-');
+
+ while ($i < $end && substr($diff[$i], 0, 1) == '+') {
+ $diff2[] = substr($diff[$i++], 1);
+ }
+ if (count($diff2) == 0) {
+ $edits[] = new Text_Diff_Op_delete($diff1);
+ } else {
+ $edits[] = new Text_Diff_Op_change($diff1, $diff2);
+ }
+ break;
+
+ default:
+ $i++;
+ break;
+ }
+ }
+
+ return $edits;
+ }
+
+ /**
+ * Parses an array containing the context diff.
+ *
+ * @param array $diff Array of lines.
+ *
+ * @return array List of all diff operations.
+ */
+ function parseContextDiff(&$diff)
+ {
+ $edits = array();
+ $i = $max_i = $j = $max_j = 0;
+ $end = count($diff) - 1;
+ while ($i < $end && $j < $end) {
+ while ($i >= $max_i && $j >= $max_j) {
+ // Find the boundaries of the diff output of the two files
+ for ($i = $j;
+ $i < $end && substr($diff[$i], 0, 3) == '***';
+ $i++);
+ for ($max_i = $i;
+ $max_i < $end && substr($diff[$max_i], 0, 3) != '---';
+ $max_i++);
+ for ($j = $max_i;
+ $j < $end && substr($diff[$j], 0, 3) == '---';
+ $j++);
+ for ($max_j = $j;
+ $max_j < $end && substr($diff[$max_j], 0, 3) != '***';
+ $max_j++);
+ }
+
+ // find what hasn't been changed
+ $array = array();
+ while ($i < $max_i &&
+ $j < $max_j &&
+ strcmp($diff[$i], $diff[$j]) == 0) {
+ $array[] = substr($diff[$i], 2);
+ $i++;
+ $j++;
+ }
+
+ while ($i < $max_i && ($max_j-$j) <= 1) {
+ if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') {
+ break;
+ }
+ $array[] = substr($diff[$i++], 2);
+ }
+
+ while ($j < $max_j && ($max_i-$i) <= 1) {
+ if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') {
+ break;
+ }
+ $array[] = substr($diff[$j++], 2);
+ }
+ if (count($array) > 0) {
+ $edits[] = new Text_Diff_Op_copy($array);
+ }
+
+ if ($i < $max_i) {
+ $diff1 = array();
+ switch (substr($diff[$i], 0, 1)) {
+ case '!':
+ $diff2 = array();
+ do {
+ $diff1[] = substr($diff[$i], 2);
+ if ($j < $max_j && substr($diff[$j], 0, 1) == '!') {
+ $diff2[] = substr($diff[$j++], 2);
+ }
+ } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!');
+ $edits[] = new Text_Diff_Op_change($diff1, $diff2);
+ break;
+
+ case '+':
+ do {
+ $diff1[] = substr($diff[$i], 2);
+ } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+');
+ $edits[] = new Text_Diff_Op_add($diff1);
+ break;
+
+ case '-':
+ do {
+ $diff1[] = substr($diff[$i], 2);
+ } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-');
+ $edits[] = new Text_Diff_Op_delete($diff1);
+ break;
+ }
+ }
+
+ if ($j < $max_j) {
+ $diff2 = array();
+ switch (substr($diff[$j], 0, 1)) {
+ case '+':
+ do {
+ $diff2[] = substr($diff[$j++], 2);
+ } while ($j < $max_j && substr($diff[$j], 0, 1) == '+');
+ $edits[] = new Text_Diff_Op_add($diff2);
+ break;
+
+ case '-':
+ do {
+ $diff2[] = substr($diff[$j++], 2);
+ } while ($j < $max_j && substr($diff[$j], 0, 1) == '-');
+ $edits[] = new Text_Diff_Op_delete($diff2);
+ break;
+ }
+ }
+ }
+
+ return $edits;
+ }
+
+}
diff --git a/includes/pear/Text/Diff/Engine/xdiff.php b/includes/pear/Text/Diff/Engine/xdiff.php
new file mode 100644
index 0000000..241d2e5
--- /dev/null
+++ b/includes/pear/Text/Diff/Engine/xdiff.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Class used internally by Diff to actually compute the diffs.
+ *
+ * This class uses the xdiff PECL package (http://pecl.php.net/package/xdiff)
+ * to compute the differences between the two input arrays.
+ *
+ * $Horde: framework/Text_Diff/Diff/Engine/xdiff.php,v 1.4.2.5 2009/07/24 13:06:24 jan Exp $
+ *
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
+ *
+ * @author Jon Parise <jon@horde.org>
+ * @package Text_Diff
+ */
+class Text_Diff_Engine_xdiff {
+
+ /**
+ */
+ function diff($from_lines, $to_lines)
+ {
+ array_walk($from_lines, array('Text_Diff', 'trimNewlines'));
+ array_walk($to_lines, array('Text_Diff', 'trimNewlines'));
+
+ /* Convert the two input arrays into strings for xdiff processing. */
+ $from_string = implode("\n", $from_lines);
+ $to_string = implode("\n", $to_lines);
+
+ /* Diff the two strings and convert the result to an array. */
+ $diff = xdiff_string_diff($from_string, $to_string, count($to_lines));
+ $diff = explode("\n", $diff);
+
+ /* Walk through the diff one line at a time. We build the $edits
+ * array of diff operations by reading the first character of the
+ * xdiff output (which is in the "unified diff" format).
+ *
+ * Note that we don't have enough information to detect "changed"
+ * lines using this approach, so we can't add Text_Diff_Op_changed
+ * instances to the $edits array. The result is still perfectly
+ * valid, albeit a little less descriptive and efficient. */
+ $edits = array();
+ foreach ($diff as $line) {
+ if (!strlen($line)) {
+ continue;
+ }
+ switch ($line[0]) {
+ case ' ':
+ $edits[] = &new Text_Diff_Op_copy(array(substr($line, 1)));
+ break;
+
+ case '+':
+ $edits[] = &new Text_Diff_Op_add(array(substr($line, 1)));
+ break;
+
+ case '-':
+ $edits[] = &new Text_Diff_Op_delete(array(substr($line, 1)));
+ break;
+ }
+ }
+
+ return $edits;
+ }
+
+}
diff --git a/includes/pear/Text/Diff/Mapped.php b/includes/pear/Text/Diff/Mapped.php
new file mode 100644
index 0000000..dc46e5e
--- /dev/null
+++ b/includes/pear/Text/Diff/Mapped.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * $Horde: framework/Text_Diff/Diff/Mapped.php,v 1.3.2.4 2009/01/06 15:23:41 jan Exp $
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
+ *
+ * @package Text_Diff
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ */
+class Text_Diff_Mapped extends Text_Diff {
+
+ /**
+ * Computes a diff between sequences of strings.
+ *
+ * This can be used to compute things like case-insensitve diffs, or diffs
+ * which ignore changes in white-space.
+ *
+ * @param array $from_lines An array of strings.
+ * @param array $to_lines An array of strings.
+ * @param array $mapped_from_lines This array should have the same size
+ * number of elements as $from_lines. The
+ * elements in $mapped_from_lines and
+ * $mapped_to_lines are what is actually
+ * compared when computing the diff.
+ * @param array $mapped_to_lines This array should have the same number
+ * of elements as $to_lines.
+ */
+ function Text_Diff_Mapped($from_lines, $to_lines,
+ $mapped_from_lines, $mapped_to_lines)
+ {
+ assert(count($from_lines) == count($mapped_from_lines));
+ assert(count($to_lines) == count($mapped_to_lines));
+
+ parent::Text_Diff($mapped_from_lines, $mapped_to_lines);
+
+ $xi = $yi = 0;
+ for ($i = 0; $i < count($this->_edits); $i++) {
+ $orig = &$this->_edits[$i]->orig;
+ if (is_array($orig)) {
+ $orig = array_slice($from_lines, $xi, count($orig));
+ $xi += count($orig);
+ }
+
+ $final = &$this->_edits[$i]->final;
+ if (is_array($final)) {
+ $final = array_slice($to_lines, $yi, count($final));
+ $yi += count($final);
+ }
+ }
+ }
+
+}
diff --git a/includes/pear/Text/Diff/Renderer.php b/includes/pear/Text/Diff/Renderer.php
new file mode 100644
index 0000000..3a51650
--- /dev/null
+++ b/includes/pear/Text/Diff/Renderer.php
@@ -0,0 +1,237 @@
+<?php
+/**
+ * A class to render Diffs in different formats.
+ *
+ * This class renders the diff in classic diff format. It is intended that
+ * this class be customized via inheritance, to obtain fancier outputs.
+ *
+ * $Horde: framework/Text_Diff/Diff/Renderer.php,v 1.5.10.12 2009/07/24 13:26:40 jan Exp $
+ *
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
+ *
+ * @package Text_Diff
+ */
+class Text_Diff_Renderer {
+
+ /**
+ * Number of leading context "lines" to preserve.
+ *
+ * This should be left at zero for this class, but subclasses may want to
+ * set this to other values.
+ */
+ var $_leading_context_lines = 0;
+
+ /**
+ * Number of trailing context "lines" to preserve.
+ *
+ * This should be left at zero for this class, but subclasses may want to
+ * set this to other values.
+ */
+ var $_trailing_context_lines = 0;
+
+ /**
+ * Constructor.
+ */
+ function Text_Diff_Renderer($params = array())
+ {
+ foreach ($params as $param => $value) {
+ $v = '_' . $param;
+ if (isset($this->$v)) {
+ $this->$v = $value;
+ }
+ }
+ }
+
+ /**
+ * Get any renderer parameters.
+ *
+ * @return array All parameters of this renderer object.
+ */
+ function getParams()
+ {
+ $params = array();
+ foreach (get_object_vars($this) as $k => $v) {
+ if ($k[0] == '_') {
+ $params[substr($k, 1)] = $v;
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * Renders a diff.
+ *
+ * @param Text_Diff $diff A Text_Diff object.
+ *
+ * @return string The formatted output.
+ */
+ function render($diff)
+ {
+ $xi = $yi = 1;
+ $block = false;
+ $context = array();
+
+ $nlead = $this->_leading_context_lines;
+ $ntrail = $this->_trailing_context_lines;
+
+ $output = $this->_startDiff();
+
+ $diffs = $diff->getDiff();
+ foreach ($diffs as $i => $edit) {
+ /* If these are unchanged (copied) lines, and we want to keep
+ * leading or trailing context lines, extract them from the copy
+ * block. */
+ if (is_a($edit, 'Text_Diff_Op_copy')) {
+ /* Do we have any diff blocks yet? */
+ if (is_array($block)) {
+ /* How many lines to keep as context from the copy
+ * block. */
+ $keep = $i == count($diffs) - 1 ? $ntrail : $nlead + $ntrail;
+ if (count($edit->orig) <= $keep) {
+ /* We have less lines in the block than we want for
+ * context => keep the whole block. */
+ $block[] = $edit;
+ } else {
+ if ($ntrail) {
+ /* Create a new block with as many lines as we need
+ * for the trailing context. */
+ $context = array_slice($edit->orig, 0, $ntrail);
+ $block[] = new Text_Diff_Op_copy($context);
+ }
+ /* @todo */
+ $output .= $this->_block($x0, $ntrail + $xi - $x0,
+ $y0, $ntrail + $yi - $y0,
+ $block);
+ $block = false;
+ }
+ }
+ /* Keep the copy block as the context for the next block. */
+ $context = $edit->orig;
+ } else {
+ /* Don't we have any diff blocks yet? */
+ if (!is_array($block)) {
+ /* Extract context lines from the preceding copy block. */
+ $context = array_slice($context, count($context) - $nlead);
+ $x0 = $xi - count($context);
+ $y0 = $yi - count($context);
+ $block = array();
+ if ($context) {
+ $block[] = new Text_Diff_Op_copy($context);
+ }
+ }
+ $block[] = $edit;
+ }
+
+ if ($edit->orig) {
+ $xi += count($edit->orig);
+ }
+ if ($edit->final) {
+ $yi += count($edit->final);
+ }
+ }
+
+ if (is_array($block)) {
+ $output .= $this->_block($x0, $xi - $x0,
+ $y0, $yi - $y0,
+ $block);
+ }
+
+ return $output . $this->_endDiff();
+ }
+
+ function _block($xbeg, $xlen, $ybeg, $ylen, &$edits)
+ {
+ $output = $this->_startBlock($this->_blockHeader($xbeg, $xlen, $ybeg, $ylen));
+
+ foreach ($edits as $edit) {
+ switch (strtolower(get_class($edit))) {
+ case 'text_diff_op_copy':
+ $output .= $this->_context($edit->orig);
+ break;
+
+ case 'text_diff_op_add':
+ $output .= $this->_added($edit->final);
+ break;
+
+ case 'text_diff_op_delete':
+ $output .= $this->_deleted($edit->orig);
+ break;
+
+ case 'text_diff_op_change':
+ $output .= $this->_changed($edit->orig, $edit->final);
+ break;
+ }
+ }
+
+ return $output . $this->_endBlock();
+ }
+
+ function _startDiff()
+ {
+ return '';
+ }
+
+ function _endDiff()
+ {
+ return '';
+ }
+
+ function _blockHeader($xbeg, $xlen, $ybeg, $ylen)
+ {
+ if ($xlen > 1) {
+ $xbeg .= ',' . ($xbeg + $xlen - 1);
+ }
+ if ($ylen > 1) {
+ $ybeg .= ',' . ($ybeg + $ylen - 1);
+ }
+
+ // this matches the GNU Diff behaviour
+ if ($xlen && !$ylen) {
+ $ybeg--;
+ } elseif (!$xlen) {
+ $xbeg--;
+ }
+
+ return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg;
+ }
+
+ function _startBlock($header)
+ {
+ return $header . "\n";
+ }
+
+ function _endBlock()
+ {
+ return '';
+ }
+
+ function _lines($lines, $prefix = ' ')
+ {
+ return $prefix . implode("\n$prefix", $lines) . "\n";
+ }
+
+ function _context($lines)
+ {
+ return $this->_lines($lines, ' ');
+ }
+
+ function _added($lines)
+ {
+ return $this->_lines($lines, '> ');
+ }
+
+ function _deleted($lines)
+ {
+ return $this->_lines($lines, '< ');
+ }
+
+ function _changed($orig, $final)
+ {
+ return $this->_deleted($orig) . "---\n" . $this->_added($final);
+ }
+
+}
diff --git a/includes/pear/Text/Diff/Renderer/context.php b/includes/pear/Text/Diff/Renderer/context.php
new file mode 100644
index 0000000..af53801
--- /dev/null
+++ b/includes/pear/Text/Diff/Renderer/context.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * "Context" diff renderer.
+ *
+ * This class renders the diff in classic "context diff" format.
+ *
+ * $Horde: framework/Text_Diff/Diff/Renderer/context.php,v 1.3.2.4 2009/01/06 15:23:42 jan Exp $
+ *
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
+ *
+ * @package Text_Diff
+ */
+
+/** Text_Diff_Renderer */
+require_once 'Text/Diff/Renderer.php';
+
+/**
+ * @package Text_Diff
+ */
+class Text_Diff_Renderer_context extends Text_Diff_Renderer {
+
+ /**
+ * Number of leading context "lines" to preserve.
+ */
+ var $_leading_context_lines = 4;
+
+ /**
+ * Number of trailing context "lines" to preserve.
+ */
+ var $_trailing_context_lines = 4;
+
+ var $_second_block = '';
+
+ function _blockHeader($xbeg, $xlen, $ybeg, $ylen)
+ {
+ if ($xlen != 1) {
+ $xbeg .= ',' . $xlen;
+ }
+ if ($ylen != 1) {
+ $ybeg .= ',' . $ylen;
+ }
+ $this->_second_block = "--- $ybeg ----\n";
+ return "***************\n*** $xbeg ****";
+ }
+
+ function _endBlock()
+ {
+ return $this->_second_block;
+ }
+
+ function _context($lines)
+ {
+ $this->_second_block .= $this->_lines($lines, ' ');
+ return $this->_lines($lines, ' ');
+ }
+
+ function _added($lines)
+ {
+ $this->_second_block .= $this->_lines($lines, '+ ');
+ return '';
+ }
+
+ function _deleted($lines)
+ {
+ return $this->_lines($lines, '- ');
+ }
+
+ function _changed($orig, $final)
+ {
+ $this->_second_block .= $this->_lines($final, '! ');
+ return $this->_lines($orig, '! ');
+ }
+
+}
diff --git a/includes/pear/Text/Diff/Renderer/inline.php b/includes/pear/Text/Diff/Renderer/inline.php
new file mode 100644
index 0000000..5dd20d2
--- /dev/null
+++ b/includes/pear/Text/Diff/Renderer/inline.php
@@ -0,0 +1,172 @@
+<?php
+/**
+ * "Inline" diff renderer.
+ *
+ * $Horde: framework/Text_Diff/Diff/Renderer/inline.php,v 1.4.10.16 2009/07/24 13:25:29 jan Exp $
+ *
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
+ *
+ * @author Ciprian Popovici
+ * @package Text_Diff
+ */
+
+/** Text_Diff_Renderer */
+require_once 'Text/Diff/Renderer.php';
+
+/**
+ * "Inline" diff renderer.
+ *
+ * This class renders diffs in the Wiki-style "inline" format.
+ *
+ * @author Ciprian Popovici
+ * @package Text_Diff
+ */
+class Text_Diff_Renderer_inline extends Text_Diff_Renderer {
+
+ /**
+ * Number of leading context "lines" to preserve.
+ */
+ var $_leading_context_lines = 10000;
+
+ /**
+ * Number of trailing context "lines" to preserve.
+ */
+ var $_trailing_context_lines = 10000;
+
+ /**
+ * Prefix for inserted text.
+ */
+ var $_ins_prefix = '<ins>';
+
+ /**
+ * Suffix for inserted text.
+ */
+ var $_ins_suffix = '</ins>';
+
+ /**
+ * Prefix for deleted text.
+ */
+ var $_del_prefix = '<del>';
+
+ /**
+ * Suffix for deleted text.
+ */
+ var $_del_suffix = '</del>';
+
+ /**
+ * Header for each change block.
+ */
+ var $_block_header = '';
+
+ /**
+ * What are we currently splitting on? Used to recurse to show word-level
+ * changes.
+ */
+ var $_split_level = 'lines';
+
+ function _blockHeader($xbeg, $xlen, $ybeg, $ylen)
+ {
+ return $this->_block_header;
+ }
+
+ function _startBlock($header)
+ {
+ return $header;
+ }
+
+ function _lines($lines, $prefix = ' ', $encode = true)
+ {
+ if ($encode) {
+ array_walk($lines, array(&$this, '_encode'));
+ }
+
+ if ($this->_split_level == 'words') {
+ return implode('', $lines);
+ } else {
+ return implode("\n", $lines) . "\n";
+ }
+ }
+
+ function _added($lines)
+ {
+ array_walk($lines, array(&$this, '_encode'));
+ $lines[0] = $this->_ins_prefix . $lines[0];
+ $lines[count($lines) - 1] .= $this->_ins_suffix;
+ return $this->_lines($lines, ' ', false);
+ }
+
+ function _deleted($lines, $words = false)
+ {
+ array_walk($lines, array(&$this, '_encode'));
+ $lines[0] = $this->_del_prefix . $lines[0];
+ $lines[count($lines) - 1] .= $this->_del_suffix;
+ return $this->_lines($lines, ' ', false);
+ }
+
+ function _changed($orig, $final)
+ {
+ /* If we've already split on words, don't try to do so again - just
+ * display. */
+ if ($this->_split_level == 'words') {
+ $prefix = '';
+ while ($orig[0] !== false && $final[0] !== false &&
+ substr($orig[0], 0, 1) == ' ' &&
+ substr($final[0], 0, 1) == ' ') {
+ $prefix .= substr($orig[0], 0, 1);
+ $orig[0] = substr($orig[0], 1);
+ $final[0] = substr($final[0], 1);
+ }
+ return $prefix . $this->_deleted($orig) . $this->_added($final);
+ }
+
+ $text1 = implode("\n", $orig);
+ $text2 = implode("\n", $final);
+
+ /* Non-printing newline marker. */
+ $nl = "\0";
+
+ /* We want to split on word boundaries, but we need to
+ * preserve whitespace as well. Therefore we split on words,
+ * but include all blocks of whitespace in the wordlist. */
+ $diff = new Text_Diff('native',
+ array($this->_splitOnWords($text1, $nl),
+ $this->_splitOnWords($text2, $nl)));
+
+ /* Get the diff in inline format. */
+ $renderer = new Text_Diff_Renderer_inline
+ (array_merge($this->getParams(),
+ array('split_level' => 'words')));
+
+ /* Run the diff and get the output. */
+ return str_replace($nl, "\n", $renderer->render($diff)) . "\n";
+ }
+
+ function _splitOnWords($string, $newlineEscape = "\n")
+ {
+ // Ignore \0; otherwise the while loop will never finish.
+ $string = str_replace("\0", '', $string);
+
+ $words = array();
+ $length = strlen($string);
+ $pos = 0;
+
+ while ($pos < $length) {
+ // Eat a word with any preceding whitespace.
+ $spaces = strspn(substr($string, $pos), " \n");
+ $nextpos = strcspn(substr($string, $pos + $spaces), " \n");
+ $words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos));
+ $pos += $spaces + $nextpos;
+ }
+
+ return $words;
+ }
+
+ function _encode(&$string)
+ {
+ $string = htmlspecialchars($string);
+ }
+
+}
diff --git a/includes/pear/Text/Diff/Renderer/unified.php b/includes/pear/Text/Diff/Renderer/unified.php
new file mode 100644
index 0000000..f990f72
--- /dev/null
+++ b/includes/pear/Text/Diff/Renderer/unified.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * "Unified" diff renderer.
+ *
+ * This class renders the diff in classic "unified diff" format.
+ *
+ * $Horde: framework/Text_Diff/Diff/Renderer/unified.php,v 1.3.10.7 2009/01/06 15:23:42 jan Exp $
+ *
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
+ *
+ * @author Ciprian Popovici
+ * @package Text_Diff
+ */
+
+/** Text_Diff_Renderer */
+require_once 'Text/Diff/Renderer.php';
+
+/**
+ * @package Text_Diff
+ */
+class Text_Diff_Renderer_unified extends Text_Diff_Renderer {
+
+ /**
+ * Number of leading context "lines" to preserve.
+ */
+ var $_leading_context_lines = 4;
+
+ /**
+ * Number of trailing context "lines" to preserve.
+ */
+ var $_trailing_context_lines = 4;
+
+ function _blockHeader($xbeg, $xlen, $ybeg, $ylen)
+ {
+ if ($xlen != 1) {
+ $xbeg .= ',' . $xlen;
+ }
+ if ($ylen != 1) {
+ $ybeg .= ',' . $ylen;
+ }
+ return "@@ -$xbeg +$ybeg @@";
+ }
+
+ function _context($lines)
+ {
+ return $this->_lines($lines, ' ');
+ }
+
+ function _added($lines)
+ {
+ return $this->_lines($lines, '+');
+ }
+
+ function _deleted($lines)
+ {
+ return $this->_lines($lines, '-');
+ }
+
+ function _changed($orig, $final)
+ {
+ return $this->_deleted($orig) . $this->_added($final);
+ }
+
+}
diff --git a/includes/pear/Text/Diff/ThreeWay.php b/includes/pear/Text/Diff/ThreeWay.php
new file mode 100644
index 0000000..5b0357c
--- /dev/null
+++ b/includes/pear/Text/Diff/ThreeWay.php
@@ -0,0 +1,276 @@
+<?php
+/**
+ * A class for computing three way diffs.
+ *
+ * $Horde: framework/Text_Diff/Diff/ThreeWay.php,v 1.3.2.4 2009/01/06 15:23:41 jan Exp $
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
+ *
+ * @package Text_Diff
+ * @since 0.3.0
+ */
+
+/** Text_Diff */
+require_once 'Text/Diff.php';
+
+/**
+ * A class for computing three way diffs.
+ *
+ * @package Text_Diff
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ */
+class Text_Diff_ThreeWay extends Text_Diff {
+
+ /**
+ * Conflict counter.
+ *
+ * @var integer
+ */
+ var $_conflictingBlocks = 0;
+
+ /**
+ * Computes diff between 3 sequences of strings.
+ *
+ * @param array $orig The original lines to use.
+ * @param array $final1 The first version to compare to.
+ * @param array $final2 The second version to compare to.
+ */
+ function Text_Diff_ThreeWay($orig, $final1, $final2)
+ {
+ if (extension_loaded('xdiff')) {
+ $engine = new Text_Diff_Engine_xdiff();
+ } else {
+ $engine = new Text_Diff_Engine_native();
+ }
+
+ $this->_edits = $this->_diff3($engine->diff($orig, $final1),
+ $engine->diff($orig, $final2));
+ }
+
+ /**
+ */
+ function mergedOutput($label1 = false, $label2 = false)
+ {
+ $lines = array();
+ foreach ($this->_edits as $edit) {
+ if ($edit->isConflict()) {
+ /* FIXME: this should probably be moved somewhere else. */
+ $lines = array_merge($lines,
+ array('<<<<<<<' . ($label1 ? ' ' . $label1 : '')),
+ $edit->final1,
+ array("======="),
+ $edit->final2,
+ array('>>>>>>>' . ($label2 ? ' ' . $label2 : '')));
+ $this->_conflictingBlocks++;
+ } else {
+ $lines = array_merge($lines, $edit->merged());
+ }
+ }
+
+ return $lines;
+ }
+
+ /**
+ * @access private
+ */
+ function _diff3($edits1, $edits2)
+ {
+ $edits = array();
+ $bb = new Text_Diff_ThreeWay_BlockBuilder();
+
+ $e1 = current($edits1);
+ $e2 = current($edits2);
+ while ($e1 || $e2) {
+ if ($e1 && $e2 && is_a($e1, 'Text_Diff_Op_copy') && is_a($e2, 'Text_Diff_Op_copy')) {
+ /* We have copy blocks from both diffs. This is the (only)
+ * time we want to emit a diff3 copy block. Flush current
+ * diff3 diff block, if any. */
+ if ($edit = $bb->finish()) {
+ $edits[] = $edit;
+ }
+
+ $ncopy = min($e1->norig(), $e2->norig());
+ assert($ncopy > 0);
+ $edits[] = new Text_Diff_ThreeWay_Op_copy(array_slice($e1->orig, 0, $ncopy));
+
+ if ($e1->norig() > $ncopy) {
+ array_splice($e1->orig, 0, $ncopy);
+ array_splice($e1->final, 0, $ncopy);
+ } else {
+ $e1 = next($edits1);
+ }
+
+ if ($e2->norig() > $ncopy) {
+ array_splice($e2->orig, 0, $ncopy);
+ array_splice($e2->final, 0, $ncopy);
+ } else {
+ $e2 = next($edits2);
+ }
+ } else {
+ if ($e1 && $e2) {
+ if ($e1->orig && $e2->orig) {
+ $norig = min($e1->norig(), $e2->norig());
+ $orig = array_splice($e1->orig, 0, $norig);
+ array_splice($e2->orig, 0, $norig);
+ $bb->input($orig);
+ }
+
+ if (is_a($e1, 'Text_Diff_Op_copy')) {
+ $bb->out1(array_splice($e1->final, 0, $norig));
+ }
+
+ if (is_a($e2, 'Text_Diff_Op_copy')) {
+ $bb->out2(array_splice($e2->final, 0, $norig));
+ }
+ }
+
+ if ($e1 && ! $e1->orig) {
+ $bb->out1($e1->final);
+ $e1 = next($edits1);
+ }
+ if ($e2 && ! $e2->orig) {
+ $bb->out2($e2->final);
+ $e2 = next($edits2);
+ }
+ }
+ }
+
+ if ($edit = $bb->finish()) {
+ $edits[] = $edit;
+ }
+
+ return $edits;
+ }
+
+}
+
+/**
+ * @package Text_Diff
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ *
+ * @access private
+ */
+class Text_Diff_ThreeWay_Op {
+
+ function Text_Diff_ThreeWay_Op($orig = false, $final1 = false, $final2 = false)
+ {
+ $this->orig = $orig ? $orig : array();
+ $this->final1 = $final1 ? $final1 : array();
+ $this->final2 = $final2 ? $final2 : array();
+ }
+
+ function merged()
+ {
+ if (!isset($this->_merged)) {
+ if ($this->final1 === $this->final2) {
+ $this->_merged = &$this->final1;
+ } elseif ($this->final1 === $this->orig) {
+ $this->_merged = &$this->final2;
+ } elseif ($this->final2 === $this->orig) {
+ $this->_merged = &$this->final1;
+ } else {
+ $this->_merged = false;
+ }
+ }
+
+ return $this->_merged;
+ }
+
+ function isConflict()
+ {
+ return $this->merged() === false;
+ }
+
+}
+
+/**
+ * @package Text_Diff
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ *
+ * @access private
+ */
+class Text_Diff_ThreeWay_Op_copy extends Text_Diff_ThreeWay_Op {
+
+ function Text_Diff_ThreeWay_Op_Copy($lines = false)
+ {
+ $this->orig = $lines ? $lines : array();
+ $this->final1 = &$this->orig;
+ $this->final2 = &$this->orig;
+ }
+
+ function merged()
+ {
+ return $this->orig;
+ }
+
+ function isConflict()
+ {
+ return false;
+ }
+
+}
+
+/**
+ * @package Text_Diff
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ *
+ * @access private
+ */
+class Text_Diff_ThreeWay_BlockBuilder {
+
+ function Text_Diff_ThreeWay_BlockBuilder()
+ {
+ $this->_init();
+ }
+
+ function input($lines)
+ {
+ if ($lines) {
+ $this->_append($this->orig, $lines);
+ }
+ }
+
+ function out1($lines)
+ {
+ if ($lines) {
+ $this->_append($this->final1, $lines);
+ }
+ }
+
+ function out2($lines)
+ {
+ if ($lines) {
+ $this->_append($this->final2, $lines);
+ }
+ }
+
+ function isEmpty()
+ {
+ return !$this->orig && !$this->final1 && !$this->final2;
+ }
+
+ function finish()
+ {
+ if ($this->isEmpty()) {
+ return false;
+ } else {
+ $edit = new Text_Diff_ThreeWay_Op($this->orig, $this->final1, $this->final2);
+ $this->_init();
+ return $edit;
+ }
+ }
+
+ function _init()
+ {
+ $this->orig = $this->final1 = $this->final2 = array();
+ }
+
+ function _append(&$array, $lines)
+ {
+ array_splice($array, sizeof($array), 0, $lines);
+ }
+
+}
diff --git a/includes/pear/Text/Diff3.php b/includes/pear/Text/Diff3.php
new file mode 100644
index 0000000..e9aea9f
--- /dev/null
+++ b/includes/pear/Text/Diff3.php
@@ -0,0 +1,276 @@
+<?php
+/**
+ * A class for computing three way diffs.
+ *
+ * $Horde: framework/Text_Diff/Diff3.php,v 1.2.10.7 2009/01/06 15:23:41 jan Exp $
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
+ *
+ * @package Text_Diff
+ * @since 0.3.0
+ */
+
+/** Text_Diff */
+require_once 'Text/Diff.php';
+
+/**
+ * A class for computing three way diffs.
+ *
+ * @package Text_Diff
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ */
+class Text_Diff3 extends Text_Diff {
+
+ /**
+ * Conflict counter.
+ *
+ * @var integer
+ */
+ var $_conflictingBlocks = 0;
+
+ /**
+ * Computes diff between 3 sequences of strings.
+ *
+ * @param array $orig The original lines to use.
+ * @param array $final1 The first version to compare to.
+ * @param array $final2 The second version to compare to.
+ */
+ function Text_Diff3($orig, $final1, $final2)
+ {
+ if (extension_loaded('xdiff')) {
+ $engine = new Text_Diff_Engine_xdiff();
+ } else {
+ $engine = new Text_Diff_Engine_native();
+ }
+
+ $this->_edits = $this->_diff3($engine->diff($orig, $final1),
+ $engine->diff($orig, $final2));
+ }
+
+ /**
+ */
+ function mergedOutput($label1 = false, $label2 = false)
+ {
+ $lines = array();
+ foreach ($this->_edits as $edit) {
+ if ($edit->isConflict()) {
+ /* FIXME: this should probably be moved somewhere else. */
+ $lines = array_merge($lines,
+ array('<<<<<<<' . ($label1 ? ' ' . $label1 : '')),
+ $edit->final1,
+ array("======="),
+ $edit->final2,
+ array('>>>>>>>' . ($label2 ? ' ' . $label2 : '')));
+ $this->_conflictingBlocks++;
+ } else {
+ $lines = array_merge($lines, $edit->merged());
+ }
+ }
+
+ return $lines;
+ }
+
+ /**
+ * @access private
+ */
+ function _diff3($edits1, $edits2)
+ {
+ $edits = array();
+ $bb = new Text_Diff3_BlockBuilder();
+
+ $e1 = current($edits1);
+ $e2 = current($edits2);
+ while ($e1 || $e2) {
+ if ($e1 && $e2 && is_a($e1, 'Text_Diff_Op_copy') && is_a($e2, 'Text_Diff_Op_copy')) {
+ /* We have copy blocks from both diffs. This is the (only)
+ * time we want to emit a diff3 copy block. Flush current
+ * diff3 diff block, if any. */
+ if ($edit = $bb->finish()) {
+ $edits[] = $edit;
+ }
+
+ $ncopy = min($e1->norig(), $e2->norig());
+ assert($ncopy > 0);
+ $edits[] = new Text_Diff3_Op_copy(array_slice($e1->orig, 0, $ncopy));
+
+ if ($e1->norig() > $ncopy) {
+ array_splice($e1->orig, 0, $ncopy);
+ array_splice($e1->final, 0, $ncopy);
+ } else {
+ $e1 = next($edits1);
+ }
+
+ if ($e2->norig() > $ncopy) {
+ array_splice($e2->orig, 0, $ncopy);
+ array_splice($e2->final, 0, $ncopy);
+ } else {
+ $e2 = next($edits2);
+ }
+ } else {
+ if ($e1 && $e2) {
+ if ($e1->orig && $e2->orig) {
+ $norig = min($e1->norig(), $e2->norig());
+ $orig = array_splice($e1->orig, 0, $norig);
+ array_splice($e2->orig, 0, $norig);
+ $bb->input($orig);
+ }
+
+ if (is_a($e1, 'Text_Diff_Op_copy')) {
+ $bb->out1(array_splice($e1->final, 0, $norig));
+ }
+
+ if (is_a($e2, 'Text_Diff_Op_copy')) {
+ $bb->out2(array_splice($e2->final, 0, $norig));
+ }
+ }
+
+ if ($e1 && ! $e1->orig) {
+ $bb->out1($e1->final);
+ $e1 = next($edits1);
+ }
+ if ($e2 && ! $e2->orig) {
+ $bb->out2($e2->final);
+ $e2 = next($edits2);
+ }
+ }
+ }
+
+ if ($edit = $bb->finish()) {
+ $edits[] = $edit;
+ }
+
+ return $edits;
+ }
+
+}
+
+/**
+ * @package Text_Diff
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ *
+ * @access private
+ */
+class Text_Diff3_Op {
+
+ function Text_Diff3_Op($orig = false, $final1 = false, $final2 = false)
+ {
+ $this->orig = $orig ? $orig : array();
+ $this->final1 = $final1 ? $final1 : array();
+ $this->final2 = $final2 ? $final2 : array();
+ }
+
+ function merged()
+ {
+ if (!isset($this->_merged)) {
+ if ($this->final1 === $this->final2) {
+ $this->_merged = &$this->final1;
+ } elseif ($this->final1 === $this->orig) {
+ $this->_merged = &$this->final2;
+ } elseif ($this->final2 === $this->orig) {
+ $this->_merged = &$this->final1;
+ } else {
+ $this->_merged = false;
+ }
+ }
+
+ return $this->_merged;
+ }
+
+ function isConflict()
+ {
+ return $this->merged() === false;
+ }
+
+}
+
+/**
+ * @package Text_Diff
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ *
+ * @access private
+ */
+class Text_Diff3_Op_copy extends Text_Diff3_Op {
+
+ function Text_Diff3_Op_Copy($lines = false)
+ {
+ $this->orig = $lines ? $lines : array();
+ $this->final1 = &$this->orig;
+ $this->final2 = &$this->orig;
+ }
+
+ function merged()
+ {
+ return $this->orig;
+ }
+
+ function isConflict()
+ {
+ return false;
+ }
+
+}
+
+/**
+ * @package Text_Diff
+ * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
+ *
+ * @access private
+ */
+class Text_Diff3_BlockBuilder {
+
+ function Text_Diff3_BlockBuilder()
+ {
+ $this->_init();
+ }
+
+ function input($lines)
+ {
+ if ($lines) {
+ $this->_append($this->orig, $lines);
+ }
+ }
+
+ function out1($lines)
+ {
+ if ($lines) {
+ $this->_append($this->final1, $lines);
+ }
+ }
+
+ function out2($lines)
+ {
+ if ($lines) {
+ $this->_append($this->final2, $lines);
+ }
+ }
+
+ function isEmpty()
+ {
+ return !$this->orig && !$this->final1 && !$this->final2;
+ }
+
+ function finish()
+ {
+ if ($this->isEmpty()) {
+ return false;
+ } else {
+ $edit = new Text_Diff3_Op($this->orig, $this->final1, $this->final2);
+ $this->_init();
+ return $edit;
+ }
+ }
+
+ function _init()
+ {
+ $this->orig = $this->final1 = $this->final2 = array();
+ }
+
+ function _append(&$array, $lines)
+ {
+ array_splice($array, sizeof($array), 0, $lines);
+ }
+
+}
diff --git a/includes/pear/Text/Wiki.php b/includes/pear/Text/Wiki.php
new file mode 100644
index 0000000..27a9161
--- /dev/null
+++ b/includes/pear/Text/Wiki.php
@@ -0,0 +1,1550 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Parse structured wiki text and render into arbitrary formats such as XHTML.
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Wiki.php 248433 2007-12-17 16:03:48Z justinpatrin $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * The baseline abstract parser class.
+ */
+require_once 'Text/Wiki/Parse.php';
+
+/**
+ * The baseline abstract render class.
+ */
+require_once 'Text/Wiki/Render.php';
+
+/**
+ * Parse structured wiki text and render into arbitrary formats such as XHTML.
+ *
+ * This is the "master" class for handling the management and convenience
+ * functions to transform Wiki-formatted text.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: 1.2.1
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki {
+
+ /**
+ *
+ * The default list of rules, in order, to apply to the source text.
+ *
+ * @access public
+ *
+ * @var array
+ *
+ */
+
+ var $rules = array(
+ 'Prefilter',
+ 'Delimiter',
+ 'Code',
+ 'Function',
+ 'Html',
+ 'Raw',
+ 'Include',
+ 'Embed',
+ 'Anchor',
+ 'Heading',
+ 'Toc',
+ 'Horiz',
+ 'Break',
+ 'Blockquote',
+ 'List',
+ 'Deflist',
+ 'Table',
+ 'Image',
+ 'Phplookup',
+ 'Center',
+ 'Newline',
+ 'Paragraph',
+ 'Url',
+ 'Freelink',
+ 'Interwiki',
+ 'Wikilink',
+ 'Colortext',
+ 'Strong',
+ 'Bold',
+ 'Emphasis',
+ 'Italic',
+ 'Underline',
+ 'Tt',
+ 'Superscript',
+ 'Subscript',
+ 'Revise',
+ 'Tighten'
+ );
+
+
+ /**
+ *
+ * The list of rules to not-apply to the source text.
+ *
+ * @access public
+ *
+ * @var array
+ *
+ */
+
+ var $disable = array(
+ 'Html',
+ 'Include',
+ 'Embed'
+ );
+
+
+ /**
+ *
+ * Custom configuration for rules at the parsing stage.
+ *
+ * In this array, the key is the parsing rule name, and the value is
+ * an array of key-value configuration pairs corresponding to the $conf
+ * property in the target parsing rule.
+ *
+ * For example:
+ *
+ * <code>
+ * $parseConf = array(
+ * 'Include' => array(
+ * 'base' => '/path/to/scripts/'
+ * )
+ * );
+ * </code>
+ *
+ * Note that most default rules do not need any parsing configuration.
+ *
+ * @access public
+ *
+ * @var array
+ *
+ */
+
+ var $parseConf = array();
+
+
+ /**
+ *
+ * Custom configuration for rules at the rendering stage.
+ *
+ * Because rendering may be different for each target format, the
+ * first-level element in this array is always a format name (e.g.,
+ * 'Xhtml').
+ *
+ * Within that first level element, the subsequent elements match the
+ * $parseConf format. That is, the sub-key is the rendering rule name,
+ * and the sub-value is an array of key-value configuration pairs
+ * corresponding to the $conf property in the target rendering rule.
+ *
+ * @access public
+ *
+ * @var array
+ *
+ */
+
+ var $renderConf = array(
+ 'Docbook' => array(),
+ 'Latex' => array(),
+ 'Pdf' => array(),
+ 'Plain' => array(),
+ 'Rtf' => array(),
+ 'Xhtml' => array()
+ );
+
+
+ /**
+ *
+ * Custom configuration for the output format itself.
+ *
+ * Even though Text_Wiki will render the tokens from parsed text,
+ * the format itself may require some configuration. For example,
+ * RTF needs to know font names and sizes, PDF requires page layout
+ * information, and DocBook needs a section hierarchy. This array
+ * matches the $conf property of the the format-level renderer
+ * (e.g., Text_Wiki_Render_Xhtml).
+ *
+ * In this array, the key is the rendering format name, and the value is
+ * an array of key-value configuration pairs corresponding to the $conf
+ * property in the rendering format rule.
+ *
+ * @access public
+ *
+ * @var array
+ *
+ */
+
+ var $formatConf = array(
+ 'Docbook' => array(),
+ 'Latex' => array(),
+ 'Pdf' => array(),
+ 'Plain' => array(),
+ 'Rtf' => array(),
+ 'Xhtml' => array()
+ );
+
+
+ /**
+ *
+ * The delimiter for token numbers of parsed elements in source text.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ var $delim = "\31";
+
+
+ /**
+ *
+ * The tokens generated by rules as the source text is parsed.
+ *
+ * As Text_Wiki applies rule classes to the source text, it will
+ * replace portions of the text with a delimited token number. This
+ * is the array of those tokens, representing the replaced text and
+ * any options set by the parser for that replaced text.
+ *
+ * The tokens array is sequential; each element is itself a sequential
+ * array where element 0 is the name of the rule that generated the
+ * token, and element 1 is an associative array where the key is an
+ * option name and the value is an option value.
+ *
+ * @access private
+ *
+ * @var array
+ *
+ */
+
+ var $tokens = array();
+
+ /**
+ * How many tokens generated pro rules.
+ *
+ * Intended to load only necessary render objects
+ *
+ * @access private
+ * @var array
+ */
+ var $_countRulesTokens = array();
+
+
+ /**
+ *
+ * The source text to which rules will be applied.
+ *
+ * This text will be transformed in-place, which means that it will
+ * change as the rules are applied.
+ *
+ * @access private
+ *
+ * @var string
+ *
+ */
+
+ var $source = '';
+
+ /**
+ * The output text
+ *
+ * @var string
+ */
+ var $output = '';
+
+
+ /**
+ *
+ * Array of rule parsers.
+ *
+ * Text_Wiki creates one instance of every rule that is applied to
+ * the source text; this array holds those instances. The array key
+ * is the rule name, and the array value is an instance of the rule
+ * class.
+ *
+ * @access private
+ *
+ * @var array
+ *
+ */
+
+ var $parseObj = array();
+
+
+ /**
+ *
+ * Array of rule renderers.
+ *
+ * Text_Wiki creates one instance of every rule that is applied to
+ * the source text; this array holds those instances. The array key
+ * is the rule name, and the array value is an instance of the rule
+ * class.
+ *
+ * @access private
+ *
+ * @var array
+ *
+ */
+
+ var $renderObj = array();
+
+
+ /**
+ *
+ * Array of format renderers.
+ *
+ * @access private
+ *
+ * @var array
+ *
+ */
+
+ var $formatObj = array();
+
+
+ /**
+ *
+ * Array of paths to search, in order, for parsing and rendering rules.
+ *
+ * @access private
+ *
+ * @var array
+ *
+ */
+
+ var $path = array(
+ 'parse' => array(),
+ 'render' => array()
+ );
+
+
+
+ /**
+ *
+ * The directory separator character.
+ *
+ * @access private
+ *
+ * @var string
+ *
+ */
+
+ var $_dirSep = DIRECTORY_SEPARATOR;
+
+ /**
+ * Temporary configuration variable
+ *
+ * @var string
+ */
+ var $renderingType = 'normal';
+
+ /**
+ * Stack of rendering callbacks
+ *
+ * @var Array
+ */
+ var $_renderCallbacks = array();
+
+ /**
+ * Current output block
+ *
+ * @var string
+ */
+ var $_block;
+
+ /**
+ * A stack of blocks
+ *
+ * @param Array
+ */
+ var $_blocks;
+
+ /**
+ *
+ * Constructor.
+ *
+ * **DEPRECATED**
+ * Please use the singleton() or factory() methods.
+ *
+ * @access public
+ *
+ * @param array $rules The set of rules to load for this object. Defaults
+ * to null, which will load the default ruleset for this parser.
+ */
+
+ function Text_Wiki($rules = null)
+ {
+ if (is_array($rules)) {
+ $this->rules = array();
+ foreach ($rules as $rule) {
+ $this->rules[] = ucfirst($rule);
+ }
+ }
+
+ $this->addPath(
+ 'parse',
+ $this->fixPath(dirname(__FILE__)) . 'Wiki/Parse/Default/'
+ );
+ $this->addPath(
+ 'render',
+ $this->fixPath(dirname(__FILE__)) . 'Wiki/Render/'
+ );
+
+ }
+
+ /**
+ * Singleton.
+ *
+ * This avoids instantiating multiple Text_Wiki instances where a number
+ * of objects are required in one call, e.g. to save memory in a
+ * CMS invironment where several parsers are required in a single page.
+ *
+ * $single = & singleton();
+ *
+ * or
+ *
+ * $single = & singleton('Parser', array('Prefilter', 'Delimiter', 'Code', 'Function',
+ * 'Html', 'Raw', 'Include', 'Embed', 'Anchor', 'Heading', 'Toc', 'Horiz',
+ * 'Break', 'Blockquote', 'List', 'Deflist', 'Table', 'Image', 'Phplookup',
+ * 'Center', 'Newline', 'Paragraph', 'Url', 'Freelink', 'Interwiki', 'Wikilink',
+ * 'Colortext', 'Strong', 'Bold', 'Emphasis', 'Italic', 'Underline', 'Tt',
+ * 'Superscript', 'Subscript', 'Revise', 'Tighten'));
+ *
+ * Call using a subset of this list. The order of passing rulesets in the
+ * $rules array is important!
+ *
+ * After calling this, call $single->setParseConf(), setRenderConf() or setFormatConf()
+ * as usual for a constructed object of this class.
+ *
+ * The internal static array of singleton objects has no index on the parser
+ * rules, the only index is on the parser name. So if you call this multiple
+ * times with different rules but the same parser name, you will get the same
+ * static parser object each time.
+ *
+ * @access public
+ * @static
+ * @since Method available since Release 1.1.0
+ * @param string $parser The parser to be used (defaults to 'Default').
+ * @param array $rules The set of rules to instantiate the object. This
+ * will only be used when the first call to singleton is made, if included
+ * in further calls it will be effectively ignored.
+ * @return &object a reference to the Text_Wiki unique instantiation.
+ */
+ function &singleton($parser = 'Default', $rules = null)
+ {
+ static $only = array();
+ if (!isset($only[$parser])) {
+ $ret = & Text_Wiki::factory($parser, $rules);
+ if (Text_Wiki::isError($ret)) {
+ return $ret;
+ }
+ $only[$parser] =& $ret;
+ }
+ return $only[$parser];
+ }
+
+ /**
+ * Returns a Text_Wiki Parser class for the specified parser.
+ *
+ * @access public
+ * @static
+ * @param string $parser The name of the parse to instantiate
+ * you need to have Text_Wiki_XXX installed to use $parser = 'XXX', it's E_FATAL
+ * @param array $rules The rules to pass into the constructor
+ * {@see Text_Wiki::singleton} for a list of rules
+ * @return Text_Wiki a Parser object extended from Text_Wiki
+ */
+ function &factory($parser = 'Default', $rules = null)
+ {
+ $class = 'Text_Wiki_' . $parser;
+ $file = str_replace('_', '/', $class).'.php';
+ if (!class_exists($class)) {
+ require_once $file;
+ if (!class_exists($class)) {
+ return Text_Wiki::error(
+ 'Class ' . $class . ' does not exist after requiring '. $file .
+ ', install package ' . $class . "\n");
+ }
+ }
+
+ $obj =& new $class($rules);
+ return $obj;
+ }
+
+ /**
+ *
+ * Set parser configuration for a specific rule and key.
+ *
+ * @access public
+ *
+ * @param string $rule The parse rule to set config for.
+ *
+ * @param array|string $arg1 The full config array to use for the
+ * parse rule, or a conf key in that array.
+ *
+ * @param string $arg2 The config value for the key.
+ *
+ * @return void
+ *
+ */
+
+ function setParseConf($rule, $arg1, $arg2 = null)
+ {
+ $rule = ucwords(strtolower($rule));
+
+ if (! isset($this->parseConf[$rule])) {
+ $this->parseConf[$rule] = array();
+ }
+
+ // if first arg is an array, use it as the entire
+ // conf array for the rule. otherwise, treat arg1
+ // as a key and arg2 as a value for the rule conf.
+ if (is_array($arg1)) {
+ $this->parseConf[$rule] = $arg1;
+ } else {
+ $this->parseConf[$rule][$arg1] = $arg2;
+ }
+ }
+
+
+ /**
+ *
+ * Get parser configuration for a specific rule and key.
+ *
+ * @access public
+ *
+ * @param string $rule The parse rule to get config for.
+ *
+ * @param string $key A key in the conf array; if null,
+ * returns the entire conf array.
+ *
+ * @return mixed The whole conf array if no key is specified,
+ * or the specific conf key value.
+ *
+ */
+
+ function getParseConf($rule, $key = null)
+ {
+ $rule = ucwords(strtolower($rule));
+
+ // the rule does not exist
+ if (! isset($this->parseConf[$rule])) {
+ return null;
+ }
+
+ // no key requested, return the whole array
+ if (is_null($key)) {
+ return $this->parseConf[$rule];
+ }
+
+ // does the requested key exist?
+ if (isset($this->parseConf[$rule][$key])) {
+ // yes, return that value
+ return $this->parseConf[$rule][$key];
+ } else {
+ // no
+ return null;
+ }
+ }
+
+
+ /**
+ *
+ * Set renderer configuration for a specific format, rule, and key.
+ *
+ * @access public
+ *
+ * @param string $format The render format to set config for.
+ *
+ * @param string $rule The render rule to set config for in the format.
+ *
+ * @param array|string $arg1 The config array, or the config key
+ * within the render rule.
+ *
+ * @param string $arg2 The config value for the key.
+ *
+ * @return void
+ *
+ */
+
+ function setRenderConf($format, $rule, $arg1, $arg2 = null)
+ {
+ $format = ucwords(strtolower($format));
+ $rule = ucwords(strtolower($rule));
+
+ if (! isset($this->renderConf[$format])) {
+ $this->renderConf[$format] = array();
+ }
+
+ if (! isset($this->renderConf[$format][$rule])) {
+ $this->renderConf[$format][$rule] = array();
+ }
+
+ // if first arg is an array, use it as the entire
+ // conf array for the render rule. otherwise, treat arg1
+ // as a key and arg2 as a value for the render rule conf.
+ if (is_array($arg1)) {
+ $this->renderConf[$format][$rule] = $arg1;
+ } else {
+ $this->renderConf[$format][$rule][$arg1] = $arg2;
+ }
+ }
+
+
+ /**
+ *
+ * Get renderer configuration for a specific format, rule, and key.
+ *
+ * @access public
+ *
+ * @param string $format The render format to get config for.
+ *
+ * @param string $rule The render format rule to get config for.
+ *
+ * @param string $key A key in the conf array; if null,
+ * returns the entire conf array.
+ *
+ * @return mixed The whole conf array if no key is specified,
+ * or the specific conf key value.
+ *
+ */
+
+ function getRenderConf($format, $rule, $key = null)
+ {
+ $format = ucwords(strtolower($format));
+ $rule = ucwords(strtolower($rule));
+
+ if (! isset($this->renderConf[$format]) ||
+ ! isset($this->renderConf[$format][$rule])) {
+ return null;
+ }
+
+ // no key requested, return the whole array
+ if (is_null($key)) {
+ return $this->renderConf[$format][$rule];
+ }
+
+ // does the requested key exist?
+ if (isset($this->renderConf[$format][$rule][$key])) {
+ // yes, return that value
+ return $this->renderConf[$format][$rule][$key];
+ } else {
+ // no
+ return null;
+ }
+
+ }
+
+ /**
+ *
+ * Set format configuration for a specific rule and key.
+ *
+ * @access public
+ *
+ * @param string $format The format to set config for.
+ *
+ * @param string $key The config key within the format.
+ *
+ * @param string $val The config value for the key.
+ *
+ * @return void
+ *
+ */
+
+ function setFormatConf($format, $arg1, $arg2 = null)
+ {
+ if (! is_array($this->formatConf[$format])) {
+ $this->formatConf[$format] = array();
+ }
+
+ // if first arg is an array, use it as the entire
+ // conf array for the format. otherwise, treat arg1
+ // as a key and arg2 as a value for the format conf.
+ if (is_array($arg1)) {
+ $this->formatConf[$format] = $arg1;
+ } else {
+ $this->formatConf[$format][$arg1] = $arg2;
+ }
+ }
+
+
+
+ /**
+ *
+ * Get configuration for a specific format and key.
+ *
+ * @access public
+ *
+ * @param string $format The format to get config for.
+ *
+ * @param mixed $key A key in the conf array; if null,
+ * returns the entire conf array.
+ *
+ * @return mixed The whole conf array if no key is specified,
+ * or the specific conf key value.
+ *
+ */
+
+ function getFormatConf($format, $key = null)
+ {
+ // the format does not exist
+ if (! isset($this->formatConf[$format])) {
+ return null;
+ }
+
+ // no key requested, return the whole array
+ if (is_null($key)) {
+ return $this->formatConf[$format];
+ }
+
+ // does the requested key exist?
+ if (isset($this->formatConf[$format][$key])) {
+ // yes, return that value
+ return $this->formatConf[$format][$key];
+ } else {
+ // no
+ return null;
+ }
+ }
+
+
+ /**
+ *
+ * Inserts a rule into to the rule set.
+ *
+ * @access public
+ *
+ * @param string $name The name of the rule. Should be different from
+ * all other keys in the rule set.
+ *
+ * @param string $tgt The rule after which to insert this new rule. By
+ * default (null) the rule is inserted at the end; if set to '', inserts
+ * at the beginning.
+ *
+ * @return void
+ *
+ */
+
+ function insertRule($name, $tgt = null)
+ {
+ $name = ucwords(strtolower($name));
+ if (! is_null($tgt)) {
+ $tgt = ucwords(strtolower($tgt));
+ }
+
+ // does the rule name to be inserted already exist?
+ if (in_array($name, $this->rules)) {
+ // yes, return
+ return null;
+ }
+
+ // the target name is not null, and not '', but does not exist
+ // in the list of rules. this means we're trying to insert after
+ // a target key, but the target key isn't there.
+ if (! is_null($tgt) && $tgt != '' &&
+ ! in_array($tgt, $this->rules)) {
+ return false;
+ }
+
+ // if $tgt is null, insert at the end. We know this is at the
+ // end (instead of resetting an existing rule) becuase we exited
+ // at the top of this method if the rule was already in place.
+ if (is_null($tgt)) {
+ $this->rules[] = $name;
+ return true;
+ }
+
+ // save a copy of the current rules, then reset the rule set
+ // so we can insert in the proper place later.
+ // where to insert the rule?
+ if ($tgt == '') {
+ // insert at the beginning
+ array_unshift($this->rules, $name);
+ return true;
+ }
+
+ // insert after the named rule
+ $tmp = $this->rules;
+ $this->rules = array();
+
+ foreach ($tmp as $val) {
+ $this->rules[] = $val;
+ if ($val == $tgt) {
+ $this->rules[] = $name;
+ }
+ }
+
+ return true;
+
+ }
+
+
+ /**
+ *
+ * Delete (remove or unset) a rule from the $rules property.
+ *
+ * @access public
+ *
+ * @param string $rule The name of the rule to remove.
+ *
+ * @return void
+ *
+ */
+
+ function deleteRule($name)
+ {
+ $name = ucwords(strtolower($name));
+ $key = array_search($name, $this->rules);
+ if ($key !== false) {
+ unset($this->rules[$key]);
+ }
+ }
+
+
+ /**
+ *
+ * Change from one rule to another in-place.
+ *
+ * @access public
+ *
+ * @param string $old The name of the rule to change from.
+ *
+ * @param string $new The name of the rule to change to.
+ *
+ * @return void
+ *
+ */
+
+ function changeRule($old, $new)
+ {
+ $old = ucwords(strtolower($old));
+ $new = ucwords(strtolower($new));
+ $key = array_search($old, $this->rules);
+ if ($key !== false) {
+ // delete the new name , case it was already there
+ $this->deleteRule($new);
+ $this->rules[$key] = $new;
+ }
+ }
+
+
+ /**
+ *
+ * Enables a rule so that it is applied when parsing.
+ *
+ * @access public
+ *
+ * @param string $rule The name of the rule to enable.
+ *
+ * @return void
+ *
+ */
+
+ function enableRule($name)
+ {
+ $name = ucwords(strtolower($name));
+ $key = array_search($name, $this->disable);
+ if ($key !== false) {
+ unset($this->disable[$key]);
+ }
+ }
+
+
+ /**
+ *
+ * Disables a rule so that it is not applied when parsing.
+ *
+ * @access public
+ *
+ * @param string $rule The name of the rule to disable.
+ *
+ * @return void
+ *
+ */
+
+ function disableRule($name)
+ {
+ $name = ucwords(strtolower($name));
+ $key = array_search($name, $this->disable);
+ if ($key === false) {
+ $this->disable[] = $name;
+ }
+ }
+
+
+ /**
+ *
+ * Parses and renders the text passed to it, and returns the results.
+ *
+ * First, the method parses the source text, applying rules to the
+ * text as it goes. These rules will modify the source text
+ * in-place, replacing some text with delimited tokens (and
+ * populating the $this->tokens array as it goes).
+ *
+ * Next, the method renders the in-place tokens into the requested
+ * output format.
+ *
+ * Finally, the method returns the transformed text. Note that the
+ * source text is transformed in place; once it is transformed, it is
+ * no longer the same as the original source text.
+ *
+ * @access public
+ *
+ * @param string $text The source text to which wiki rules should be
+ * applied, both for parsing and for rendering.
+ *
+ * @param string $format The target output format, typically 'xhtml'.
+ * If a rule does not support a given format, the output from that
+ * rule is rule-specific.
+ *
+ * @return string The transformed wiki text.
+ *
+ */
+
+ function transform($text, $format = 'Xhtml')
+ {
+ $this->parse($text);
+ return $this->render($format);
+ }
+
+
+ /**
+ *
+ * Sets the $_source text property, then parses it in place and
+ * retains tokens in the $_tokens array property.
+ *
+ * @access public
+ *
+ * @param string $text The source text to which wiki rules should be
+ * applied, both for parsing and for rendering.
+ *
+ * @return void
+ *
+ */
+
+ function parse($text)
+ {
+ // set the object property for the source text
+ $this->source = $text;
+
+ // reset the tokens.
+ $this->tokens = array();
+ $this->_countRulesTokens = array();
+
+ // apply the parse() method of each requested rule to the source
+ // text.
+ foreach ($this->rules as $name) {
+ // do not parse the rules listed in $disable
+ if (! in_array($name, $this->disable)) {
+
+ // load the parsing object
+ $this->loadParseObj($name);
+
+ // load may have failed; only parse if
+ // an object is in the array now
+ if (is_object($this->parseObj[$name])) {
+ $this->parseObj[$name]->parse();
+ }
+ }
+ }
+ }
+
+
+ /**
+ *
+ * Renders tokens back into the source text, based on the requested format.
+ *
+ * @access public
+ *
+ * @param string $format The target output format, typically 'xhtml'.
+ * If a rule does not support a given format, the output from that
+ * rule is rule-specific.
+ *
+ * @return string The transformed wiki text.
+ *
+ */
+
+ function render($format = 'Xhtml')
+ {
+ // the rendering method we're going to use from each rule
+ $format = ucwords(strtolower($format));
+
+ // the eventual output text
+ $this->output = '';
+
+ // when passing through the parsed source text, keep track of when
+ // we are in a delimited section
+ $in_delim = false;
+
+ // when in a delimited section, capture the token key number
+ $key = '';
+
+ // load the format object, or crap out if we can't find it
+ $result = $this->loadFormatObj($format);
+ if ($this->isError($result)) {
+ return $result;
+ }
+
+ // pre-rendering activity
+ if (is_object($this->formatObj[$format])) {
+ $this->output .= $this->formatObj[$format]->pre();
+ }
+
+ // load the render objects
+ foreach (array_keys($this->_countRulesTokens) as $rule) {
+ $this->loadRenderObj($format, $rule);
+ }
+
+ if ($this->renderingType == 'preg') {
+ $this->output = preg_replace_callback('/'.$this->delim.'(\d+)'.$this->delim.'/',
+ array(&$this, '_renderToken'),
+ $this->source);
+ /*
+//Damn strtok()! Why does it "skip" empty parts of the string. It's useless now!
+ } elseif ($this->renderingType == 'strtok') {
+ echo '<pre>'.htmlentities($this->source).'</pre>';
+ $t = strtok($this->source, $this->delim);
+ $inToken = true;
+ $i = 0;
+ while ($t !== false) {
+ echo 'Token: '.$i.'<pre>"'.htmlentities($t).'"</pre><br/><br/>';
+ if ($inToken) {
+ //$this->output .= $this->renderObj[$this->tokens[$t][0]]->token($this->tokens[$t][1]);
+ } else {
+ $this->output .= $t;
+ }
+ $inToken = !$inToken;
+ $t = strtok($this->delim);
+ ++$i;
+ }
+ */
+ } else {
+ // pass through the parsed source text character by character
+ $this->_block = '';
+ $tokenStack = array();
+ $k = strlen($this->source);
+ for ($i = 0; $i < $k; $i++) {
+
+ // the current character
+ $char = $this->source{$i};
+
+ // are alredy in a delimited section?
+ if ($in_delim) {
+
+ // yes; are we ending the section?
+ if ($char == $this->delim) {
+
+ if (count($this->_renderCallbacks) == 0) {
+ $this->output .= $this->_block;
+ $this->_block = '';
+ }
+ if (isset($opts['type'])) {
+ if ($opts['type'] == 'start') {
+ array_push($tokenStack, $rule);
+ } elseif ($opts['type'] == 'end') {
+ if ($tokenStack[count($tokenStack) - 1] != $rule) {
+ return Text_Wiki::error('Unbalanced tokens, check your syntax');
+ } else {
+ array_pop($tokenStack);
+ }
+ }
+ }
+
+ // yes, get the replacement text for the delimited
+ // token number and unset the flag.
+ $key = (int)$key;
+ $rule = $this->tokens[$key][0];
+ $opts = $this->tokens[$key][1];
+ $this->_block .= $this->renderObj[$rule]->token($opts);
+ $in_delim = false;
+
+ } else {
+
+ // no, add to the delimited token key number
+ $key .= $char;
+
+ }
+
+ } else {
+
+ // not currently in a delimited section.
+ // are we starting into a delimited section?
+ if ($char == $this->delim) {
+ // yes, reset the previous key and
+ // set the flag.
+ $key = '';
+ $in_delim = true;
+
+ } else {
+ // no, add to the output as-is
+ $this->_block .= $char;
+ }
+ }
+ }
+ }
+
+ if (count($this->_renderCallbacks)) {
+ return $this->error('Render callbacks left over after processing finished');
+ }
+ /*
+ while (count($this->_renderCallbacks)) {
+ $this->popRenderCallback();
+ }
+ */
+ if (strlen($this->_block)) {
+ $this->output .= $this->_block;
+ $this->_block = '';
+ }
+
+ // post-rendering activity
+ if (is_object($this->formatObj[$format])) {
+ $this->output .= $this->formatObj[$format]->post();
+ }
+
+ // return the rendered source text.
+ return $this->output;
+ }
+
+ /**
+ * Renders a token, for use only as an internal callback
+ *
+ * @param array Matches from preg_rpelace_callback, [1] is the token number
+ * @return string The rendered text for the token
+ * @access private
+ */
+ function _renderToken($matches) {
+ return $this->renderObj[$this->tokens[$matches[1]][0]]->token($this->tokens[$matches[1]][1]);
+ }
+
+ function registerRenderCallback($callback) {
+ $this->_blocks[] = $this->_block;
+ $this->_block = '';
+ $this->_renderCallbacks[] = $callback;
+ }
+
+ function popRenderCallback() {
+ if (count($this->_renderCallbacks) == 0) {
+ return Text_Wiki::error('Render callback popped when no render callbacks in stack');
+ } else {
+ $callback = array_pop($this->_renderCallbacks);
+ $this->_block = call_user_func($callback, $this->_block);
+ if (count($this->_blocks)) {
+ $parentBlock = array_pop($this->_blocks);
+ $this->_block = $parentBlock.$this->_block;
+ }
+ if (count($this->_renderCallbacks) == 0) {
+ $this->output .= $this->_block;
+ $this->_block = '';
+ }
+ }
+ }
+
+ /**
+ *
+ * Returns the parsed source text with delimited token placeholders.
+ *
+ * @access public
+ *
+ * @return string The parsed source text.
+ *
+ */
+
+ function getSource()
+ {
+ return $this->source;
+ }
+
+
+ /**
+ *
+ * Returns tokens that have been parsed out of the source text.
+ *
+ * @access public
+ *
+ * @param array $rules If an array of rule names is passed, only return
+ * tokens matching these rule names. If no array is passed, return all
+ * tokens.
+ *
+ * @return array An array of tokens.
+ *
+ */
+
+ function getTokens($rules = null)
+ {
+ if (is_null($rules)) {
+ return $this->tokens;
+ } else {
+ settype($rules, 'array');
+ $result = array();
+ foreach ($this->tokens as $key => $val) {
+ if (in_array($val[0], $rules)) {
+ $result[$key] = $val;
+ }
+ }
+ return $result;
+ }
+ }
+
+
+ /**
+ *
+ * Add a token to the Text_Wiki tokens array, and return a delimited
+ * token number.
+ *
+ * @access public
+ *
+ * @param array $options An associative array of options for the new
+ * token array element. The keys and values are specific to the
+ * rule, and may or may not be common to other rule options. Typical
+ * options keys are 'text' and 'type' but may include others.
+ *
+ * @param boolean $id_only If true, return only the token number, not
+ * a delimited token string.
+ *
+ * @return string|int By default, return the number of the
+ * newly-created token array element with a delimiter prefix and
+ * suffix; however, if $id_only is set to true, return only the token
+ * number (no delimiters).
+ *
+ */
+
+ function addToken($rule, $options = array(), $id_only = false)
+ {
+ // increment the token ID number. note that if you parse
+ // multiple times with the same Text_Wiki object, the ID number
+ // will not reset to zero.
+ static $id;
+ if (! isset($id)) {
+ $id = 0;
+ } else {
+ $id ++;
+ }
+
+ // force the options to be an array
+ settype($options, 'array');
+
+ // add the token
+ $this->tokens[$id] = array(
+ 0 => $rule,
+ 1 => $options
+ );
+ if (!isset($this->_countRulesTokens[$rule])) {
+ $this->_countRulesTokens[$rule] = 1;
+ } else {
+ ++$this->_countRulesTokens[$rule];
+ }
+
+ // return a value
+ if ($id_only) {
+ // return the last token number
+ return $id;
+ } else {
+ // return the token number with delimiters
+ return $this->delim . $id . $this->delim;
+ }
+ }
+
+
+ /**
+ *
+ * Set or re-set a token with specific information, overwriting any
+ * previous rule name and rule options.
+ *
+ * @access public
+ *
+ * @param int $id The token number to reset.
+ *
+ * @param int $rule The rule name to use.
+ *
+ * @param array $options An associative array of options for the
+ * token array element. The keys and values are specific to the
+ * rule, and may or may not be common to other rule options. Typical
+ * options keys are 'text' and 'type' but may include others.
+ *
+ * @return void
+ *
+ */
+
+ function setToken($id, $rule, $options = array())
+ {
+ $oldRule = $this->tokens[$id][0];
+ // reset the token
+ $this->tokens[$id] = array(
+ 0 => $rule,
+ 1 => $options
+ );
+ if ($rule != $oldRule) {
+ if (!($this->_countRulesTokens[$oldRule]--)) {
+ unset($this->_countRulesTokens[$oldRule]);
+ }
+ if (!isset($this->_countRulesTokens[$rule])) {
+ $this->_countRulesTokens[$rule] = 1;
+ } else {
+ ++$this->_countRulesTokens[$rule];
+ }
+ }
+ }
+
+
+ /**
+ *
+ * Load a rule parser class file.
+ *
+ * @access public
+ *
+ * @return bool True if loaded, false if not.
+ *
+ */
+
+ function loadParseObj($rule)
+ {
+ $rule = ucwords(strtolower($rule));
+ $file = $rule . '.php';
+ $class = "Text_Wiki_Parse_$rule";
+
+ if (! class_exists($class)) {
+ $loc = $this->findFile('parse', $file);
+ if ($loc) {
+ // found the class
+ include_once $loc;
+ } else {
+ // can't find the class
+ $this->parseObj[$rule] = null;
+ // can't find the class
+ return $this->error(
+ "Parse rule '$rule' not found"
+ );
+ }
+ }
+
+ $this->parseObj[$rule] =& new $class($this);
+
+ }
+
+
+ /**
+ *
+ * Load a rule-render class file.
+ *
+ * @access public
+ *
+ * @return bool True if loaded, false if not.
+ *
+ */
+
+ function loadRenderObj($format, $rule)
+ {
+ $format = ucwords(strtolower($format));
+ $rule = ucwords(strtolower($rule));
+ $file = "$format/$rule.php";
+ $class = "Text_Wiki_Render_$format" . "_$rule";
+
+ if (! class_exists($class)) {
+ // load the class
+ $loc = $this->findFile('render', $file);
+ if ($loc) {
+ // found the class
+ include_once $loc;
+ } else {
+ // can't find the class
+ return $this->error(
+ "Render rule '$rule' in format '$format' not found"
+ );
+ }
+ }
+
+ $this->renderObj[$rule] =& new $class($this);
+ }
+
+
+ /**
+ *
+ * Load a format-render class file.
+ *
+ * @access public
+ *
+ * @return bool True if loaded, false if not.
+ *
+ */
+
+ function loadFormatObj($format)
+ {
+ $format = ucwords(strtolower($format));
+ $file = $format . '.php';
+ $class = "Text_Wiki_Render_$format";
+
+ if (! class_exists($class)) {
+ $loc = $this->findFile('render', $file);
+ if ($loc) {
+ // found the class
+ include_once $loc;
+ } else {
+ // can't find the class
+ return $this->error(
+ "Rendering format class '$class' not found"
+ );
+ }
+ }
+
+ $this->formatObj[$format] =& new $class($this);
+ }
+
+
+ /**
+ *
+ * Add a path to a path array.
+ *
+ * @access public
+ *
+ * @param string $type The path-type to add (parse or render).
+ *
+ * @param string $dir The directory to add to the path-type.
+ *
+ * @return void
+ *
+ */
+
+ function addPath($type, $dir)
+ {
+ $dir = $this->fixPath($dir);
+ if (! isset($this->path[$type])) {
+ $this->path[$type] = array($dir);
+ } else {
+ array_unshift($this->path[$type], $dir);
+ }
+ }
+
+
+ /**
+ *
+ * Get the current path array for a path-type.
+ *
+ * @access public
+ *
+ * @param string $type The path-type to look up (plugin, filter, or
+ * template). If not set, returns all path types.
+ *
+ * @return array The array of paths for the requested type.
+ *
+ */
+
+ function getPath($type = null)
+ {
+ if (is_null($type)) {
+ return $this->path;
+ } elseif (! isset($this->path[$type])) {
+ return array();
+ } else {
+ return $this->path[$type];
+ }
+ }
+
+
+ /**
+ *
+ * Searches a series of paths for a given file.
+ *
+ * @param array $type The type of paths to search (template, plugin,
+ * or filter).
+ *
+ * @param string $file The file name to look for.
+ *
+ * @return string|bool The full path and file name for the target file,
+ * or boolean false if the file is not found in any of the paths.
+ *
+ */
+
+ function findFile($type, $file)
+ {
+ // get the set of paths
+ $set = $this->getPath($type);
+
+ // start looping through them
+ foreach ($set as $path) {
+ $fullname = $path . $file;
+ if (file_exists($fullname) && is_readable($fullname)) {
+ return $fullname;
+ }
+ }
+
+ // could not find the file in the set of paths
+ return false;
+ }
+
+
+ /**
+ *
+ * Append a trailing '/' to paths, unless the path is empty.
+ *
+ * @access private
+ *
+ * @param string $path The file path to fix
+ *
+ * @return string The fixed file path
+ *
+ */
+
+ function fixPath($path)
+ {
+ $len = strlen($this->_dirSep);
+
+ if (! empty($path) &&
+ substr($path, -1 * $len, $len) != $this->_dirSep) {
+ return $path . $this->_dirSep;
+ } else {
+ return $path;
+ }
+ }
+
+
+ /**
+ *
+ * Simple error-object generator.
+ *
+ * @access public
+ *
+ * @param string $message The error message.
+ *
+ * @return object PEAR_Error
+ *
+ */
+
+ function &error($message)
+ {
+ if (! class_exists('PEAR_Error')) {
+ include_once 'PEAR.php';
+ }
+ return PEAR::throwError($message);
+ }
+
+
+ /**
+ *
+ * Simple error checker.
+ *
+ * @access public
+ *
+ * @param mixed $obj Check if this is a PEAR_Error object or not.
+ *
+ * @return bool True if a PEAR_Error, false if not.
+ *
+ */
+
+ function isError(&$obj)
+ {
+ return is_a($obj, 'PEAR_Error');
+ }
+}
+
+?>
diff --git a/includes/pear/Text/Wiki/Creole.php b/includes/pear/Text/Wiki/Creole.php
new file mode 100644
index 0000000..255eaac
--- /dev/null
+++ b/includes/pear/Text/Wiki/Creole.php
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ *
+ * Parse structured wiki text and render into arbitrary formats such as XHTML.
+ * This is the Text_Wiki extension for Mediawiki markup
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ *
+ * @link http://pear.php.net/package/Text_Wiki
+ *
+ * @version CVS: $Id: Creole.php 274202 2009-01-22 13:28:32Z mic $
+ *
+ */
+
+/**
+ *
+ * "Master" class for handling the management and convenience
+ *
+ */
+
+require_once 'Text/Wiki.php';
+
+/**
+ *
+ * Base Text_Wiki handler class extension for Creole markup
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ *
+ * @link http://pear.php.net/package/Text_Wiki
+ *
+ * @see Text_Wiki::Text_Wiki()
+ *
+ */
+
+class Text_Wiki_Creole extends Text_Wiki {
+
+ // *single newlines* are handled as in most wikis (ignored)
+ // if Newline is removed from rules, they will be handled as in word-processors (meaning a paragraph break)
+
+ var $rules = array(
+ 'Prefilter',
+ 'Delimiter',
+ 'Preformatted',
+ 'Tt',
+ 'Trim',
+ 'Break',
+ 'Raw',
+ 'Box',
+ 'Footnote',
+ 'Heading',
+ 'Newline',
+ 'Deflist',
+ 'Blockquote',
+ 'Newline',
+ 'Url',
+ 'Wikilink',
+ 'Image',
+ //'Heading',
+ 'Table',
+ 'Center',
+ 'Horiz',
+ 'Deflist',
+ 'List',
+ 'Address',
+ 'Paragraph',
+ 'Superscript',
+ 'Subscript',
+ 'Underline',
+ 'Emphasis',
+ 'Strong',
+ //'Italic',
+ //'Bold',
+ 'Tighten'
+ );
+
+ /**
+ * Constructor: just adds the path to Creole rules
+ *
+ * @access public
+ * @param array $rules The set of rules to load for this object.
+ */
+
+ function Text_Wiki_Creole($rules = null) {
+ parent::Text_Wiki($rules);
+ $this->addPath('parse', $this->fixPath(dirname(__FILE__)).'Parse/Creole');
+ $this->renderingType = 'char';
+ $this->setRenderConf('xhtml', 'center', 'css', 'center');
+ $this->setRenderConf('xhtml', 'url', 'target', null);
+ }
+
+ function checkInnerTags(&$text) {
+ $started = array();
+ $i = false;
+ while (($i = strpos($text, $this->delim, $i)) !== false) {
+ $j = strpos($text, $this->delim, $i + 1);
+ $t = substr($text, $i + 1, $j - $i - 1);
+ $i = $j + 1;
+ $rule = strtolower($this->tokens[$t][0]);
+ $type = $this->tokens[$t][1]['type'];
+
+ if ($type == 'start') {
+ if (empty($started[$rule])) {
+ $started[$rule] = 0;
+ }
+ $started[$rule] += 1;
+ }
+ else if ($type == 'end') {
+ if (empty($started[$rule])) return false;
+
+ $started[$rule] -= 1;
+ if (! $started[$rule]) unset($started[$rule]);
+ }
+ }
+ return ! (count($started) > 0);
+ }
+
+ function restoreRaw($text) {
+ $i = false;
+ while (($i = strpos($text, $this->delim, $i)) !== false) {
+ $j = strpos($text, $this->delim, $i + 1);
+ $t = substr($text, $i + 1, $j - $i - 1);
+ $rule = strtolower($this->tokens[$t][0]);
+
+ if ($rule == 'raw') {
+ $text = str_replace($this->delim. $t. $this->delim, $this->tokens[$t][1]['text'], $text);
+ }
+ else {
+ $i = $j + 1;
+ }
+ }
+ return $text;
+ }
+}
+
+?>
diff --git a/includes/pear/Text/Wiki/Default.php b/includes/pear/Text/Wiki/Default.php
new file mode 100644
index 0000000..379cf12
--- /dev/null
+++ b/includes/pear/Text/Wiki/Default.php
@@ -0,0 +1,27 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Parse structured wiki text and render into arbitrary formats such as XHTML.
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Justin Patrin <justinpatrin@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Default.php 208363 2006-03-01 16:58:17Z justinpatrin $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+require_once('Text/Wiki.php');
+
+/**
+ * This is the parser for the Default ruleset. For now, this simply extends Text_Wiki.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @version Release: @package_version@
+ * @author Justin Patrin <justinpatrin@php.net>
+ */
+class Text_Wiki_Default extends Text_Wiki {
+}
diff --git a/includes/pear/Text/Wiki/Parse.php b/includes/pear/Text/Wiki/Parse.php
new file mode 100644
index 0000000..1b14c89
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse.php
@@ -0,0 +1,264 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Baseline rule class for extension into a "real" parser component.
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Parse.php 191781 2005-07-29 08:57:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * Baseline rule class for extension into a "real" parser component.
+ *
+ * Text_Wiki_Rule classes do not stand on their own; they are called by a
+ * Text_Wiki object, typcially in the transform() method. Each rule class
+ * performs three main activities: parse, process, and render.
+ *
+ * The parse() method takes a regex and applies it to the whole block of
+ * source text at one time. Each match is sent as $matches to the
+ * process() method.
+ *
+ * The process() method acts on the matched text from the source, and
+ * then processes the source text is some way. This may mean the
+ * creation of a delimited token using addToken(). In every case, the
+ * process() method returns the text that should replace the matched text
+ * from parse().
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Parse {
+
+
+ /**
+ *
+ * Configuration options for this parser rule.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ var $conf = array();
+
+
+ /**
+ *
+ * Regular expression to find matching text for this rule.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = null;
+
+
+ /**
+ *
+ * The name of this rule for new token array elements.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ var $rule = null;
+
+
+ /**
+ *
+ * A reference to the calling Text_Wiki object.
+ *
+ * This is needed so that each rule has access to the same source
+ * text, token set, URLs, interwiki maps, page names, etc.
+ *
+ * @access public
+ *
+ * @var object
+ */
+
+ var $wiki = null;
+
+
+ /**
+ *
+ * Constructor for this parser rule.
+ *
+ * @access public
+ *
+ * @param object &$obj The calling "parent" Text_Wiki object.
+ *
+ */
+
+ function Text_Wiki_Parse(&$obj)
+ {
+ // set the reference to the calling Text_Wiki object;
+ // this allows us access to the shared source text, token
+ // array, etc.
+ $this->wiki =& $obj;
+
+ // set the name of this rule; generally used when adding
+ // to the tokens array. strip off the Text_Wiki_Parse_ portion.
+ // text_wiki_parse_
+ // 0123456789012345
+ $tmp = substr(get_class($this), 16);
+ $this->rule = ucwords(strtolower($tmp));
+
+ // override config options for the rule if specified
+ if (isset($this->wiki->parseConf[$this->rule]) &&
+ is_array($this->wiki->parseConf[$this->rule])) {
+
+ $this->conf = array_merge(
+ $this->conf,
+ $this->wiki->parseConf[$this->rule]
+ );
+
+ }
+ }
+
+
+ /**
+ *
+ * Abstrct method to parse source text for matches.
+ *
+ * Applies the rule's regular expression to the source text, passes
+ * every match to the process() method, and replaces the matched text
+ * with the results of the processing.
+ *
+ * @access public
+ *
+ * @see Text_Wiki_Parse::process()
+ *
+ */
+
+ function parse()
+ {
+ $this->wiki->source = preg_replace_callback(
+ $this->regex,
+ array(&$this, 'process'),
+ $this->wiki->source
+ );
+ }
+
+
+ /**
+ *
+ * Abstract method to generate replacements for matched text.
+ *
+ * @access public
+ *
+ * @param array $matches An array of matches from the parse() method
+ * as generated by preg_replace_callback. $matches[0] is the full
+ * matched string, $matches[1] is the first matched pattern,
+ * $matches[2] is the second matched pattern, and so on.
+ *
+ * @return string The processed text replacement; defaults to the
+ * full matched string (i.e., no changes to the text).
+ *
+ * @see Text_Wiki_Parse::parse()
+ *
+ */
+
+ function process(&$matches)
+ {
+ return $matches[0];
+ }
+
+
+ /**
+ *
+ * Simple method to safely get configuration key values.
+ *
+ * @access public
+ *
+ * @param string $key The configuration key.
+ *
+ * @param mixed $default If the key does not exist, return this value
+ * instead.
+ *
+ * @return mixed The configuration key value (if it exists) or the
+ * default value (if not).
+ *
+ */
+
+ function getConf($key, $default = null)
+ {
+ if (isset($this->conf[$key])) {
+ return $this->conf[$key];
+ } else {
+ return $default;
+ }
+ }
+
+
+ /**
+ *
+ * Extract 'attribute="value"' portions of wiki markup.
+ *
+ * This kind of markup is typically used only in macros, but is useful
+ * anywhere.
+ *
+ * The syntax is pretty strict; there can be no spaces between the
+ * option name, the equals, and the first double-quote; the value
+ * must be surrounded by double-quotes. You can escape characters in
+ * the value with a backslash, and the backslash will be stripped for
+ * you.
+ *
+ * @access public
+ *
+ * @param string $text The "attributes" portion of markup.
+ *
+ * @return array An associative array of key-value pairs where the
+ * key is the option name and the value is the option value.
+ *
+ */
+
+ function getAttrs($text)
+ {
+ // find the =" sections;
+ $tmp = explode('="', trim($text));
+
+ // basic setup
+ $k = count($tmp) - 1;
+ $attrs = array();
+ $key = null;
+
+ // loop through the sections
+ foreach ($tmp as $i => $val) {
+
+ // first element is always the first key
+ if ($i == 0) {
+ $key = trim($val);
+ continue;
+ }
+
+ // find the last double-quote in the value.
+ // the part to the left is the value for the last key,
+ // the part to the right is the next key name
+ $pos = strrpos($val, '"');
+ $attrs[$key] = stripslashes(substr($val, 0, $pos));
+ $key = trim(substr($val, $pos+1));
+
+ }
+
+ return $attrs;
+
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Address.php b/includes/pear/Text/Wiki/Parse/Creole/Address.php
new file mode 100644
index 0000000..5e1eca1
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Address.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ *
+ * Parses for signatures.
+ * This class implements a Text_Wiki rule to find sections of the source
+ * text that are signatures. A signature is any line starting with exactly
+ * two - signs.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Address.php 222265 2006-10-23 13:11:27Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_Address extends Text_Wiki_Parse {
+
+ /**
+ *
+ * The regular expression used to find source text matching this
+ * rule.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ var $regex = '/^--([^-].*)$/m';
+
+ /**
+ *
+ * Generates a token entry for the matched text. Token options are:
+ *
+ * 'start' => The starting point of the signature.
+ *
+ * 'end' => The ending point of the signature.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token number to be used as a placeholder in
+ * the source text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $start = $this->wiki->addToken(
+ $this->rule, array('type' => 'start')
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule, array('type' => 'end')
+ );
+
+ return "\n" . $start . trim($matches[1]) . $end;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Blockquote.php b/includes/pear/Text/Wiki/Parse/Creole/Blockquote.php
new file mode 100644
index 0000000..2ab6c14
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Blockquote.php
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ *
+ * Parse for block-quoted text.
+ *
+ * Find source text marked as a blockquote, identified by any number of
+ * greater-than signs '>' at the start of the line, followed by an
+ * optional space, and then the quote text; each '>' indicates an
+ * additional level of quoting.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Paul M. Jones <pmjones@php.net>
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Blockquote.php 230214 2007-02-19 08:57:14Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_Blockquote extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * Regex for parsing the source text.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/\n(([>:]).*\n)(?!([>:]))/Us';
+
+
+ /**
+ *
+ * Generates a replacement for the matched text.
+ *
+ * Token options are:
+ *
+ * 'type' =>
+ * 'start' : the start of a blockquote
+ * 'end' : the end of a blockquote
+ *
+ * 'level' => the indent level (0 for the first level, 1 for the
+ * second, etc)
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A series of text and delimited tokens marking the different
+ * list text and list elements.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // the replacement text we will return to parse()
+ $return = '';
+
+ // the list of post-processing matches
+ $list = array();
+
+ // $matches[1] is the text matched as a list set by parse();
+ // create an array called $list that contains a new set of
+ // matches for the various list-item elements.
+ preg_match_all(
+ '=^([>:]+)(.*?\n)=ms',
+ $matches[1],
+ $list,
+ PREG_SET_ORDER
+ );
+
+ // a stack of starts and ends; we keep this so that we know what
+ // indent level we're at.
+ $stack = array();
+
+ // loop through each list-item element.
+ foreach ($list as $key => $val) {
+
+ // $val[0] is the full matched list-item line
+ // $val[1] is the number of initial '>' chars (indent level)
+ // $val[2] is the quote text
+
+ // we number levels starting at 1, not zero
+ $level = strlen($val[1]);
+
+ // get the text of the line
+ $text = trim($val[2]);
+
+ // add a level to the list?
+ while ($level > count($stack)) {
+
+ $css = ($val[1][count($stack)] == ':') ? 'remark' : '';
+
+ // the current indent level is greater than the number
+ // of stack elements, so we must be starting a new
+ // level. push the new level onto the stack with a
+ // dummy value (boolean true)...
+ array_push($stack, true);
+
+ $return .= "\n\n";
+
+ // ...and add a start token to the return.
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'start',
+ 'level' => $level - 1,
+ 'css' => $css
+ )
+ );
+
+ $return .= "\n\n";
+ }
+
+ // remove a level?
+ while (count($stack) > $level) {
+
+ // as long as the stack count is greater than the
+ // current indent level, we need to end list types.
+ // continue adding end-list tokens until the stack count
+ // and the indent level are the same.
+ array_pop($stack);
+
+ $return .= "\n\n";
+
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array (
+ 'type' => 'end',
+ 'level' => count($stack)
+ )
+ );
+
+ $return .= "\n\n";
+ }
+
+ // add the line text.
+ $return .= $text . "\n";
+ }
+
+ // the last line may have been indented. go through the stack
+ // and create end-tokens until the stack is empty.
+ $return .= "\n\n";
+
+ while (count($stack) > 0) {
+ array_pop($stack);
+
+ $return .= "\n\n";
+
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array (
+ 'type' => 'end',
+ 'level' => count($stack)
+ )
+ );
+
+ $return .= "\n\n";
+ }
+
+ // we're done! send back the replacement text.
+ return "\n\n$return\n\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Box.php b/includes/pear/Text/Wiki/Parse/Creole/Box.php
new file mode 100644
index 0000000..4660a3d
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Box.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+*
+* Parses for bold text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Justin Patrin <papercrane@reversefold.com>
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Box.php 240481 2007-07-30 19:11:32Z mic $
+*
+*/
+
+/**
+*
+* Parses for bold text.
+*
+* This class implements a Text_Wiki_Rule to find source text marked for
+* strong emphasis (bold) as defined by text surrounded by three
+* single-quotes. On parsing, the text itself is left in place, but the
+* starting and ending instances of three single-quotes are replaced with
+* tokens.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Justin Patrin <papercrane@reversefold.com>
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Box extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/\n\[\d+\].*/s';
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * emphasized text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A pair of delimited tokens to be used as a placeholder in
+ * the source text surrounding the text to be emphasized.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $start = $this->wiki->addToken($this->rule, array('type' => 'start', 'css' => 'footnotes'));
+ $end = $this->wiki->addToken($this->rule, array('type' => 'end'));
+ return $start . $matches[0] . "\n" . $end . "\n\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Break.php b/includes/pear/Text/Wiki/Parse/Creole/Break.php
new file mode 100644
index 0000000..47d12d9
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Break.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+*
+* Parses for explicit line breaks.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Break.php 230561 2007-02-23 14:19:19Z mic $
+*
+*/
+
+/**
+*
+* Parses for explicit line breaks.
+*
+* This class implements a Text_Wiki_Parse to mark forced line breaks in the
+* source text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Break extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ //var $regex = "/[ \n]*([\\\][\\\]|\%\%\%)[ \n]*/";
+ var $regex = "/ *([\\\][\\\]|\%\%\%)\n?/";
+
+
+ /**
+ *
+ * Generates a replacement token for the matched text.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A delimited token to be used as a placeholder in
+ * the source text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ return $this->wiki->addToken($this->rule);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Center.php b/includes/pear/Text/Wiki/Parse/Creole/Center.php
new file mode 100644
index 0000000..3753aca
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Center.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ *
+ * Parses for centered text.
+ *
+ * This class implements a Text_Wiki_Parse to find source text marked to
+ * be a center element, as defined by text on a line by itself prefixed
+ * with an exclamation mark (!).
+ * The centered text itself is left in the source, but is prefixed and
+ * suffixed with delimited tokens marking its start and end.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Tomaiuolo Michele <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Center.php 240476 2007-07-30 14:22:34Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_Center extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/^! *(.*?)$/m';
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * centered text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A pair of delimited tokens to be used as a
+ * placeholder in the source text surrounding the centered text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $start = $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'start'
+ )
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'end'
+ )
+ );
+
+ return $start . trim($matches[1]) . $end . "\n\n";
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Delimiter.php b/includes/pear/Text/Wiki/Parse/Creole/Delimiter.php
new file mode 100644
index 0000000..c53b78a
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Delimiter.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ *
+ * Parses for Text_Wiki delimiter characters already in the source text.
+ *
+ * This class implements a Text_Wiki_Parse to find instances of the delimiter
+ * character already embedded in the source text; it extracts them and replaces
+ * them with a delimited token, then renders them as the delimiter itself
+ * when the target format is XHTML.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Paul M. Jones <pmjones@php.net>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Delimiter.php 222265 2006-10-23 13:11:27Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_Delimiter extends Text_Wiki_Parse {
+
+ /**
+ *
+ * Constructor. Overrides the Text_Wiki_Parse constructor so that we
+ * can set the $regex property dynamically (we need to include the
+ * Text_Wiki $delim character.
+ *
+ * @param object &$obj The calling "parent" Text_Wiki object.
+ *
+ * @param string $name The token name to use for this rule.
+ *
+ */
+
+ function Text_Wiki_Parse_Delimiter(&$obj)
+ {
+ parent::Text_Wiki_Parse($obj);
+ $this->regex = '/' . $this->wiki->delim . '/';
+ }
+
+
+ /**
+ *
+ * Generates a token entry for the matched text. Token options are:
+ *
+ * 'text' => The full matched text.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token number to be used as a placeholder in
+ * the source text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ return $this->wiki->addToken(
+ $this->rule,
+ array('text' => $this->wiki->delim)
+ );
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Emphasis.php b/includes/pear/Text/Wiki/Parse/Creole/Emphasis.php
new file mode 100644
index 0000000..979f89e
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Emphasis.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ *
+ * Parses for italic text.
+ *
+ * This class implements a Text_Wiki_Parse to find source text marked for
+ * emphasis (italics) as defined by text surrounded by two slashes.
+ * On parsing, the text itself is left in place, but the starting and ending
+ * instances of two single-quotes are replaced with tokens.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Paul M. Jones <pmjones@php.net>
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Emphasis.php 242125 2007-09-03 21:16:10Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_Emphasis extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/\/\/(.+?)\/\//";
+ //var $regex = "/(?:\/\/(.+?)\/\/|(?:(?<=[\W_\xFF])\/(?![ \/]))(.+?)(?:(?<![ \/])\/(?=[\W_\xFF])))/";
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * emphasized text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A pair of delimited tokens to be used as a
+ * placeholder in the source text surrounding the text to be
+ * emphasized.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $text = $matches[1];
+ //$text = $matches[1]/* ? $matches[1] : $matches[2]*/;
+
+ if (! $this->wiki->checkInnerTags($text)) {
+ return $matches[0];
+ }
+
+ $start = $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'start')
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'end')
+ );
+
+ return $start . $text . $end;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Footnote.php b/includes/pear/Text/Wiki/Parse/Creole/Footnote.php
new file mode 100644
index 0000000..00e4178
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Footnote.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ *
+ * Parses for bold text.
+ *
+ * This class implements a Text_Wiki_Rule to find source text marked for
+ * strong emphasis (bold) as defined by text surrounded by two
+ * stars. On parsing, the text itself is left in place, but the
+ * starting and ending instances of two stars are replaced with
+ * tokens.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Paul M. Jones <pmjones@php.net>
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Footnote.php 236407 2007-05-26 17:47:24Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_Footnote extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/(\n)*\[([0-9]+)\]/";
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * emphasized text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A pair of delimited tokens to be used as a placeholder in
+ * the source text surrounding the text to be emphasized.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $id = $matches[2];
+
+ if ($matches[1] == "\n") {
+ $matches[1] = "\n\n";
+ $name = "fn$id";
+ $href = "#ref$id";
+ }
+ else {
+ $name = "ref$id";
+ $href = "#fn$id";
+ }
+
+ $token = $this->wiki->addToken(
+ 'Url',
+ array('text' => "[$id]", 'href' => $href, 'name' => $name, 'type' => 'inline')
+ );
+
+ return $matches[1] . $token;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Heading.php b/includes/pear/Text/Wiki/Parse/Creole/Heading.php
new file mode 100644
index 0000000..eb213c9
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Heading.php
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ *
+ * Parses for heading text.
+ *
+ * This class implements a Text_Wiki_Parse to find source text marked to
+ * be a heading element, as defined by text on a line by itself prefixed
+ * with a number of equasl signs (=), determining the heading level.
+ * Equal signs at the end of the line are silently removed.
+ * The heading text itself is left in the source, but is prefixed and
+ * suffixed with delimited tokens marking the start and end of the heading.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Paul M. Jones <pmjones@php.net>
+ * @author Tomaiuolo Michele <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Heading.php 228641 2007-02-01 09:57:36Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_Heading extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/^(={1,6}) *(.*?) *=*$/m';
+
+ var $conf = array(
+ 'id_prefix' => 'toc'
+ );
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * heading text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A pair of delimited tokens to be used as a
+ * placeholder in the source text surrounding the heading text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // keep a running count for header IDs. we use this later
+ // when constructing TOC entries, etc.
+ static $id;
+ if (! isset($id)) {
+ $id = 0;
+ }
+
+ $prefix = htmlspecialchars($this->getConf('id_prefix'));
+
+ $start = $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'start',
+ 'level' => strlen($matches[1]),
+ 'text' => trim($matches[2]),
+ 'id' => $prefix . $id ++
+ )
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'end',
+ 'level' => strlen($matches[1])
+ )
+ );
+
+ return $start . trim($matches[2]) . $end . "\n\n";
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Horiz.php b/includes/pear/Text/Wiki/Parse/Creole/Horiz.php
new file mode 100644
index 0000000..60c6160
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Horiz.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ *
+ * Parses for horizontal ruling lines.
+ *
+ * This class implements a Text_Wiki_Parse to find source text marked to
+ * be a horizontal rule, as defined by four dashed on their own line.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Paul M. Jones <pmjones@php.net>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Horiz.php 228654 2007-02-01 11:35:53Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_Horiz extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/^([-]{4,})$/m';
+
+
+ /**
+ *
+ * Generates a replacement token for the matched text.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A token marking the horizontal rule.
+ *
+ */
+
+ function process(&$matches)
+ {
+ return "\n" . $this->wiki->addToken($this->rule) . "\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Image.php b/includes/pear/Text/Wiki/Parse/Creole/Image.php
new file mode 100644
index 0000000..09ddb8b
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Image.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ *
+ * Parse for images in the source text.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Tomaiuolo Michele <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Image.php 243106 2007-09-28 22:02:50Z mic $
+ *
+ */
+
+
+class Text_Wiki_Parse_Image extends Text_Wiki_Parse {
+
+ /**
+ *
+ * Constructor. Overrides the Text_Wiki_Parse constructor so that we
+ * can set the $regex property dynamically (we need to include the
+ * Text_Wiki $delim character).
+ *
+ * @param object &$obj The calling "parent" Text_Wiki object.
+ *
+ * @param string $name The token name to use for this rule.
+ *
+ */
+
+ function Text_Wiki_Parse_Image(&$obj)
+ {
+ parent::Text_Wiki_Parse($obj);
+ $this->regex = '/{{([^' . $this->wiki->delim . ']*)(\|([^' . $this->wiki->delim . ']*))?}}/U';
+ }
+
+
+ /**
+ *
+ * Generates a replacement token for the matched text.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A token marking the horizontal rule.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $src = trim($matches[1]);
+ $src = ltrim($src, '/');
+ $alt = isset($matches[3]) ? trim($matches[3]) : $src;
+
+ return $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'src' => $src,
+ 'attr' => array('alt' => $alt, 'title' => $alt)
+ )
+ );
+ }
+
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/List.php b/includes/pear/Text/Wiki/Parse/Creole/List.php
new file mode 100644
index 0000000..36ab590
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/List.php
@@ -0,0 +1,244 @@
+<?php
+
+/**
+ *
+ * Parses for bulleted and numbered lists.
+ *
+ * This class implements a Text_Wiki_Parse to find source text marked as
+ * a bulleted or numbered list. In short, if a line starts with '*' then
+ * it is a bullet list item; if a line starts with '#' then it is a
+ * number list item. Multiple * or # indicate an indented sub-list.
+ * The list items must be on sequential lines, and are ended by blank lines.
+ * Using a non-* non-# character at the beginning of a line ends the list.
+ * Note that single newline characters may be eaten beforehand by other rules.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Justin Patrin <papercrane@reversefold.com>
+ * @author Paul M. Jones <pmjones@php.net>
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: List.php 240550 2007-08-01 07:57:34Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_List extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/\n((\*[^\#\-\*]|\-[^\-\d\*\#]|\#[^\#\-\*]).*?)\n(?![\*\-#])/s';
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' =>
+ * 'bullet_start' : the start of a bullet list
+ * 'bullet_end' : the end of a bullet list
+ * 'number_start' : the start of a number list
+ * 'number_end' : the end of a number list
+ * 'item_start' : the start of item text (bullet or number)
+ * 'item_end' : the end of item text (bullet or number)
+ * 'unknown' : unknown type of list or item
+ *
+ * 'level' => the indent level (0 for the first level, 1 for the
+ * second, etc)
+ *
+ * 'count' => the list item number at this level. not needed for
+ * xhtml, but very useful for PDF and RTF.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A series of text and delimited tokens marking the different
+ * list text and list elements.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // the replacement text we will return
+ $return = '';
+
+ // the list of post-processing matches
+ $list = array();
+
+ // a stack of list-start and list-end types; we keep this
+ // so that we know what kind of list we're working with
+ // (bullet or number) and what indent level we're at.
+ $stack = array();
+
+ // the item count is the number of list items for any
+ // given list-type on the stack
+ $itemcount = array();
+
+ // have we processed the very first list item?
+ $pastFirst = false;
+
+ // populate $list with this set of matches. $matches[1] is the
+ // text matched as a list set by parse().
+ preg_match_all(
+ '/^((\*|\-|#)+) *(.*?)$/ms',
+ $matches[1],
+ $list,
+ PREG_SET_ORDER
+ );
+
+ if (count($list) === 1 && $matches[0][0] === '*' && $matches[0][1] !== ' ' && strpos($matches[0], '*', 1)) {
+ return $matches[0];
+ }
+
+ // loop through each list-item element.
+ foreach ($list as $key => $val) {
+ // $val[0] is the full matched list-item line
+ // $val[1] is the level (number)
+ // $val[2] is the type (* or #)
+ // $val[3] is the list item text
+
+ // how many levels are we indented? (1 means the "root"
+ // list level, no indenting.)
+ $stars = $val[1];
+ $level = strlen($stars);
+ $last = $stars[strlen($stars) - 1];
+
+ // get the list item type
+ if ($last == '*' || $last == '-') {
+ $type = 'bullet';
+ } elseif ($last == '#') {
+ $type = 'number';
+ } else {
+ $type = 'unknown';
+ }
+
+ // get the text of the list item
+ $text = $val[3];
+
+ // remove a level from the list?
+ while (count($stack) > $level || (count($stack) == $level && $type != $stack[$level - 1])) {
+
+ // so we don't keep counting the stack, we set up a temp
+ // var for the count. -1 becuase we're going to pop the
+ // stack in the next command. $tmp will then equal the
+ // current level of indent.
+ $tmp = count($stack) - 1;
+
+ // as long as the stack count is greater than the
+ // current indent level, we need to end list types.
+ // continue adding end-list tokens until the stack count
+ // and the indent level are the same.
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array (
+ 'type' => array_pop($stack) . '_list_end',
+ 'level' => $tmp
+ )
+ );
+
+ // reset to the current (previous) list type so that
+ // the new list item matches the proper list type.
+ if ($tmp) {
+ $oldtype = $stack[$tmp - 1];
+ }
+
+ // reset the item count for the popped indent level
+ unset($itemcount[$tmp + 1]);
+ }
+
+ // add a level to the list?
+ if ($level > count($stack)) {
+
+ // the current indent level is greater than the
+ // number of stack elements, so we must be starting
+ // a new list. push the new list type onto the
+ // stack...
+ array_push($stack, $type);
+
+ // ...and add a list-start token to the return.
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => $type . '_list_start',
+ 'level' => $level - 1
+ )
+ );
+ }
+
+ // add to the item count for this list (taking into account
+ // which level we are at).
+ if (! isset($itemcount[$level])) {
+ // first count
+ $itemcount[$level] = 0;
+ } else {
+ // increment count
+ $itemcount[$level]++;
+ }
+
+ // is this the very first item in the list?
+ if (! $pastFirst) {
+ $first = true;
+ $pastFirst = true;
+ } else {
+ $first = false;
+ }
+
+ // create a list-item starting token.
+ $start = $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => $type . '_item_start',
+ 'level' => $level,
+ 'count' => $itemcount[$level],
+ 'first' => $first
+ )
+ );
+
+ // create a list-item ending token.
+ $end = $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => $type . '_item_end',
+ 'level' => $level,
+ 'count' => $itemcount[$level]
+ )
+ );
+
+ // add the starting token, list-item text, and ending token
+ // to the return.
+ $return .= "\n" . $start . $text . $end;
+ }
+
+ // the last list-item may have been indented. go through the
+ // list-type stack and create end-list tokens until the stack
+ // is empty.
+ while (count($stack) > 0) {
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array (
+ 'type' => array_pop($stack) . '_list_end',
+ 'level' => count($stack)
+ )
+ );
+ }
+
+ // we're done! send back the replacement text.
+ return "\n\n" . $return . "\n\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Newline.php b/includes/pear/Text/Wiki/Parse/Creole/Newline.php
new file mode 100644
index 0000000..92554ed
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Newline.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ *
+ * Parses for implied line breaks indicated by newlines.
+ * Newlines are not considered if followed by another newline
+ * or by one of these chars: * | - # = {
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Newline.php 240560 2007-08-01 11:00:11Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_Newline extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ //var $regex = '/(?<!\n)\n(?![\n\#\=\|\-\>\:]|\*[^\*\#]|\*+ )/m';
+ var $regex = '/(?<!\n)\n(?!\n|\#|\*|\=|\||\>|\:|\;|\!|\-\D)/m';
+
+
+ /**
+ *
+ * Generates a replacement token for the matched text.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A delimited token to be used as a placeholder in
+ * the source text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ return ' '; // $this->wiki->addToken($this->rule);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Paragraph.php b/includes/pear/Text/Wiki/Parse/Creole/Paragraph.php
new file mode 100644
index 0000000..d23ed33
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Paragraph.php
@@ -0,0 +1,139 @@
+<?php
+
+/**
+ *
+ * Parses for paragraph blocks.
+ * This class implements a Text_Wiki rule to find sections of the source
+ * text that are paragraphs. A paragraph is any line not starting with a
+ * token delimiter, followed by two newlines.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Paul M. Jones <pmjones@php.net>
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Paragraph.php 230214 2007-02-19 08:57:14Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_Paragraph extends Text_Wiki_Parse {
+
+ /**
+ *
+ * The regular expression used to find source text matching this
+ * rule.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ var $regex = "/^.+?\n/m"; // (?=[\n\-\|#{=])
+
+ var $conf = array(
+ 'skip' => array(
+ 'address',
+ 'box',
+ 'blockquote',
+ 'code',
+ 'heading',
+ 'center',
+ 'horiz',
+ 'deflist',
+ 'table',
+ 'list',
+ 'paragraph',
+ 'preformatted',
+ 'toc'
+ )
+ );
+
+
+ /**
+ *
+ * Generates a token entry for the matched text. Token options are:
+ *
+ * 'start' => The starting point of the paragraph.
+ *
+ * 'end' => The ending point of the paragraph.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token number to be used as a placeholder in
+ * the source text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $delim = $this->wiki->delim;
+
+ // was anything there?
+ if (trim($matches[0]) == '') {
+ return '';
+ }
+
+ // does the match start with a delimiter?
+ if (substr($matches[0], 0, 1) != $delim) {
+ // no.
+
+ $start = $this->wiki->addToken(
+ $this->rule, array('type' => 'start')
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule, array('type' => 'end')
+ );
+
+ return $start . trim($matches[0]) . $end;
+ }
+
+ // the line starts with a delimiter. read in the delimited
+ // token number, check the token, and see if we should
+ // skip it.
+
+ // loop starting at the second character (we already know
+ // the first is a delimiter) until we find another
+ // delimiter; the text between them is a token key number.
+ $key = '';
+ $len = strlen($matches[0]);
+ for ($i = 1; $i < $len; $i++) {
+ $char = $matches[0]{$i};
+ if ($char == $delim) {
+ break;
+ } else {
+ $key .= $char;
+ }
+ }
+
+ // look at the token and see if it's skippable (if we skip,
+ // it will not be marked as a paragraph)
+ $token_type = strtolower($this->wiki->tokens[$key][0]);
+ $skip = $this->getConf('skip', array());
+
+ if (in_array($token_type, $skip)) {
+ // this type of token should not have paragraphs applied to it.
+ // return the entire matched text.
+ return $matches[0];
+ } else {
+
+ $start = $this->wiki->addToken(
+ $this->rule, array('type' => 'start')
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule, array('type' => 'end')
+ );
+
+ return $start . trim($matches[0]) . $end;
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Prefilter.php b/includes/pear/Text/Wiki/Parse/Creole/Prefilter.php
new file mode 100644
index 0000000..ced69b3
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Prefilter.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ *
+ * "Pre-filter" the source text.
+ *
+ * Convert DOS and Mac line endings to Unix, convert tabs to 4-spaces,
+ * add newlines to the top and end of the source text.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Paul M. Jones <pmjones@php.net>
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Prefilter.php 222265 2006-10-23 13:11:27Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_Prefilter extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * Simple parsing method.
+ *
+ * @access public
+ *
+ */
+
+ function parse()
+ {
+ // convert DOS line endings
+ $this->wiki->source = str_replace("\r\n", "\n",
+ $this->wiki->source);
+
+ // convert Macintosh line endings
+ $this->wiki->source = str_replace("\r", "\n",
+ $this->wiki->source);
+
+ // convert tabs to four-spaces
+ $this->wiki->source = str_replace("\t", " ",
+ $this->wiki->source);
+
+ // add extra newlines at the top and end; this
+ // seems to help many rules.
+ $this->wiki->source = "\n\n" . $this->wiki->source . "\n\n";
+ }
+
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Preformatted.php b/includes/pear/Text/Wiki/Parse/Creole/Preformatted.php
new file mode 100644
index 0000000..ac164fb
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Preformatted.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ *
+ * Parses for preformatted text.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Tomaiuolo Michele <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Preformatted.php 240474 2007-07-30 13:14:41Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_Preformatted extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/\n{{{\n(.*)\n}}}\n/Us';
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'text' => The preformatted text.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A token to be used as a placeholder
+ * in the source text for the preformatted text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // > any line consisting of only indented three closing curly braces
+ // > will have one space removed from the indentation
+ // > -- http://www.wikicreole.org/wiki/AddNoWikiEscapeProposal
+ $find = "/\n( *) }}}/";
+ $replace = "\n$1}}}";
+ $matches[1] = preg_replace($find, $replace, $matches[1]);
+
+ $token = $this->wiki->addToken(
+ $this->rule,
+ array('text' => $matches[1])
+ );
+ return "\n\n" . $token . "\n\n";
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Raw.php b/includes/pear/Text/Wiki/Parse/Creole/Raw.php
new file mode 100644
index 0000000..6679267
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Raw.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ *
+ * Parses for monospaced inline text.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Tomaiuolo Michele <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Raw.php 242165 2007-09-04 19:43:07Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_Raw extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/~(_|[^ \w\n])/';
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * monospaced text. The text itself is encapsulated into a Raw token.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A token to be used as a placeholder
+ * in the source text for the preformatted text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ return $this->wiki->addToken(
+ $this->rule,
+ array('text' => $matches[1], 'type' => 'escape')
+ );
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Strong.php b/includes/pear/Text/Wiki/Parse/Creole/Strong.php
new file mode 100644
index 0000000..1d07d15
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Strong.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ *
+ * Parses for bold text.
+ *
+ * This class implements a Text_Wiki_Rule to find source text marked for
+ * strong emphasis (bold) as defined by text surrounded by two
+ * stars. On parsing, the text itself is left in place, but the
+ * starting and ending instances of two stars are replaced with
+ * tokens.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Paul M. Jones <pmjones@php.net>
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Strong.php 242126 2007-09-03 21:17:00Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_Strong extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/\*\*(.+?)\*\*/";
+ //var $regex = "/(?:\*\*(.+?)\*\*|(?:(?<=[\W_\xFF])\*(?![ \*]))(.+?)(?:(?<![ \*])\*(?=[\W_\xFF])))/";
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * emphasized text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A pair of delimited tokens to be used as a placeholder in
+ * the source text surrounding the text to be emphasized.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $text = $matches[1];
+ //$text = $matches[1] ? $matches[1] : $matches[2];
+
+ if (! $this->wiki->checkInnerTags($text)) {
+ return $matches[0];
+ }
+
+ $start = $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'start')
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'end')
+ );
+
+ return $start . $text . $end;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Subscript.php b/includes/pear/Text/Wiki/Parse/Creole/Subscript.php
new file mode 100644
index 0000000..596f652
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Subscript.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ *
+ * Parses for italic text.
+ *
+ * This class implements a Text_Wiki_Parse to find source text marked for
+ * superscript as defined by text surrounded by two '^'.
+ * On parsing, the text itself is left in place, but the starting and ending
+ * instances of two '^' are replaced with tokens.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Paul M. Jones <pmjones@php.net>
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ */
+
+class Text_Wiki_Parse_Subscript extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/\,\,(.*?)\,\,/";
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * superscript text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A pair of delimited tokens to be used as a
+ * placeholder in the source text surrounding the text to be
+ * superscripted.
+ *
+ */
+
+ function process(&$matches)
+ {
+ if (! $this->wiki->checkInnerTags($matches[1])) {
+ return $matches[0];
+ }
+
+ $start = $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'start')
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'end')
+ );
+
+ return $start . $matches[1] . $end;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Superscript.php b/includes/pear/Text/Wiki/Parse/Creole/Superscript.php
new file mode 100644
index 0000000..0f5e38c
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Superscript.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ *
+ * Parses for italic text.
+ *
+ * This class implements a Text_Wiki_Parse to find source text marked for
+ * superscript as defined by text surrounded by two '^'.
+ * On parsing, the text itself is left in place, but the starting and ending
+ * instances of two '^' are replaced with tokens.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Paul M. Jones <pmjones@php.net>
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ */
+
+class Text_Wiki_Parse_Superscript extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/(\^\^(.*?)\^\^|(?<=\d)(st|nd|rd|th|er|e|re|ers|res|nds|de|des|ère|ème|ères|èmes|o|a)(?!\w))/";
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * superscript text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A pair of delimited tokens to be used as a
+ * placeholder in the source text surrounding the text to be
+ * superscripted.
+ *
+ */
+
+ function process(&$matches)
+ {
+ if (! $this->wiki->checkInnerTags($matches[0])) {
+ return $matches[0];
+ }
+
+ $start = $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'start')
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'end')
+ );
+
+ return $start . trim($matches[0], '^') . $end;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Table.php b/includes/pear/Text/Wiki/Parse/Creole/Table.php
new file mode 100644
index 0000000..2ab3284
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Table.php
@@ -0,0 +1,207 @@
+<?php
+
+/**
+ *
+ * Parses for table markup.
+ *
+ * This class implements a Text_Wiki_Parse to find source text marked as
+ * a set of table rows, where a line start (and optionally ends) with a
+ * single-pipe (|) and uses single-pipes to separate table cells.
+ * The rows must be on sequential lines (no blank lines between them).
+ * A blank line indicates the beginning of other text or another table.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ * @author Paul M. Jones <pmjones@php.net>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Table.php 243077 2007-09-28 17:14:58Z mic $
+ *
+ */
+
+
+class Text_Wiki_Parse_Table extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/\n((\|).*)(\n)(?!(\|))/Us';
+
+
+ /**
+ *
+ * Generates a replacement for the matched text.
+ *
+ * Token options are:
+ *
+ * 'type' =>
+ * 'table_start' : the start of a bullet list
+ * 'table_end' : the end of a bullet list
+ * 'row_start' : the start of a number list
+ * 'row_end' : the end of a number list
+ * 'cell_start' : the start of item text (bullet or number)
+ * 'cell_end' : the end of item text (bullet or number)
+ *
+ * 'cols' => the number of columns in the table (for 'table_start')
+ *
+ * 'rows' => the number of rows in the table (for 'table_start')
+ *
+ * 'span' => column span (for 'cell_start')
+ *
+ * 'attr' => column attribute flag (for 'cell_start')
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A series of text and delimited tokens marking the different
+ * table elements and cell text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // our eventual return value
+ $return = '';
+
+ // the number of columns in the table
+ $num_cols = 0;
+
+ // the number of rows in the table
+ $num_rows = 0;
+
+ // rows are separated by newlines in the matched text
+ $rows = explode("\n", $matches[1]);
+
+ // loop through each row
+ foreach ($rows as $row) {
+
+ // increase the row count
+ $num_rows ++;
+
+ // remove first and last (optional) pipe
+ $row = substr($row, 1);
+ if ($row[strlen($row) - 1] == '|') {
+ $row = substr($row, 0, -1);
+ }
+
+ // cells are separated by pipes
+ $cells = explode("|", $row);
+
+ if (count($cells) == 1 && $cells[0][0] == '=' && ($num_rows == 1 || $num_rows == count($rows)) && ! isset($caption)) {
+ $caption = trim(trim($cells[0], '='));
+
+ // start the caption...
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array ('type' => 'caption_start')
+ );
+
+ // ...add the content...
+ $return .= $caption;
+
+ // ...and end the caption.
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array ('type' => 'caption_end')
+ );
+ }
+ else {
+
+ // update the column count
+ if (count($cells) > $num_cols) {
+ $num_cols = count($cells);
+ }
+
+ // start a new row
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'row_start')
+ );
+
+ for ($i = 0; $i < count($cells); $i++) {
+ $cell = $cells[$i];
+
+ // by default, cells span only one column (their own)
+ $span = 1;
+ $attr = '';
+
+ while ($i + 1 < count($cells) && ! strlen($cells[$i + 1])) {
+ $i++;
+ $span++;
+ }
+
+ if (strlen($cell) > 0 && $cell[0] == '=') {
+ $attr = 'header';
+ $cell = trim($cell, '=');
+ }
+
+ // start a new cell...
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array (
+ 'type' => 'cell_start',
+ 'attr' => $attr,
+ 'span' => $span
+ )
+ );
+
+ // ...add the content...
+ $return .= trim($cell);
+
+ // ...and end the cell.
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array (
+ 'type' => 'cell_end',
+ 'attr' => $attr,
+ 'span' => $span
+ )
+ );
+ }
+
+ // end the row
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'row_end')
+ );
+ }
+ }
+
+ // we're done!
+ return
+ "\n\n".
+ $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'table_start',
+ 'rows' => $num_rows,
+ 'cols' => $num_cols
+ )
+ ).
+ $return.
+ $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'table_end'
+ )
+ ).
+ "\n\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Tighten.php b/includes/pear/Text/Wiki/Parse/Creole/Tighten.php
new file mode 100644
index 0000000..8ef1b45
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Tighten.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ *
+ * The rule removes all remaining newlines.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Paul M. Jones <pmjones@php.net>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Tighten.php 222265 2006-10-23 13:11:27Z mic $
+ *
+ */
+
+
+class Text_Wiki_Parse_Tighten extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * Apply tightening directly to the source text.
+ *
+ * @access public
+ *
+ */
+
+ function parse()
+ {
+ $this->wiki->source = str_replace("\n", '',
+ $this->wiki->source);
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Trim.php b/includes/pear/Text/Wiki/Parse/Creole/Trim.php
new file mode 100644
index 0000000..f10c9a2
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Trim.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ *
+ * Trim lines in the source text and compress 3 or more newlines to
+ * 2 newlines.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Paul M. Jones <pmjones@php.net>
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ */
+
+class Text_Wiki_Parse_Trim extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * Simple parsing method.
+ *
+ * @access public
+ *
+ */
+
+ function parse()
+ {
+ // trim lines
+ $find = "/ *\n */";
+ $replace = "\n";
+ $this->wiki->source = preg_replace($find, $replace, $this->wiki->source);
+
+ // trim lines with only one dash or star
+ $find = "/\n[\-\*]\n/";
+ $replace = "\n\n";
+ $this->wiki->source = preg_replace($find, $replace, $this->wiki->source);
+
+ // finally, compress all instances of 3 or more newlines
+ // down to two newlines.
+ $find = "/\n{3,}/m";
+ $replace = "\n\n";
+ $this->wiki->source = preg_replace($find, $replace, $this->wiki->source);
+
+ // numbered lists
+ $find = "/(\n[\*\#]*)([\d]+[\.\)]|[\w]\)) /s";
+ $replace = "$1# ";
+ $this->wiki->source = preg_replace($find, $replace, $this->wiki->source);
+
+ // numbers in parentesis are footnotes and references
+ $find = "/\(([\d][\d]?)\)/";
+ $replace = "[$1]";
+ $this->wiki->source = preg_replace($find, $replace, $this->wiki->source);
+
+ // add hr before footnotes
+ $find = "/(\n+\-\-\-\-+\n*)?(\n\[[\d]+\].*)/s";
+ $replace = "\n\n----\n\n$2";
+ $this->wiki->source = preg_replace($find, $replace, $this->wiki->source);
+
+ /*
+ // wrap images in tables
+ $find = "/(?<=\n\n){{([^\|}]*)\|([^}]*)}}(?=\n\n)/";
+ $replace = "| {{ $1 | $2 }}\n|= $2";
+ $this->wiki->source = preg_replace($find, $replace, $this->wiki->source);
+
+ // wrap images in tables
+ $find = "/(?<=\n\n){{([^\|}]*)}}(?=\n\n)/";
+ $replace = "| {{ $1 }}";
+ $this->wiki->source = preg_replace($find, $replace, $this->wiki->source);
+ */
+ }
+
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Tt.php b/includes/pear/Text/Wiki/Parse/Creole/Tt.php
new file mode 100644
index 0000000..31072bc
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Tt.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ *
+ * Parses for monospaced inline text.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Tomaiuolo Michele <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Tt.php 240474 2007-07-30 13:14:41Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_Tt extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/{{{(.*?)}}}(?!}|{{{)/';
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * monospaced text. The text itself is encapsulated into a Raw token.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A token to be used as a placeholder
+ * in the source text for the preformatted text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // remove the sequence }}}{{{
+ $find = "/}}}{{{/";
+ $replace = "";
+ $matches[1] = preg_replace($find, $replace, $matches[1]);
+
+ $start = $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'start')
+ );
+
+ $raw = $this->wiki->addToken(
+ 'Raw',
+ array('text' => $matches[1])
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'end')
+ );
+
+ return $start . $raw . $end;
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Underline.php b/includes/pear/Text/Wiki/Parse/Creole/Underline.php
new file mode 100644
index 0000000..c3d5b14
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Underline.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ *
+ * Parses for italic text.
+ *
+ * This class implements a Text_Wiki_Parse to find source text marked for
+ * underlined as defined by text surrounded by two '_'.
+ * On parsing, the text itself is left in place, but the starting and ending
+ * instances of two '^' are replaced with tokens.
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Paul M. Jones <pmjones@php.net>
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Underline.php 242127 2007-09-03 21:29:36Z mic $
+ *
+ */
+
+class Text_Wiki_Parse_Underline extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/__(.+?)__/";
+ //var $regex = "/(?:\_\_(.+?)\_\_|(?:(?<=[\W_\xFF])\_(?![ \_]))(.+?)(?:(?<![ \_])\_(?=[\W_\xFF])))/";
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * superscript text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A pair of delimited tokens to be used as a
+ * placeholder in the source text surrounding the text to be
+ * superscripted.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $text = $matches[1];
+ //$text = $matches[1] ? $matches[1] : $matches[2];
+
+ if (! $this->wiki->checkInnerTags($text)) {
+ return $matches[0];
+ }
+
+ $start = $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'start')
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'end')
+ );
+
+ return $start . $text . $end;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Url.php b/includes/pear/Text/Wiki/Parse/Creole/Url.php
new file mode 100644
index 0000000..4422a31
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Url.php
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ *
+ * Parse for URLS in the source text.
+ *
+ * raw -- http://example.com
+ * no descr. -- [[http://example.com]]
+ * described -- [[http://example.com|Example Description]]
+ *
+ * When rendering a URL token, this will convert URLs pointing to a .gif,
+ * .jpg, or .png image into an inline <img /> tag (for the 'xhtml'
+ * format).
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ * @license LGPL
+ *
+ * @version $Id: Url.php 293784 2010-01-20 18:48:09Z justinpatrin $
+ *
+ */
+
+class Text_Wiki_Parse_Url extends Text_Wiki_Parse {
+
+ /**
+ *
+ * Constructor. Overrides the Text_Wiki_Parse constructor so that we
+ * can set the $regex property dynamically (we need to include the
+ * Text_Wiki $delim character).
+ *
+ * @param object &$obj The calling "parent" Text_Wiki object.
+ *
+ * @param string $name The token name to use for this rule.
+ *
+ */
+
+ function Text_Wiki_Parse_Url(&$obj)
+ {
+ parent::Text_Wiki_Parse($obj);
+ $this->regex = '/((?:\[\[ *((?:\w+:\/\/|mailto:|\/)[^\|\]\n ]*)( *\| *([^\]\n]*))? *\]\])|((?<=[^\~\w])(https?:\/\/|ftps?:\/\/|mailto:)[^\'\"\n ' . $this->wiki->delim . ']*[A-Za-z0-9\/\?\=\&\~\_#]))/';
+ }
+
+
+ /**
+ *
+ * Generates a replacement for the matched text.
+ *
+ * Token options are:
+ *
+ * 'href' => the URL link href portion
+ *
+ * 'text' => the displayed text of the URL link
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A token to be used as a placeholder
+ * in the source text for the preformatted text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ if (isset($matches[2])) $href = trim($matches[2]);
+ if (isset($matches[4])) $text = trim($matches[4]);
+ if (isset($matches[5])) $rawurl = $matches[5];
+ if (empty($href)) $href = $rawurl;
+
+ if (empty($text)) {
+ $text = $href;
+ if (strpos($text, '/') === FALSE) {
+ $text = str_replace('http://', '', $text);
+ $text = str_replace('mailto:', '', $text);
+ }
+ return $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'inline',
+ 'href' => $href,
+ 'text' => $text
+ )
+ );
+ } else {
+ return $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'start',
+ 'href' => $href,
+ 'text' => $text
+ )
+ ) . $text .
+ $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'end',
+ 'href' => $href,
+ 'text' => $text
+ )
+ );
+ }
+ }
+
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Creole/Wikilink.php b/includes/pear/Text/Wiki/Parse/Creole/Wikilink.php
new file mode 100644
index 0000000..19cafc1
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Creole/Wikilink.php
@@ -0,0 +1,322 @@
+<?php
+
+/**
+ * Mediawiki: Parses for links to (inter)wiki pages or images.
+ *
+ * Text_Wiki rule parser to find links, it groups the 3 rules:
+ * # Wikilink: links to internal Wiki pages
+ * # Interwiki: links to external Wiki pages (sister projects, interlangage)
+ * # Image: Images
+ * as defined by text surrounded by double brackets [[]]
+ * Translated are the link itself, the section (anchor) and alternate text
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @author Paul M. Jones <pmjones@php.net>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Wikilink.php 240474 2007-07-30 13:14:41Z mic $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * Wikilink, Interwiki and Image rules parser class for Mediawiki.
+ * This class implements a Text_Wiki_Parse to find links marked
+ * in source by text surrounded by 2 opening/closing brackets as
+ * [[Wiki page name#Section|Alternate text]]
+ * On parsing, the link is replaced with a token.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ * @see Text_Wiki_Parse::Text_Wiki_Parse()
+ */
+
+class Text_Wiki_Parse_Wikilink extends Text_Wiki_Parse {
+
+ /**
+ * Configuration for this rule (Wikilink)
+ *
+ * @access public
+ * @var array
+ */
+
+ var $conf = array(
+ 'spaceUnderscore' => true,
+ 'project' => array('demo', 'd'),
+ 'url' => 'http://example.com/en/page=%s',
+ 'langage' => 'en'
+ );
+
+ /**
+ * Configuration for the Image rule
+ *
+ * @access public
+ * @var array
+ */
+
+ var $imageConf = array(
+ 'prefix' => array('Image', 'image')
+ );
+
+ /**
+ * Configuration for the Interwiki rule
+ *
+ * @access public
+ * @var array
+ */
+
+ var $interwikiConf = array(
+ 'sites' => array(
+ 'manual' => 'http://www.php.net/manual/en/%s',
+ 'pear' => 'http://pear.php.net/package/%s',
+ 'bugs' => 'http://pear.php.net/package/%s/bugs'
+ ),
+ 'interlangage' => array('en', 'de', 'fr')
+ );
+
+ /**
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ * @var string
+ * @see Text_Wiki_Parse::parse()
+ */
+
+ var $regex = '/(?<!\[)\[\[(?!\[) *(:?)((?:[^:\n]+:)+)?([^:\n]+)(#(?:[^\n]*))?(?: *\| *(((?R))|[^\n]*))? *]]/msU';
+
+ /**
+ * Constructor.
+ * We override the constructor to get Image and Interwiki config
+ *
+ * @param object &$obj the base conversion handler
+ * @return The parser object
+ * @access public
+ */
+
+ function Text_Wiki_Parse_Wikilink(&$obj)
+ {
+ $default = $this->conf;
+ parent::Text_Wiki_Parse($obj);
+
+ // override config options for image if specified
+ if (in_array('Image', $this->wiki->disable)) {
+ $this->imageConf['prefix'] = array();
+ } else {
+ if (isset($this->wiki->parseConf['Image']) &&
+ is_array($this->wiki->parseConf['Image'])) {
+ $this->imageConf = array_merge(
+ $this->imageConf,
+ $this->wiki->parseConf['Image']
+ );
+ }
+ }
+
+ // override config options for interwiki if specified
+ if (in_array('Interwiki', $this->wiki->disable)) {
+ $this->interwikiConf['sites'] = array();
+ $this->interwikiConf['interlangage'] = array();
+ } else {
+ if (isset($this->wiki->parseConf['Interwiki']) &&
+ is_array($this->wiki->parseConf['Interwiki'])) {
+ $this->interwikiConf = array_merge(
+ $this->interwikiConf,
+ $this->wiki->parseConf['Interwiki']
+ );
+ }
+ if (empty($this->conf['langage'])) {
+ $this->interwikiConf['interlangage'] = array();
+ }
+ }
+ //$this->regex = str_replace('DELIM', $this->wiki->delim, $this->regex);
+ // convert the list of recognized schemes to a regex OR,
+/* $schemes = $this->getConf('schemes', $default['schemes']);
+ $this->url = str_replace( '#delim#', $this->wiki->delim,
+ '#(?:' . (is_array($schemes) ? implode('|', $schemes) : $schemes) . ')://'
+ . $this->getConf('host_regexp', $default['host_regexp'])
+ . $this->getConf('path_regexp', $default['path_regexp']) .'#'); */
+ }
+
+ /**
+ * Generates a replacement for the matched text. Token options are:
+ * - 'page' => the name of the target wiki page
+ * -'anchor' => the optional section in it
+ * - 'text' => the optional alternate link text
+ *
+ * @access public
+ * @param array &$matches The array of matches from parse().
+ * @return string token to be used as replacement
+ */
+
+ function process(&$matches)
+ {
+ $matches[3] = $this->wiki->restoreRaw($matches[3]);
+
+ // Starting colon ?
+ $colon = !empty($matches[1]);
+ $auto = $interlang = $interwiki = $image = $site = '';
+ // Prefix ?
+ if (!empty($matches[2])) {
+ $prefix = explode(':', substr($matches[2], 0, -1));
+ $count = count($prefix);
+ $i = -1;
+ // Autolink
+ if (isset($this->conf['project']) &&
+ in_array(trim($prefix[0]), $this->conf['project'])) {
+ $auto = trim($prefix[0]);
+ unset($prefix[0]);
+ $i = 0;
+ }
+ while (++$i < $count) {
+ $prefix[$i] = trim($prefix[$i]);
+ // interlangage
+ if (!$interlang &&
+ in_array($prefix[$i], $this->interwikiConf['interlangage'])) {
+ $interlang = $prefix[$i];
+ unset($prefix[$i]);
+ continue;
+ }
+ // image
+ if (!$image && in_array($prefix[$i], $this->imageConf['prefix'])) {
+ $image = $prefix[$i];
+ unset($prefix[$i]);
+ break;
+ }
+ // interwiki
+ if (isset($this->interwikiConf['sites'][$prefix[$i]])) {
+ $interwiki = $this->interwikiConf['sites'][$prefix[$i]];
+ $site = $prefix[$i];
+ unset($prefix[$i]);
+ }
+ break;
+ }
+ if ($prefix) {
+ $matches[3] = implode(':', $prefix) . ':' . $matches[3];
+ }
+ }
+ $text = empty($matches[5]) ? $matches[3] : $matches[5];
+ $matches[3] = trim($matches[3]);
+ $matches[4] = empty($matches[4]) ? '' : trim($matches[4]);
+ if ($this->conf['spaceUnderscore']) {
+ $matches[3] = preg_replace('/\s+/', '_', $matches[3]);
+ $matches[4] = preg_replace('/\s+/', '_', $matches[4]);
+ }
+ if ($image) {
+ return $this->image($matches[3] . (empty($matches[4]) ? '' : '#' . $matches[4]),
+ $text, $interlang, $colon);
+ }
+ if (!$interwiki && $interlang && isset($this->conf['url'])) {
+ if ($interlang == $this->conf['langage']) {
+ $interlang = '';
+ } else {
+ $interwiki = $this->conf['url'];
+ $site = isset($this->conf['project']) ? $this->conf['project'][0] : '';
+ }
+ }
+ if ($interwiki) {
+ return $this->interwiki($site, $interwiki,
+ $matches[3] . (empty($matches[4]) ? '' : '#' . $matches[4]),
+ $text, $interlang, $colon);
+ }
+ if ($interlang) {
+ $matches[3] = $interlang . ':' . $matches[3];
+ $text = (empty($matches[5]) ? $interlang . ':' : '') . $text;
+ }
+
+ $start = $this->wiki->addToken($this->rule, array(
+ 'type' => 'start',
+ 'page' => $matches[3],
+ 'anchor' => (empty($matches[4]) ? '' : $matches[4]),
+ 'text' => $text
+ ));
+
+ $end = $this->wiki->addToken($this->rule, array(
+ 'type' => 'end',
+ 'page' => $matches[3],
+ 'anchor' => (empty($matches[4]) ? '' : $matches[4]),
+ 'text' => $text
+ ));
+
+ // create and return the replacement token
+ return $start . $text . $end;
+ }
+
+ /**
+ * Generates an image token. Token options are:
+ * - 'src' => the name of the image file
+ * - 'attr' => an array of attributes for the image:
+ * | - 'alt' => the optional alternate image text
+ * | - 'align => 'left', 'center' or 'right'
+ *
+ * @access public
+ * @param array &$matches The array of matches from parse().
+ * @return string token to be used as replacement
+ */
+
+ function image($name, $text, $interlang, $colon)
+ {
+ $attr = array('alt' => '');
+ // scan text for supplementary attibutes
+ if (strpos($text, '|') !== false) {
+ $splits = explode('|', $text);
+ $sep = '';
+ foreach ($splits as $split) {
+ switch (strtolower($split)) {
+ case 'left': case 'center': case 'right':
+ $attr['align'] = strtolower($split);
+ break;
+ default:
+ $attr['alt'] .= $sep . $split;
+ $sep = '|';
+ }
+ }
+ } else {
+ $attr['alt'] = $text;
+ }
+ $options = array(
+ 'src' => ($interlang ? $interlang . ':' : '') . $name,
+ 'attr' => $attr);
+
+ // create and return the replacement token
+ return $this->wiki->addToken('Image', $options);
+ }
+
+ /**
+ * Generates an interwiki token. Token options are:
+ * - 'page' => the name of the target wiki page
+ * - 'site' => the key for external site
+ * - 'url' => the full target url
+ * - 'text' => the optional alternate link text
+ *
+ * @access public
+ * @param array &$matches The array of matches from parse().
+ * @return string token to be used as replacement
+ */
+
+ function interwiki($site, $interwiki, $page, $text, $interlang, $colon)
+ {
+ if ($interlang) {
+ $interwiki = preg_replace('/\b' . $this->conf['langage'] . '\b/i',
+ $interlang, $interwiki);
+ }
+ // set the options
+ $options = array(
+ 'page' => $page,
+ 'site' => $site,
+ 'url' => sprintf($interwiki, $page),
+ 'text' => $text
+ );
+
+ // create and return the replacement token
+ return $this->wiki->addToken('Interwiki', $options);
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Parse/Default/Anchor.php b/includes/pear/Text/Wiki/Parse/Default/Anchor.php
new file mode 100644
index 0000000..83b57c6
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Anchor.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+*
+* Parses for anchor targets.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Manuel Holtgrewe <purestorm at ggnore dot net>
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Anchor.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* This class implements a Text_Wiki_Parse to add an anchor target name
+* in the wiki page.
+*
+* @author Manuel Holtgrewe <purestorm at ggnore dot net>
+*
+* @author Paul M. Jones <pmjones at ciaweb dot net>
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+*/
+
+class Text_Wiki_Parse_Anchor extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to find source text matching this
+ * rule. Looks like a macro: [[# anchor_name]]
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ var $regex = '/(\[\[# )([-_A-Za-z0-9.]+?)( .+)?(\]\])/i';
+
+
+ /**
+ *
+ * Generates a token entry for the matched text. Token options are:
+ *
+ * 'text' => The full matched text, not including the <code></code> tags.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token number to be used as a placeholder in
+ * the source text.
+ *
+ */
+
+ function process(&$matches) {
+
+ $name = $matches[2];
+ $text = $matches[3];
+
+ $start = $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'start', 'name' => $name)
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'end', 'name' => $name)
+ );
+
+ // done, place the script output directly in the source
+ return $start . trim($text) . $end;
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Parse/Default/Blockquote.php b/includes/pear/Text/Wiki/Parse/Default/Blockquote.php
new file mode 100644
index 0000000..61da770
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Blockquote.php
@@ -0,0 +1,180 @@
+<?php
+
+/**
+*
+* Parse for block-quoted text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Blockquote.php 222150 2006-10-21 05:56:28Z justinpatrin $
+*
+*/
+
+/**
+*
+* Parse for block-quoted text.
+*
+* Find source text marked as a blockquote, identified by any number of
+* greater-than signs '>' at the start of the line, followed by a space,
+* and then the quote text; each '>' indicates an additional level of
+* quoting.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Blockquote extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * Regex for parsing the source text.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/\n((\>).*\n)(?!(\>))/Us';
+
+
+ /**
+ *
+ * Generates a replacement for the matched text.
+ *
+ * Token options are:
+ *
+ * 'type' =>
+ * 'start' : the start of a blockquote
+ * 'end' : the end of a blockquote
+ *
+ * 'level' => the indent level (0 for the first level, 1 for the
+ * second, etc)
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A series of text and delimited tokens marking the different
+ * list text and list elements.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // the replacement text we will return to parse()
+ $return = "\n";
+
+ // the list of post-processing matches
+ $list = array();
+
+ // $matches[1] is the text matched as a list set by parse();
+ // create an array called $list that contains a new set of
+ // matches for the various list-item elements.
+ preg_match_all(
+ '=^(\>+) (.*\n)=Ums',
+ $matches[1],
+ $list,
+ PREG_SET_ORDER
+ );
+
+ $curLevel = 0;
+
+ // loop through each list-item element.
+ foreach ($list as $key => $val) {
+
+ // $val[0] is the full matched list-item line
+ // $val[1] is the number of initial '>' chars (indent level)
+ // $val[2] is the quote text
+
+ // we number levels starting at 1, not zero
+ $level = strlen($val[1]);
+
+ // add a level to the list?
+ while ($level > $curLevel) {
+ // the current indent level is greater than the number
+ // of stack elements, so we must be starting a new
+ // level. push the new level onto the stack with a
+ // dummy value (boolean true)...
+ ++$curLevel;
+
+ //$return .= "\n";
+
+ // ...and add a start token to the return.
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'start',
+ 'level' => $curLevel
+ )
+ );
+
+ //$return .= "\n\n";
+ }
+
+ // remove a level?
+ while ($curLevel > $level) {
+
+ // as long as the stack count is greater than the
+ // current indent level, we need to end list types.
+ // continue adding end-list tokens until the stack count
+ // and the indent level are the same.
+
+ //$return .= "\n\n";
+
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array (
+ 'type' => 'end',
+ 'level' => $curLevel
+ )
+ );
+
+ //$return .= "\n";
+ --$curLevel;
+ }
+
+ // add the line text.
+ $return .= $val[2];
+ }
+
+ // the last char of the matched pattern must be \n but we don't
+ // want this to be inside the tokens
+ $return = substr($return, 0, -1);
+
+ // the last line may have been indented. go through the stack
+ // and create end-tokens until the stack is empty.
+ //$return .= "\n";
+
+ while ($curLevel > 0) {
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array (
+ 'type' => 'end',
+ 'level' => $curLevel
+ )
+ );
+ --$curLevel;
+ }
+
+ // put back the trailing \n
+ $return .= "\n";
+
+ // we're done! send back the replacement text.
+ return $return;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Bold.php b/includes/pear/Text/Wiki/Parse/Default/Bold.php
new file mode 100644
index 0000000..ba5e965
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Bold.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+*
+* Parses for bold text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Bold.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for bold text.
+*
+* This class implements a Text_Wiki_Rule to find source text marked for
+* strong emphasis (bold) as defined by text surrounded by three
+* single-quotes. On parsing, the text itself is left in place, but the
+* starting and ending instances of three single-quotes are replaced with
+* tokens.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Bold extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/'''(()|[^'].*)'''/U";
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * emphasized text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A pair of delimited tokens to be used as a placeholder in
+ * the source text surrounding the text to be emphasized.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $start = $this->wiki->addToken($this->rule, array('type' => 'start'));
+ $end = $this->wiki->addToken($this->rule, array('type' => 'end'));
+ return $start . $matches[1] . $end;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Break.php b/includes/pear/Text/Wiki/Parse/Default/Break.php
new file mode 100644
index 0000000..1a684f1
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Break.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+*
+* Parses for explicit line breaks.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Break.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for explicit line breaks.
+*
+* This class implements a Text_Wiki_Parse to mark forced line breaks in the
+* source text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Break extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/ _\n/';
+
+
+ /**
+ *
+ * Generates a replacement token for the matched text.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A delimited token to be used as a placeholder in
+ * the source text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ return $this->wiki->addToken($this->rule);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Center.php b/includes/pear/Text/Wiki/Parse/Default/Center.php
new file mode 100644
index 0000000..6ddde51
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Center.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+*
+* Parses for centered lines of text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Center.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for centered lines of text.
+*
+* This class implements a Text_Wiki_Parse to find lines marked for centering.
+* The line must start with "= " (i.e., an equal-sign followed by a space).
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Center extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to find source text matching this
+ * rule.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ var $regex = '/\n\= (.*?)\n/';
+
+ /**
+ *
+ * Generates a token entry for the matched text.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token number to be used as a placeholder in
+ * the source text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $start = $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'start')
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'end')
+ );
+
+ return "\n" . $start . $matches[1] . $end . "\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Code.php b/includes/pear/Text/Wiki/Parse/Default/Code.php
new file mode 100644
index 0000000..fc2d3c1
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Code.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+*
+* Parses for text marked as a code example block.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Code.php 237313 2007-06-09 23:11:25Z justinpatrin $
+*
+*/
+
+/**
+*
+* Parses for text marked as a code example block.
+*
+* This class implements a Text_Wiki_Parse to find sections marked as code
+* examples. Blocks are marked as the string <code> on a line by itself,
+* followed by the inline code example, and terminated with the string
+* </code> on a line by itself. The code example is run through the
+* native PHP highlight_string() function to colorize it, then surrounded
+* with <pre>...</pre> tags when rendered as XHTML.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Code extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to find source text matching this
+ * rule.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+/* var $regex = '/^(\<code( .+)?\>)\n(.+)\n(\<\/code\>)(\s|$)/Umsi';*/
+ var $regex = ';^<code(\s[^>]*)?>((?:(?R)|.*?)*)\n</code>(\s|$);msi';
+
+ /**
+ *
+ * Generates a token entry for the matched text. Token options are:
+ *
+ * 'text' => The full matched text, not including the <code></code> tags.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token number to be used as a placeholder in
+ * the source text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // are there additional attribute arguments?
+ $args = trim($matches[1]);
+
+ if ($args == '') {
+ $options = array(
+ 'text' => $matches[2],
+ 'attr' => array('type' => '')
+ );
+ } else {
+ // get the attributes...
+ $attr = $this->getAttrs($args);
+
+ // ... and make sure we have a 'type'
+ if (! isset($attr['type'])) {
+ $attr['type'] = '';
+ }
+
+ // retain the options
+ $options = array(
+ 'text' => $matches[2],
+ 'attr' => $attr
+ );
+ }
+
+ return $this->wiki->addToken($this->rule, $options) . $matches[3];
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Parse/Default/Colortext.php b/includes/pear/Text/Wiki/Parse/Default/Colortext.php
new file mode 100644
index 0000000..5d82cd6
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Colortext.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+*
+* Parses for colorized text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Colortext.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for colorized text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Colortext extends Text_Wiki_Parse {
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/\#\#(.+?)\|(.+?)\#\#/";
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * emphasized text. The text itself is left in the source.
+ *
+ * 'color' => the color indicator
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A pair of delimited tokens to be used as a
+ * placeholder in the source text surrounding the text to be
+ * emphasized.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $start = $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'start',
+ 'color' => $matches[1]
+ )
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'end',
+ 'color' => $matches[1]
+ )
+ );
+
+ return $start . $matches[2] . $end;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Deflist.php b/includes/pear/Text/Wiki/Parse/Default/Deflist.php
new file mode 100644
index 0000000..a8ee0fb
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Deflist.php
@@ -0,0 +1,122 @@
+<?php
+
+/**
+*
+* Parses for definition lists.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Deflist.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for definition lists.
+*
+* This class implements a Text_Wiki_Parse to find source text marked as a
+* definition list. In short, if a line starts with ':' then it is a
+* definition list item; another ':' on the same line indicates the end
+* of the definition term and the beginning of the definition narrative.
+* The list items must be on sequential lines (no blank lines between
+* them) -- a blank line indicates the beginning of a new list.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Deflist extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/\n((: ).*\n)(?!(: |\n))/Us';
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' =>
+ * 'list_start' : the start of a definition list
+ * 'list_end' : the end of a definition list
+ * 'term_start' : the start of a definition term
+ * 'term_end' : the end of a definition term
+ * 'narr_start' : the start of definition narrative
+ * 'narr_end' : the end of definition narrative
+ * 'unknown' : unknown type of definition portion
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A series of text and delimited tokens marking the different
+ * list text and list elements.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // the replacement text we will return to parse()
+ $return = '';
+
+ // the list of post-processing matches
+ $list = array();
+
+ // start the deflist
+ $options = array('type' => 'list_start');
+ $return .= $this->wiki->addToken($this->rule, $options);
+
+ // $matches[1] is the text matched as a list set by parse();
+ // create an array called $list that contains a new set of
+ // matches for the various definition-list elements.
+ preg_match_all(
+ '/^(: )(.*)?( : )(.*)?$/Ums',
+ $matches[1],
+ $list,
+ PREG_SET_ORDER
+ );
+
+ // add each term and narrative
+ foreach ($list as $key => $val) {
+ $return .= (
+ $this->wiki->addToken($this->rule, array('type' => 'term_start')) .
+ trim($val[2]) .
+ $this->wiki->addToken($this->rule, array('type' => 'term_end')) .
+ $this->wiki->addToken($this->rule, array('type' => 'narr_start')) .
+ trim($val[4]) .
+ $this->wiki->addToken($this->rule, array('type' => 'narr_end'))
+ );
+ }
+
+
+ // end the deflist
+ $options = array('type' => 'list_end');
+ $return .= $this->wiki->addToken($this->rule, $options);
+
+ // done!
+ return "\n" . $return . "\n\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Delimiter.php b/includes/pear/Text/Wiki/Parse/Default/Delimiter.php
new file mode 100644
index 0000000..8aa4b53
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Delimiter.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+*
+* Parses for Text_Wiki delimiter characters already in the source text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Delimiter.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for Text_Wiki delimiter characters already in the source text.
+*
+* This class implements a Text_Wiki_Parse to find instances of the delimiter
+* character already embedded in the source text; it extracts them and replaces
+* them with a delimited token, then renders them as the delimiter itself
+* when the target format is XHTML.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Delimiter extends Text_Wiki_Parse {
+
+ /**
+ *
+ * Constructor. Overrides the Text_Wiki_Parse constructor so that we
+ * can set the $regex property dynamically (we need to include the
+ * Text_Wiki $delim character.
+ *
+ * @param object &$obj The calling "parent" Text_Wiki object.
+ *
+ * @param string $name The token name to use for this rule.
+ *
+ */
+
+ function Text_Wiki_Parse_delimiter(&$obj)
+ {
+ parent::Text_Wiki_Parse($obj);
+ $this->regex = '/' . $this->wiki->delim . '/';
+ }
+
+
+ /**
+ *
+ * Generates a token entry for the matched text. Token options are:
+ *
+ * 'text' => The full matched text.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token number to be used as a placeholder in
+ * the source text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ return $this->wiki->addToken(
+ $this->rule,
+ array('text' => $this->wiki->delim)
+ );
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Embed.php b/includes/pear/Text/Wiki/Parse/Default/Embed.php
new file mode 100644
index 0000000..2d31a71
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Embed.php
@@ -0,0 +1,106 @@
+<?php
+
+/**
+*
+* Embeds the results of a PHP script at render-time.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Embed.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Embeds the results of a PHP script at render-time.
+*
+* This class implements a Text_Wiki_Parse to embed the contents of a URL
+* inside the page at render-time. Typically used to get script output.
+* This differs from the 'include' rule, which incorporates results at
+* parse-time; 'embed' output does not get parsed by Text_Wiki, while
+* 'include' ouput does.
+*
+* This rule is inherently not secure; it allows cross-site scripting to
+* occur if the embedded output has <script> or other similar tags. Be
+* careful.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Embed extends Text_Wiki_Parse {
+
+ var $conf = array(
+ 'base' => '/path/to/scripts/'
+ );
+
+ var $file = null;
+
+ var $output = null;
+
+ var $vars = null;
+
+
+ /**
+ *
+ * The regular expression used to find source text matching this
+ * rule.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ var $regex = '/(\[\[embed )(.+?)( .+?)?(\]\])/i';
+
+
+ /**
+ *
+ * Generates a token entry for the matched text. Token options are:
+ *
+ * 'text' => The full matched text, not including the <code></code> tags.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token number to be used as a placeholder in
+ * the source text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // save the file location
+ $this->file = $this->getConf('base', './') . $matches[2];
+
+ // extract attribs as variables in the local space
+ $this->vars = $this->getAttrs($matches[3]);
+ unset($this->vars['this']);
+ extract($this->vars);
+
+ // run the script
+ ob_start();
+ include($this->file);
+ $this->output = ob_get_contents();
+ ob_end_clean();
+
+ // done, place the script output directly in the source
+ return $this->wiki->addToken(
+ $this->rule,
+ array('text' => $this->output)
+ );
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Emphasis.php b/includes/pear/Text/Wiki/Parse/Default/Emphasis.php
new file mode 100644
index 0000000..85dd212
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Emphasis.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+*
+* Parses for emphasized text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Emphasis.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for emphasized text.
+*
+* This class implements a Text_Wiki_Parse to find source text marked for
+* emphasis (italics) as defined by text surrounded by two single-quotes.
+* On parsing, the text itself is left in place, but the starting and ending
+* instances of two single-quotes are replaced with tokens.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_emphasis extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/\/\/(.*?)\/\//";
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * emphasized text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A pair of delimited tokens to be used as a
+ * placeholder in the source text surrounding the text to be
+ * emphasized.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $start = $this->wiki->addToken(
+ $this->rule, array('type' => 'start')
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule, array('type' => 'end')
+ );
+
+ return $start . $matches[1] . $end;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Freelink.php b/includes/pear/Text/Wiki/Parse/Default/Freelink.php
new file mode 100644
index 0000000..6cc0bc6
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Freelink.php
@@ -0,0 +1,134 @@
+<?php
+
+/**
+*
+* Parses for wiki freelink text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Freelink.php 224598 2006-12-08 08:23:51Z justinpatrin $
+*
+*/
+
+/**
+*
+* Parses for freelinked page links.
+*
+* This class implements a Text_Wiki_Parse to find source text marked as a
+* wiki freelink, and automatically create a link to that page.
+*
+* A freelink is any page name not conforming to the standard
+* StudlyCapsStyle for a wiki page name. For example, a page normally
+* named MyHomePage can be renamed and referred to as ((My Home Page)) --
+* note the spaces in the page name. You can also make a "nice-looking"
+* link without renaming the target page; e.g., ((MyHomePage|My Home
+* Page)). Finally, you can use named anchors on the target page:
+* ((MyHomePage|My Home Page#Section1)).
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Freelink extends Text_Wiki_Parse {
+
+ var $conf = array (
+ 'utf-8' => false
+ );
+
+ /**
+ *
+ * Constructor. We override the Text_Wiki_Parse constructor so we can
+ * explicitly comment each part of the $regex property.
+ *
+ * @access public
+ *
+ * @param object &$obj The calling "parent" Text_Wiki object.
+ *
+ */
+
+ function Text_Wiki_Parse_Freelink(&$obj)
+ {
+ parent::Text_Wiki_Parse($obj);
+ if ($this->getConf('utf-8')) {
+ $any = '\p{L}';
+ } else {
+ $any = '';
+ }
+ $this->regex =
+ '/' . // START regex
+ "\\(\\(" . // double open-parens
+ "(" . // START freelink page patter
+ "[-A-Za-z0-9 _+\\/.,;:!?'\"\\[\\]\\{\\}&".$any."\xc0-\xff]+" . // 1 or more of just about any character
+ ")" . // END freelink page pattern
+ "(" . // START display-name
+ "\|" . // a pipe to start the display name
+ "[-A-Za-z0-9 _+\\/.,;:!?'\"\\[\\]\\{\\}&".$any."\xc0-\xff]+" . // 1 or more of just about any character
+ ")?" . // END display-name pattern 0 or 1
+ "(" . // START pattern for named anchors
+ "\#" . // a hash mark
+ "[A-Za-z]" . // 1 alpha
+ "[-A-Za-z0-9_:.]*" . // 0 or more alpha, digit, underscore
+ ")?" . // END named anchors pattern 0 or 1
+ "()\\)\\)" . // double close-parens
+ '/'.($this->getConf('utf-8') ? 'u' : ''); // END regex
+ }
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'page' => the wiki page name (e.g., HomePage).
+ *
+ * 'text' => alternative text to be displayed in place of the wiki
+ * page name.
+ *
+ * 'anchor' => a named anchor on the target wiki page
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token to be used as a placeholder in
+ * the source text, plus any text priot to the match.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // use nice variable names
+ $page = $matches[1];
+ $text = $matches[2];
+ $anchor = $matches[3];
+
+ // is the page given a new text appearance?
+ if (trim($text) == '') {
+ // no
+ $text = $page;
+ } else {
+ // yes, strip the leading | character
+ $text = substr($text, 1);
+ }
+
+ // set the options
+ $options = array(
+ 'page' => $page,
+ 'text' => $text,
+ 'anchor' => $anchor
+ );
+
+ // return a token placeholder
+ return $this->wiki->addToken($this->rule, $options);
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Parse/Default/Function.php b/includes/pear/Text/Wiki/Parse/Default/Function.php
new file mode 100644
index 0000000..35580bf
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Function.php
@@ -0,0 +1,141 @@
+<?php
+
+/**
+*
+* Parses for an API function documentation block.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Function.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for an API function documentation block.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Function extends Text_Wiki_Parse {
+
+ var $regex = '/^(\<function\>)\n(.+)\n(\<\/function\>)(\s|$)/Umsi';
+
+ function process(&$matches)
+ {
+ // default options
+ $opts = array(
+ 'name' => null,
+ 'access' => null,
+ 'return' => null,
+ 'params' => array(),
+ 'throws' => array()
+ );
+
+ // split apart the markup lines and loop through them
+ $lines = explode("\n", $matches[2]);
+ foreach ($lines as $line) {
+
+ // skip blank lines
+ if (trim($line) == '') {
+ continue;
+ }
+
+ // find the first ':' on the line; the left part is the
+ // type, the right part is the value. skip lines without
+ // a ':' on them.
+ $pos = strpos($line, ':');
+ if ($pos === false) {
+ continue;
+ }
+
+ // $type is the line type: name, access, return, param, throws
+ // 012345678901234
+ // name: something
+ $type = trim(substr($line, 0, $pos));
+ $val = trim(substr($line, $pos+1));
+
+ switch($type) {
+
+ case 'a':
+ case 'access':
+ $opts['access'] = $val;
+ break;
+
+ case 'n':
+ case 'name':
+ $opts['name'] = $val;
+ break;
+
+ case 'p':
+ case 'param':
+ $tmp = explode(',', $val);
+ $k = count($tmp);
+ if ($k == 1) {
+ $opts['params'][] = array(
+ 'type' => $tmp[0],
+ 'descr' => null,
+ 'default' => null
+ );
+ } elseif ($k == 2) {
+ $opts['params'][] = array(
+ 'type' => $tmp[0],
+ 'descr' => $tmp[1],
+ 'default' => null
+ );
+ } else {
+ $opts['params'][] = array(
+ 'type' => $tmp[0],
+ 'descr' => $tmp[1],
+ 'default' => $tmp[2]
+ );
+ }
+ break;
+
+ case 'r':
+ case 'return':
+ case 'returns':
+ $opts['return'] = $val;
+ break;
+
+ case 't':
+ case 'throws':
+ $tmp = explode(',', $val);
+ $k = count($tmp);
+ if ($k == 1) {
+ $opts['throws'][] = array(
+ 'type' => $tmp[0],
+ 'descr' => null
+ );
+ } else {
+ $opts['throws'][] = array(
+ 'type' => $tmp[0],
+ 'descr' => $tmp[1]
+ );
+ }
+ break;
+
+ default:
+ $opts[$type] = $val;
+ break;
+
+ }
+ }
+
+ // add the token back in place
+ return $this->wiki->addToken($this->rule, $opts) . $matches[4];
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Heading.php b/includes/pear/Text/Wiki/Parse/Default/Heading.php
new file mode 100644
index 0000000..35727e9
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Heading.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+*
+* Parses for heading text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Heading.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for heading text.
+*
+* This class implements a Text_Wiki_Parse to find source text marked to
+* be a heading element, as defined by text on a line by itself prefixed
+* with a number of plus signs (+). The heading text itself is left in
+* the source, but is prefixed and suffixed with delimited tokens marking
+* the start and end of the heading.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Heading extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/^(\+{1,6}) (.*)/m';
+
+ var $conf = array(
+ 'id_prefix' => 'toc'
+ );
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * heading text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A pair of delimited tokens to be used as a
+ * placeholder in the source text surrounding the heading text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // keep a running count for header IDs. we use this later
+ // when constructing TOC entries, etc.
+ static $id;
+ if (! isset($id)) {
+ $id = 0;
+ }
+
+ $prefix = htmlspecialchars($this->getConf('id_prefix'));
+
+ $start = $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'start',
+ 'level' => strlen($matches[1]),
+ 'text' => $matches[2],
+ 'id' => $prefix . $id ++
+ )
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'end',
+ 'level' => strlen($matches[1])
+ )
+ );
+
+ return $start . $matches[2] . $end . "\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Horiz.php b/includes/pear/Text/Wiki/Parse/Default/Horiz.php
new file mode 100644
index 0000000..a63cb5f
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Horiz.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+*
+* Parses for horizontal ruling lines.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Horiz.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for horizontal ruling lines.
+*
+* This class implements a Text_Wiki_Parse to find source text marked to
+* be a horizontal rule, as defined by four dashed on their own line.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Horiz extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/^([-]{4,})$/m';
+
+
+ /**
+ *
+ * Generates a replacement token for the matched text.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A token marking the horizontal rule.
+ *
+ */
+
+ function process(&$matches)
+ {
+ return $this->wiki->addToken($this->rule);
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Html.php b/includes/pear/Text/Wiki/Parse/Default/Html.php
new file mode 100644
index 0000000..38e7389
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Html.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+*
+* Parses for blocks of HTML code.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Html.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for blocks of HTML code.
+*
+* This class implements a Text_Wiki_Parse to find source text marked as
+* HTML to be redndred as-is. The block start is marked by <html> on its
+* own line, and the block end is marked by </html> on its own line.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Html extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/^\<html\>\n(.+)\n\<\/html\>(\s|$)/Umsi';
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'text' => The text of the HTML to be rendered as-is.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token to be used as a placeholder in
+ * the source text, plus any text following the HTML block.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $options = array('text' => $matches[1]);
+ return $this->wiki->addToken($this->rule, $options) . $matches[2];
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Image.php b/includes/pear/Text/Wiki/Parse/Default/Image.php
new file mode 100644
index 0000000..5594644
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Image.php
@@ -0,0 +1,136 @@
+<?php
+
+/**
+*
+* Parses for image placement.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Image.php 195859 2005-09-12 11:34:44Z toggg $
+*
+*/
+
+/**
+*
+* Parses for image placement.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Image extends Text_Wiki_Parse {
+
+ /**
+ * URL schemes recognized by this rule.
+ *
+ * @access public
+ * @var array
+ */
+ var $conf = array(
+ 'schemes' => 'http|https|ftp|gopher|news',
+ 'host_regexp' => '(?:[^.\s/"\'<\\\#delim#\ca-\cz]+\.)*[a-z](?:[-a-z0-9]*[a-z0-9])?\.?',
+ 'path_regexp' => '(?:/[^\s"<\\\#delim#\ca-\cz]*)?'
+ );
+
+ /**
+ *
+ * The regular expression used to find source text matching this
+ * rule.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ var $regex = '/(\[\[image\s+)(.+?)(\]\])/i';
+
+
+ /**
+ * The regular expressions used to check ecternal urls
+ *
+ * @access public
+ * @var string
+ * @see parse()
+ */
+ var $url = '';
+
+ /**
+ * Constructor.
+ * We override the constructor to build up the url regex from config
+ *
+ * @param object &$obj the base conversion handler
+ * @return The parser object
+ * @access public
+ */
+ function Text_Wiki_Parse_Image(&$obj)
+ {
+ $default = $this->conf;
+ parent::Text_Wiki_Parse($obj);
+
+ // convert the list of recognized schemes to a regex OR,
+ $schemes = $this->getConf('schemes', $default['schemes']);
+ $this->url = str_replace( '#delim#', $this->wiki->delim,
+ '#(?:' . (is_array($schemes) ? implode('|', $schemes) : $schemes) . ')://'
+ . $this->getConf('host_regexp', $default['host_regexp'])
+ . $this->getConf('path_regexp', $default['path_regexp']) .'#');
+ }
+
+ /**
+ *
+ * Generates a token entry for the matched text. Token options are:
+ *
+ * 'src' => The image source, typically a relative path name.
+ *
+ * 'opts' => Any macro options following the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token number to be used as a placeholder in
+ * the source text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $pos = strpos($matches[2], ' ');
+
+ if ($pos === false) {
+ $options = array(
+ 'src' => $matches[2],
+ 'attr' => array());
+ } else {
+ // everything after the space is attribute arguments
+ $options = array(
+ 'src' => substr($matches[2], 0, $pos),
+ 'attr' => $this->getAttrs(substr($matches[2], $pos+1))
+ );
+ // check the scheme case of external link
+ if (array_key_exists('link', $options['attr'])) {
+ // external url ?
+ if (($pos = strpos($options['attr']['link'], '://')) !== false) {
+ if (!preg_match($this->url, $options['attr']['link'])) {
+ return $matches[0];
+ }
+ } elseif (in_array('Wikilink', $this->wiki->disable)) {
+ return $matches[0]; // Wikilink disabled
+ }
+ }
+ }
+
+ return $this->wiki->addToken($this->rule, $options);
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Parse/Default/Include.php b/includes/pear/Text/Wiki/Parse/Default/Include.php
new file mode 100644
index 0000000..c86b15e
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Include.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+*
+* Includes the contents of another PHP script into the source text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Include.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* This class implements a Text_Wiki_Parse to include the results of a
+* script directly into the source at parse-time; thus, the output of the
+* script will be parsed by Text_Wiki. This differs from the 'embed'
+* rule, which incorporates the results at render-time, meaning that the
+* 'embed' content is not parsed by Text_Wiki.
+*
+* DANGER!
+*
+* This rule is inherently not secure; it allows cross-site scripting to
+* occur if the embedded output has <script> or other similar tags. Be
+* careful.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Include extends Text_Wiki_Parse {
+
+ var $conf = array(
+ 'base' => '/path/to/scripts/'
+ );
+
+ var $file = null;
+
+ var $output = null;
+
+ var $vars = null;
+
+ /**
+ *
+ * The regular expression used to find source text matching this
+ * rule.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ var $regex = '/(\[\[include )(.+?)( .+?)?(\]\])/i';
+
+
+ /**
+ *
+ * Includes the results of the script directly into the source; the output
+ * will subsequently be parsed by the remaining Text_Wiki rules.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return The results of the included script.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // save the file location
+ $this->file = $this->getConf('base', './') . $matches[2];
+
+ // extract attribs as variables in the local space
+ $this->vars = $this->getAttrs($matches[3]);
+ unset($this->vars['this']);
+ extract($this->vars);
+
+ // run the script
+ ob_start();
+ include($this->file);
+ $this->output = ob_get_contents();
+ ob_end_clean();
+
+ // done, place the script output directly in the source
+ return $this->output;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Interwiki.php b/includes/pear/Text/Wiki/Parse/Default/Interwiki.php
new file mode 100644
index 0000000..e5f1d77
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Interwiki.php
@@ -0,0 +1,138 @@
+<?php
+
+/**
+*
+* Parses for interwiki links.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Interwiki.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for interwiki links.
+*
+* This class implements a Text_Wiki_Parse to find source text marked as
+* an Interwiki link. See the regex for a detailed explanation of the
+* text matching procedure; e.g., "InterWikiName:PageName".
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Interwiki extends Text_Wiki_Parse {
+
+ // double-colons wont trip up now
+ var $regex = '([A-Za-z0-9_]+):((?!:)[A-Za-z0-9_\/=&~#.:;-]+)';
+
+
+ /**
+ *
+ * Parser. We override the standard parser so we can
+ * find both described interwiki links and standalone links.
+ *
+ * @access public
+ *
+ * @return void
+ *
+ */
+
+ function parse()
+ {
+ // described interwiki links
+ $tmp_regex = '/\[' . $this->regex . ' (.+?)\]/';
+ $this->wiki->source = preg_replace_callback(
+ $tmp_regex,
+ array(&$this, 'processDescr'),
+ $this->wiki->source
+ );
+
+ // standalone interwiki links
+ $tmp_regex = '/' . $this->regex . '/';
+ $this->wiki->source = preg_replace_callback(
+ $tmp_regex,
+ array(&$this, 'process'),
+ $this->wiki->source
+ );
+
+ }
+
+
+ /**
+ *
+ * Generates a replacement for the matched standalone interwiki text.
+ * Token options are:
+ *
+ * 'site' => The key name for the Text_Wiki interwiki array map,
+ * usually the name of the interwiki site.
+ *
+ * 'page' => The page on the target interwiki to link to.
+ *
+ * 'text' => The text to display as the link.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token to be used as a placeholder in
+ * the source text, plus any text priot to the match.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $options = array(
+ 'site' => $matches[1],
+ 'page' => $matches[2],
+ 'text' => $matches[0]
+ );
+
+ return $this->wiki->addToken($this->rule, $options);
+ }
+
+
+ /**
+ *
+ * Generates a replacement for described interwiki links. Token
+ * options are:
+ *
+ * 'site' => The key name for the Text_Wiki interwiki array map,
+ * usually the name of the interwiki site.
+ *
+ * 'page' => The page on the target interwiki to link to.
+ *
+ * 'text' => The text to display as the link.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token to be used as a placeholder in
+ * the source text, plus any text priot to the match.
+ *
+ */
+
+ function processDescr(&$matches)
+ {
+ $options = array(
+ 'site' => $matches[1],
+ 'page' => $matches[2],
+ 'text' => $matches[3]
+ );
+
+ return $this->wiki->addToken($this->rule, $options);
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Italic.php b/includes/pear/Text/Wiki/Parse/Default/Italic.php
new file mode 100644
index 0000000..97ba9ff
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Italic.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+*
+* Parses for italic text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Italic.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for italic text.
+*
+* This class implements a Text_Wiki_Parse to find source text marked for
+* emphasis (italics) as defined by text surrounded by two single-quotes.
+* On parsing, the text itself is left in place, but the starting and ending
+* instances of two single-quotes are replaced with tokens.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Italic extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/''(()|[^'].*)''/U";
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * emphasized text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A pair of delimited tokens to be used as a
+ * placeholder in the source text surrounding the text to be
+ * emphasized.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $start = $this->wiki->addToken(
+ $this->rule, array('type' => 'start')
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule, array('type' => 'end')
+ );
+
+ return $start . $matches[1] . $end;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/List.php b/includes/pear/Text/Wiki/Parse/Default/List.php
new file mode 100644
index 0000000..0e6d466
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/List.php
@@ -0,0 +1,262 @@
+<?php
+
+/**
+*
+* Parses for bulleted and numbered lists.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: List.php 248434 2007-12-17 16:12:25Z justinpatrin $
+*
+*/
+
+/**
+*
+* Parses for bulleted and numbered lists.
+*
+* This class implements a Text_Wiki_Parse to find source text marked as
+* a bulleted or numbered list. In short, if a line starts with '* ' then
+* it is a bullet list item; if a line starts with '# ' then it is a
+* number list item. Spaces in front of the * or # indicate an indented
+* sub-list. The list items must be on sequential lines, and may be
+* separated by blank lines to improve readability. Using a non-* non-#
+* non-whitespace character at the beginning of a line ends the list.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_List extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/^((\*|#)\s.*\n)(?!\2\s|(?:\s+((?:\*|#) |\n)))/Usm';
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' =>
+ * 'bullet_start' : the start of a bullet list
+ * 'bullet_end' : the end of a bullet list
+ * 'number_start' : the start of a number list
+ * 'number_end' : the end of a number list
+ * 'item_start' : the start of item text (bullet or number)
+ * 'item_end' : the end of item text (bullet or number)
+ * 'unknown' : unknown type of list or item
+ *
+ * 'level' => the indent level (0 for the first level, 1 for the
+ * second, etc)
+ *
+ * 'count' => the list item number at this level. not needed for
+ * xhtml, but very useful for PDF and RTF.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A series of text and delimited tokens marking the different
+ * list text and list elements.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // the replacement text we will return
+ $return = '';
+
+ // the list of post-processing matches
+ $list = array();
+
+ // a stack of list-start and list-end types; we keep this
+ // so that we know what kind of list we're working with
+ // (bullet or number) and what indent level we're at.
+ $stack = array();
+
+ // the item count is the number of list items for any
+ // given list-type on the stack
+ $itemcount = array();
+
+ // have we processed the very first list item?
+ $pastFirst = false;
+
+ // populate $list with this set of matches. $matches[1] is the
+ // text matched as a list set by parse().
+ preg_match_all(
+ '=^(\s*)(\*|#)\s(.*)$=Ums',
+ $matches[1],
+ $list,
+ PREG_SET_ORDER
+ );
+
+ $numSpaces = 0;
+
+ // loop through each list-item element.
+ foreach ($list as $key => $val) {
+
+ // $val[0] is the full matched list-item line
+ // $val[1] is the number of initial spaces (indent level)
+ // $val[2] is the list item type (* or #)
+ // $val[3] is the list item text
+
+ // how many levels are we indented? (1 means the "root"
+ // list level, no indenting.)
+ $level = strlen($val[1]) + 1;
+
+ // get the list item type
+ if ($val[2] == '*') {
+ $type = 'bullet';
+ } elseif ($val[2] == '#') {
+ $type = 'number';
+ } else {
+ $type = 'unknown';
+ }
+
+ // get the text of the list item
+ $text = $val[3];
+
+ // add a level to the list?
+ if ($level > count($stack)) {
+
+ //watch for the same # of spaces and reset level
+ if ($level == $numSpaces) {
+ $level = count($stack);
+ } else {
+
+ $numSpaces = $level;
+
+ // reset level as sometimes people use too many spaces
+ $level = count($stack) + 1;
+
+ // the current indent level is greater than the
+ // number of stack elements, so we must be starting
+ // a new list. push the new list type onto the
+ // stack...
+ array_push($stack, $type);
+
+ // ...and add a list-start token to the return.
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => $type . '_list_start',
+ 'level' => $level - 1
+ )
+ );
+ }
+ }
+
+
+ // remove a level from the list?
+ while (count($stack) > $level) {
+
+ // so we don't keep counting the stack, we set up a temp
+ // var for the count. -1 becuase we're going to pop the
+ // stack in the next command. $tmp will then equal the
+ // current level of indent.
+ $tmp = count($stack) - 1;
+
+ // as long as the stack count is greater than the
+ // current indent level, we need to end list types.
+ // continue adding end-list tokens until the stack count
+ // and the indent level are the same.
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array (
+ 'type' => array_pop($stack) . '_list_end',
+ 'level' => $tmp
+ )
+ );
+
+ // reset to the current (previous) list type so that
+ // the new list item matches the proper list type.
+ $type = $stack[$tmp - 1];
+
+ // reset the item count for the popped indent level
+ unset($itemcount[$tmp + 1]);
+ }
+
+ // add to the item count for this list (taking into account
+ // which level we are at).
+ if (! isset($itemcount[$level])) {
+ // first count
+ $itemcount[$level] = 0;
+ } else {
+ // increment count
+ $itemcount[$level]++;
+ }
+
+ // is this the very first item in the list?
+ if (! $pastFirst) {
+ $first = true;
+ $pastFirst = true;
+ } else {
+ $first = false;
+ }
+
+ // create a list-item starting token.
+ $start = $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => $type . '_item_start',
+ 'level' => $level,
+ 'count' => $itemcount[$level],
+ 'first' => $first
+ )
+ );
+
+ // create a list-item ending token.
+ $end = $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => $type . '_item_end',
+ 'level' => $level,
+ 'count' => $itemcount[$level]
+ )
+ );
+
+ // add the starting token, list-item text, and ending token
+ // to the return.
+ $return .= $start . $val[3] . $end;
+ }
+
+ // the last list-item may have been indented. go through the
+ // list-type stack and create end-list tokens until the stack
+ // is empty.
+ while (count($stack) > 0) {
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array (
+ 'type' => array_pop($stack) . '_list_end',
+ 'level' => count($stack)
+ )
+ );
+ }
+
+ // we're done! send back the replacement text.
+ return "\n\n" . $return . "\n\n";
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Parse/Default/Newline.php b/includes/pear/Text/Wiki/Parse/Default/Newline.php
new file mode 100644
index 0000000..495b7d7
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Newline.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+*
+* Parses for implied line breaks indicated by newlines.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Newline.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for implied line breaks indicated by newlines.
+*
+* This class implements a Text_Wiki_Parse to mark implied line breaks in the
+* source text, usually a single carriage return in the middle of a paragraph
+* or block-quoted text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Newline extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/([^\n])\n([^\n])/m';
+
+
+ /**
+ *
+ * Generates a replacement token for the matched text.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A delimited token to be used as a placeholder in
+ * the source text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ return $matches[1] .
+ $this->wiki->addToken($this->rule) .
+ $matches[2];
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Paragraph.php b/includes/pear/Text/Wiki/Parse/Default/Paragraph.php
new file mode 100644
index 0000000..e748a11
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Paragraph.php
@@ -0,0 +1,116 @@
+<?php
+
+/**
+*
+* Parses for paragraph blocks.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Paragraph.php 286814 2009-08-04 17:03:17Z rodrigosprimo $
+*
+*/
+
+/**
+*
+* Parses for paragraph blocks.
+*
+* This class implements a Text_Wiki rule to find sections of the source
+* text that are paragraphs. A para is any line not starting with a token
+* delimiter, followed by two newlines.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Paragraph extends Text_Wiki_Parse {
+
+ /**
+ *
+ * The regular expression used to find source text matching this
+ * rule.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ var $regex = "/^.*?\n\n/m";
+
+ var $conf = array(
+ 'skip' => array(
+ 'blockquote', // are we sure about this one?
+ 'code',
+ 'heading',
+ 'horiz',
+ 'deflist',
+ 'table',
+ 'list',
+ 'toc'
+ )
+ );
+
+
+ /**
+ *
+ * Generates a token entry for the matched text. Token options are:
+ *
+ * 'start' => The starting point of the paragraph.
+ *
+ * 'end' => The ending point of the paragraph.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token number to be used as a placeholder in
+ * the source text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $delim = $this->wiki->delim;
+ $skip = $this->getConf('skip', array());
+
+ // was anything there?
+ if (trim($matches[0]) == '') {
+ return '';
+ }
+
+ // does the match has tokens inside?
+ preg_match_all("/(?:$delim)(\d+?)(?:$delim)/", $matches[0], $delimiters, PREG_SET_ORDER);
+
+ // look each delimiter inside the match and see if it's skippable
+ // (if we skip, it will not be marked as a paragraph)
+ foreach ($delimiters as $d) {
+ $token_type = strtolower($this->wiki->tokens[$d[1]][0]);
+ if (in_array($token_type, $skip)) {
+ return $matches[0];
+ }
+ }
+
+ // if there is no skipable token inside the match
+ // add the Paragraph token and return
+ $start = $this->wiki->addToken(
+ $this->rule, array('type' => 'start')
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule, array('type' => 'end')
+ );
+
+ return $start . trim($matches[0]) . $end;
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Parse/Default/Phplookup.php b/includes/pear/Text/Wiki/Parse/Default/Phplookup.php
new file mode 100644
index 0000000..7028ec1
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Phplookup.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+*
+* Find source text marked for lookup in the PHP online manual.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Phplookup.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Find source text marked for lookup in the PHP online manual.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Phplookup extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/\[\[php (.+?)\]\]/";
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * teletype text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A pair of delimited tokens to be used as a
+ * placeholder in the source text surrounding the teletype text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ return $this->wiki->addToken(
+ $this->rule, array('text' => $matches[1])
+ );
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Prefilter.php b/includes/pear/Text/Wiki/Parse/Default/Prefilter.php
new file mode 100644
index 0000000..db20071
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Prefilter.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+*
+* "Pre-filter" the source text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Prefilter.php 224599 2006-12-08 08:30:37Z justinpatrin $
+*
+*/
+
+/**
+*
+* "Pre-filter" the source text.
+*
+* Convert DOS and Mac line endings to Unix, concat lines ending in a
+* backslash \ with the next line, convert tabs to 4-spaces, add newlines
+* to the top and end of the source text, compress 3 or more newlines to
+* 2 newlines.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Prefilter extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * Simple parsing method.
+ *
+ * @access public
+ *
+ */
+
+ function parse()
+ {
+ // convert DOS line endings
+ $this->wiki->source = str_replace("\r\n", "\n",
+ $this->wiki->source);
+
+ // convert Macintosh line endings
+ $this->wiki->source = str_replace("\r", "\n",
+ $this->wiki->source);
+
+ // concat lines ending in a backslash
+ $this->wiki->source = str_replace("\\\n", "",
+ $this->wiki->source);
+
+ // convert tabs to four-spaces
+ $this->wiki->source = str_replace("\t", " ",
+ $this->wiki->source);
+
+ // add extra newlines at the top and end; this
+ // seems to help many rules.
+ $this->wiki->source = "\n" . $this->wiki->source . "\n\n";
+
+ $this->wiki->source = str_replace("\n----\n","\n\n----\n\n",
+ $this->wiki->source);
+ $this->wiki->source = preg_replace("/\n(\\+{1,6})(.*)\n/m",
+ "\n\n\\1 \\2\n\n",
+ $this->wiki->source);
+
+ // finally, compress all instances of 3 or more newlines
+ // down to two newlines.
+ $find = "/\n{3,}/m";
+ $replace = "\n\n";
+ $this->wiki->source = preg_replace($find, $replace,
+ $this->wiki->source);
+ }
+
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Raw.php b/includes/pear/Text/Wiki/Parse/Default/Raw.php
new file mode 100644
index 0000000..c4f1b53
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Raw.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+*
+* Parses for text marked as "raw" (i.e., to be rendered as-is).
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Raw.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for text marked as "raw" (i.e., to be rendered as-is).
+*
+* This class implements a Text_Wiki rule to find sections of the source
+* text that are not to be processed by Text_Wiki. These blocks of "raw"
+* text will be rendered as they were found.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Raw extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to find source text matching this
+ * rule.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ var $regex = "/``(.*)``/U";
+
+
+ /**
+ *
+ * Generates a token entry for the matched text. Token options are:
+ *
+ * 'text' => The full matched text.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token number to be used as a placeholder in
+ * the source text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $options = array('text' => $matches[1]);
+ return $this->wiki->addToken($this->rule, $options);
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Revise.php b/includes/pear/Text/Wiki/Parse/Default/Revise.php
new file mode 100644
index 0000000..28bf9fd
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Revise.php
@@ -0,0 +1,145 @@
+<?php
+
+/**
+*
+* Parses for text marked as revised (insert/delete).
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Revise.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for text marked as revised (insert/delete).
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Revise extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/\@\@({*?.*}*?)\@\@/U";
+
+
+ /**
+ *
+ * Config options.
+ *
+ * @access public
+ *
+ * @var array
+ *
+ */
+
+ var $conf = array(
+ 'delmark' => '---',
+ 'insmark' => '+++'
+ );
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * inserted text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A pair of delimited tokens to be used as a
+ * placeholder in the source text surrounding the teletype text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $output = '';
+ $src = $matches[1];
+ $delmark = $this->getConf('delmark'); // ---
+ $insmark = $this->getConf('insmark'); // +++
+
+ // '---' must be before '+++' (if they both appear)
+ $del = strpos($src, $delmark);
+ $ins = strpos($src, $insmark);
+
+ // if neither is found, return right away
+ if ($del === false && $ins === false) {
+ return $matches[0];
+ }
+
+ // handle text to be deleted
+ if ($del !== false) {
+
+ // move forward to the end of the deletion mark
+ $del += strlen($delmark);
+
+ if ($ins === false) {
+ // there is no insertion text following
+ $text = substr($src, $del);
+ } else {
+ // there is insertion text following,
+ // mitigate the length
+ $text = substr($src, $del, $ins - $del);
+ }
+
+ $output .= $this->wiki->addToken(
+ $this->rule, array('type' => 'del_start')
+ );
+
+ $output .= $text;
+
+ $output .= $this->wiki->addToken(
+ $this->rule, array('type' => 'del_end')
+ );
+ }
+
+ // handle text to be inserted
+ if ($ins !== false) {
+
+ // move forward to the end of the insert mark
+ $ins += strlen($insmark);
+ $text = substr($src, $ins);
+
+ $output .= $this->wiki->addToken(
+ $this->rule, array('type' => 'ins_start')
+ );
+
+ $output .= $text;
+
+ $output .= $this->wiki->addToken(
+ $this->rule, array('type' => 'ins_end')
+ );
+ }
+
+ return $output;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Smiley.php b/includes/pear/Text/Wiki/Parse/Default/Smiley.php
new file mode 100644
index 0000000..358af6c
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Smiley.php
@@ -0,0 +1,157 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Default: Parses for smileys / emoticons tags
+ *
+ * This class implements a Text_Wiki_Rule to find source text marked as
+ * smileys defined by symbols as ':)' , ':-)' or ':smile:'
+ * The symbol is replaced with a token.
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Smiley.php 197527 2005-10-04 08:17:51Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * Smiley rule parser class for Default.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ * @see Text_Wiki_Parse::Text_Wiki_Parse()
+ */
+class Text_Wiki_Parse_Smiley extends Text_Wiki_Parse {
+
+ /**
+ * Configuration keys for this rule
+ * 'smileys' => array Smileys recognized by this rule, symbols key definitions:
+ * 'symbol' => array ( 'name', 'description' [, 'variante', ...] ) as
+ * ':)' => array('smile', 'Smile'),
+ * ':D' => array('biggrin', 'Very Happy',':grin:'),
+ * the eventual elements after symbol and description are variantes
+ *
+ * 'auto_nose' => boolean enabling the auto nose feature:
+ * auto build a variante for 2 chars symbols by inserting a '-' as ':)' <=> ':-)'
+ *
+ * @access public
+ * @var array 'config-key' => mixed config-value
+ */
+ var $conf = array(
+ 'smileys' => array(
+ ':D' => array('biggrin', 'Very Happy', ':grin:'),
+ ':)' => array('smile', 'Smile', '(:'),
+ ':(' => array('sad', 'Sad', '):'),
+ ':o' => array('surprised', 'Surprised', ':eek:', 'o:'),
+ ':shock:' => array('eek', 'Shocked'),
+ ':?' => array('confused', 'Confused', ':???:'),
+ '8)' => array('cool', 'Cool', '(8'),
+ ':lol:' => array('lol', 'Laughing'),
+ ':x' => array('mad', 'Mad'),
+ ':P' => array('razz', 'Razz'),
+ ':oops:' => array('redface', 'Embarassed'),
+ ':cry:' => array('cry', 'Crying or Very sad'),
+ ':evil:' => array('evil', 'Evil or Very Mad'),
+ ':twisted:' => array('twisted', 'Twisted Evil'),
+ ':roll:' => array('rolleyes', 'Rolling Eyes'),
+ ';)' => array('wink', 'Wink', '(;'),
+ ':!:' => array('exclaim', 'Exclamation'),
+ ':?:' => array('question', 'Question'),
+ ':idea:' => array('idea', 'Idea'),
+ ':arrow:' => array('arrow', 'Arrow'),
+ ':|' => array('neutral', 'Neutral', '|:'),
+ ':mrgreen:' => array('mrgreen', 'Mr. Green'),
+ ),
+ 'auto_nose' => true
+ );
+
+ /**
+ * Definition array of smileys, variantes references their model
+ * 'symbol' => array ( 'name', 'description')
+ *
+ * @access private
+ * @var array 'config-key' => mixed config-value
+ */
+ var $_smileys = array();
+
+ /**
+ * Constructor.
+ * We override the constructor to build up the regex from config
+ *
+ * @param object &$obj the base conversion handler
+ * @return The parser object
+ * @access public
+ */
+ function Text_Wiki_Parse_Smiley(&$obj)
+ {
+ $default = $this->conf;
+ parent::Text_Wiki_Parse($obj);
+
+ // read the list of smileys to sort out variantes and :xxx: while building the regexp
+ $this->_smileys = $this->getConf('smileys', $default['smileys']);
+ $autoNose = $this->getConf('auto_nose', $default['auto_nose']);
+ $reg1 = $reg2 = '';
+ $sep1 = ':(?:';
+ $sep2 = '';
+ foreach ($this->_smileys as $smiley => $def) {
+ for ($i = 1; $i < count($def); $i++) {
+ if ($i > 1) {
+ $cur = $def[$i];
+ $this->_smileys[$cur] = &$this->_smileys[$smiley];
+ } else {
+ $cur = $smiley;
+ }
+ $len = strlen($cur);
+ if (($cur{0} == ':') && ($len > 2) && ($cur{$len - 1} == ':')) {
+ $reg1 .= $sep1 . preg_quote(substr($cur, 1, -1), '#');
+ $sep1 = '|';
+ continue;
+ }
+ if ($autoNose && ($len === 2)) {
+ $variante = $cur{0} . '-' . $cur{1};
+ $this->_smileys[$variante] = &$this->_smileys[$smiley];
+ $cur = preg_quote($cur{0}, '#') . '-?' . preg_quote($cur{1}, '#');
+ } else {
+ $cur = preg_quote($cur, '#');
+ }
+ $reg2 .= $sep2 . $cur;
+ $sep2 = '|';
+ }
+ }
+ $delim = '[\n\r\s' . $this->wiki->delim . '$^]';
+ $this->regex = '#(?<=' . $delim .
+ ')(' . ($reg1 ? $reg1 . '):' . ($reg2 ? '|' : '') : '') . $reg2 .
+ ')(?=' . $delim . ')#i';
+ }
+
+ /**
+ * Generates a replacement token for the matched text. Token options are:
+ * 'symbol' => the original marker
+ * 'name' => the name of the smiley
+ * 'desc' => the description of the smiley
+ *
+ * @param array &$matches The array of matches from parse().
+ * @return string Delimited token representing the smiley
+ * @access public
+ */
+ function process(&$matches)
+ {
+ // tokenize
+ return $this->wiki->addToken($this->rule,
+ array(
+ 'symbol' => $matches[1],
+ 'name' => $this->_smileys[$matches[1]][0],
+ 'desc' => $this->_smileys[$matches[1]][1]
+ ));
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Parse/Default/Strong.php b/includes/pear/Text/Wiki/Parse/Default/Strong.php
new file mode 100644
index 0000000..3fc4595
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Strong.php
@@ -0,0 +1,86 @@
+<?php
+
+/**
+*
+* Parses for strongly-emphasized text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Strong.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+
+/**
+*
+* Parses for strongly-emphasized text.
+*
+* This class implements a Text_Wiki_Parse to find source text marked for
+* strong emphasis (bold) as defined by text surrounded by three
+* single-quotes. On parsing, the text itself is left in place, but the
+* starting and ending instances of three single-quotes are replaced with
+* tokens.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Strong extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/\*\*(.*?)\*\*/";
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * emphasized text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A pair of delimited tokens to be used as a placeholder in
+ * the source text surrounding the text to be emphasized.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $start = $this->wiki->addToken(
+ $this->rule, array('type' => 'start')
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule, array('type' => 'end')
+ );
+
+ return $start . $matches[1] . $end;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Subscript.php b/includes/pear/Text/Wiki/Parse/Default/Subscript.php
new file mode 100644
index 0000000..eb75c14
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Subscript.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+*
+* Parses for subscripted text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Subscript.php 180691 2005-02-24 17:24:56Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for subscripted text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Subscript extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/,,(()|.*),,/U";
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * emphasized text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A pair of delimited tokens to be used as a placeholder in
+ * the source text surrounding the text to be emphasized.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $start = $this->wiki->addToken(
+ $this->rule, array('type' => 'start')
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule, array('type' => 'end')
+ );
+
+ return $start . $matches[1] . $end;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Superscript.php b/includes/pear/Text/Wiki/Parse/Default/Superscript.php
new file mode 100644
index 0000000..e63c190
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Superscript.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+*
+* Parses for superscripted text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Superscript.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for superscripted text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Superscript extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/\^\^(()|.*)\^\^/U";
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * emphasized text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A pair of delimited tokens to be used as a placeholder in
+ * the source text surrounding the text to be emphasized.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $start = $this->wiki->addToken(
+ $this->rule, array('type' => 'start')
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule, array('type' => 'end')
+ );
+
+ return $start . $matches[1] . $end;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Table.php b/includes/pear/Text/Wiki/Parse/Default/Table.php
new file mode 100644
index 0000000..81168b5
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Table.php
@@ -0,0 +1,226 @@
+<?php
+
+/**
+*
+* Parses for table markup.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Table.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Parses for table markup.
+*
+* This class implements a Text_Wiki_Parse to find source text marked as a
+* set of table rows, where a line start and ends with double-pipes (||)
+* and uses double-pipes to separate table cells. The rows must be on
+* sequential lines (no blank lines between them) -- a blank line
+* indicates the beginning of a new table.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Table extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = '/\n((\|\|).*)(\n)(?!(\|\|))/Us';
+
+
+ /**
+ *
+ * Generates a replacement for the matched text.
+ *
+ * Token options are:
+ *
+ * 'type' =>
+ * 'table_start' : the start of a bullet list
+ * 'table_end' : the end of a bullet list
+ * 'row_start' : the start of a number list
+ * 'row_end' : the end of a number list
+ * 'cell_start' : the start of item text (bullet or number)
+ * 'cell_end' : the end of item text (bullet or number)
+ *
+ * 'cols' => the number of columns in the table (for 'table_start')
+ *
+ * 'rows' => the number of rows in the table (for 'table_start')
+ *
+ * 'span' => column span (for 'cell_start')
+ *
+ * 'attr' => column attribute flag (for 'cell_start')
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A series of text and delimited tokens marking the different
+ * table elements and cell text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // our eventual return value
+ $return = '';
+
+ // the number of columns in the table
+ $num_cols = 0;
+
+ // the number of rows in the table
+ $num_rows = 0;
+
+ // rows are separated by newlines in the matched text
+ $rows = explode("\n", $matches[1]);
+
+ // loop through each row
+ foreach ($rows as $row) {
+
+ // increase the row count
+ $num_rows ++;
+
+ // start a new row
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'row_start')
+ );
+
+ // cells are separated by double-pipes
+ $cell = explode("||", $row);
+
+ // get the number of cells (columns) in this row
+ $last = count($cell) - 1;
+
+ // is this more than the current column count?
+ // (we decrease by 1 because we never use cell zero)
+ if ($last - 1 > $num_cols) {
+ // increase the column count
+ $num_cols = $last - 1;
+ }
+
+ // by default, cells span only one column (their own)
+ $span = 1;
+
+ // ignore cell zero, and ignore the "last" cell; cell zero
+ // is before the first double-pipe, and the "last" cell is
+ // after the last double-pipe. both are always empty.
+ for ($i = 1; $i < $last; $i ++) {
+
+ // if there is no content at all, then it's an instance
+ // of two sets of || next to each other, indicating a
+ // span.
+ if ($cell[$i] == '') {
+
+ // add to the span and loop to the next cell
+ $span += 1;
+ continue;
+
+ } else {
+
+ // this cell has content.
+
+ // find any special "attr"ibute cell markers
+ if (substr($cell[$i], 0, 2) == '> ') {
+ // right-align
+ $attr = 'right';
+ $cell[$i] = substr($cell[$i], 2);
+ } elseif (substr($cell[$i], 0, 2) == '= ') {
+ // center-align
+ $attr = 'center';
+ $cell[$i] = substr($cell[$i], 2);
+ } elseif (substr($cell[$i], 0, 2) == '< ') {
+ // left-align
+ $attr = 'left';
+ $cell[$i] = substr($cell[$i], 2);
+ } elseif (substr($cell[$i], 0, 2) == '~ ') {
+ $attr = 'header';
+ $cell[$i] = substr($cell[$i], 2);
+ } else {
+ $attr = null;
+ }
+
+ // start a new cell...
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array (
+ 'type' => 'cell_start',
+ 'attr' => $attr,
+ 'span' => $span
+ )
+ );
+
+ // ...add the content...
+ $return .= trim($cell[$i]);
+
+ // ...and end the cell.
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array (
+ 'type' => 'cell_end',
+ 'attr' => $attr,
+ 'span' => $span
+ )
+ );
+
+ // reset the span.
+ $span = 1;
+ }
+
+ }
+
+ // end the row
+ $return .= $this->wiki->addToken(
+ $this->rule,
+ array('type' => 'row_end')
+ );
+
+ }
+
+ // wrap the return value in start and end tokens
+ $return =
+ $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'table_start',
+ 'rows' => $num_rows,
+ 'cols' => $num_cols
+ )
+ )
+ . $return .
+ $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'table_end'
+ )
+ );
+
+ // we're done!
+ return "\n$return\n\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Tighten.php b/includes/pear/Text/Wiki/Parse/Default/Tighten.php
new file mode 100644
index 0000000..a03b51f
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Tighten.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+*
+* The rule removes all remaining newlines.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Tighten.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+
+/**
+*
+* The rule removes all remaining newlines.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Tighten extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * Apply tightening directly to the source text.
+ *
+ * @access public
+ *
+ */
+
+ function parse()
+ {
+ $this->wiki->source = str_replace("\n", '',
+ $this->wiki->source);
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Toc.php b/includes/pear/Text/Wiki/Parse/Default/Toc.php
new file mode 100644
index 0000000..dd29a57
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Toc.php
@@ -0,0 +1,130 @@
+<?php
+
+/**
+*
+* Looks through parsed text and builds a table of contents.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Toc.php 187179 2005-05-28 21:33:02Z pmjones $
+*
+*/
+
+/**
+*
+* Looks through parsed text and builds a table of contents.
+*
+* This class implements a Text_Wiki_Parse to find all heading tokens and
+* build a table of contents. The [[toc]] tag gets replaced with a list
+* of all the level-2 through level-6 headings.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+
+class Text_Wiki_Parse_Toc extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/\n\[\[toc( .*)?\]\]\n/m";
+
+
+ /**
+ *
+ * Generates a replacement for the matched text.
+ *
+ * Token options are:
+ *
+ * 'type' => ['list_start'|'list_end'|'item_start'|'item_end'|'target']
+ *
+ * 'level' => The heading level (1-6).
+ *
+ * 'count' => Which entry number this is in the list.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A token indicating the TOC collection point.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $count = 0;
+
+ if (isset($matches[1])) {
+ $attr = $this->getAttrs(trim($matches[1]));
+ } else {
+ $attr = array();
+ }
+
+ $output = $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'list_start',
+ 'level' => 0,
+ 'attr' => $attr
+ )
+ );
+
+ foreach ($this->wiki->getTokens('Heading') as $key => $val) {
+
+ if ($val[1]['type'] != 'start') {
+ continue;
+ }
+
+ $options = array(
+ 'type' => 'item_start',
+ 'id' => $val[1]['id'],
+ 'level' => $val[1]['level'],
+ 'count' => $count ++
+ );
+
+ $output .= $this->wiki->addToken($this->rule, $options);
+
+ $output .= $val[1]['text'];
+
+ $output .= $this->wiki->addToken(
+ $this->rule,
+ array(
+ 'type' => 'item_end',
+ 'level' => $val[1]['level']
+ )
+ );
+ }
+
+ $output .= $this->wiki->addToken(
+ $this->rule, array(
+ 'type' => 'list_end',
+ 'level' => 0
+ )
+ );
+
+ return "\n$output\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Tt.php b/includes/pear/Text/Wiki/Parse/Default/Tt.php
new file mode 100644
index 0000000..6fb7a7f
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Tt.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+*
+* Find source text marked for teletype (monospace).
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Tt.php 180591 2005-02-23 17:38:29Z pmjones $
+*
+*/
+
+/**
+*
+* Find source text marked for teletype (monospace).
+*
+* Defined by text surrounded by two curly braces. On parsing, the text
+* itself is left in place, but the starting and ending instances of
+* curly braces are replaced with tokens.
+*
+* Token options are:
+*
+* 'type' => ['start'|'end'] The starting or ending point of the
+* teletype text. The text itself is left in the source.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Tt extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/{{({*?.*}*?)}}/U";
+
+
+ /**
+ *
+ * Generates a replacement for the matched text.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return string A pair of delimited tokens to be used as a
+ * placeholder in the source text surrounding the teletype text.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $start = $this->wiki->addToken(
+ $this->rule, array('type' => 'start')
+ );
+
+ $end = $this->wiki->addToken(
+ $this->rule, array('type' => 'end')
+ );
+
+ return $start . $matches[1] . $end;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Underline.php b/includes/pear/Text/Wiki/Parse/Default/Underline.php
new file mode 100644
index 0000000..dc04de4
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Underline.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+*
+* Parses for bold text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Underline.php 190446 2005-07-10 20:40:20Z justinpatrin $
+*
+*/
+
+/**
+*
+* Parses for bold text.
+*
+* This class implements a Text_Wiki_Rule to find source text marked for
+* strong emphasis (bold) as defined by text surrounded by three
+* single-quotes. On parsing, the text itself is left in place, but the
+* starting and ending instances of three single-quotes are replaced with
+* tokens.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Underline extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * The regular expression used to parse the source text and find
+ * matches conforming to this rule. Used by the parse() method.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ * @see parse()
+ *
+ */
+
+ var $regex = "/__(()|[^_].*)__/U";
+
+
+ /**
+ *
+ * Generates a replacement for the matched text. Token options are:
+ *
+ * 'type' => ['start'|'end'] The starting or ending point of the
+ * emphasized text. The text itself is left in the source.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A pair of delimited tokens to be used as a placeholder in
+ * the source text surrounding the text to be emphasized.
+ *
+ */
+
+ function process(&$matches)
+ {
+ $start = $this->wiki->addToken($this->rule, array('type' => 'start'));
+ $end = $this->wiki->addToken($this->rule, array('type' => 'end'));
+ return $start . $matches[1] . $end;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Url.php b/includes/pear/Text/Wiki/Parse/Default/Url.php
new file mode 100644
index 0000000..7a6f08c
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Url.php
@@ -0,0 +1,281 @@
+<?php
+
+/**
+*
+* Parse for URLS in the source text.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Url.php 293784 2010-01-20 18:48:09Z justinpatrin $
+*
+*/
+
+/**
+*
+* Parse for URLS in the source text.
+*
+* Various URL markings are supported: inline (the URL by itself),
+* numbered or footnote reference (where the URL is enclosed in square
+* brackets), and named reference (where the URL is enclosed in square
+* brackets and has a name included inside the brackets). E.g.:
+*
+* inline -- http://example.com
+* numbered -- [http://example.com]
+* described -- [http://example.com Example Description]
+*
+* When rendering a URL token, this will convert URLs pointing to a .gif,
+* .jpg, or .png image into an inline <img /> tag (for the 'xhtml'
+* format).
+*
+* Token options are:
+*
+* 'type' => ['inline'|'footnote'|'descr'] the type of URL
+*
+* 'href' => the URL link href portion
+*
+* 'text' => the displayed text of the URL link
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Url extends Text_Wiki_Parse {
+
+
+ /**
+ *
+ * Keeps a running count of numbered-reference URLs.
+ *
+ * @access public
+ *
+ * @var int
+ *
+ */
+
+ var $footnoteCount = 0;
+
+
+ /**
+ *
+ * URL schemes recognized by this rule.
+ *
+ * @access public
+ *
+ * @var array
+ *
+ */
+
+ var $conf = array(
+ 'schemes' => array(
+ 'http://',
+ 'https://',
+ 'ftp://',
+ 'gopher://',
+ 'news://',
+ 'mailto:'
+ )
+ );
+
+
+ /**
+ *
+ * Constructor.
+ *
+ * We override the constructor so we can comment the regex nicely.
+ *
+ * @access public
+ *
+ */
+
+ function Text_Wiki_Parse_Url(&$obj)
+ {
+ parent::Text_Wiki_Parse($obj);
+
+ // convert the list of recognized schemes to a regex-safe string,
+ // where the pattern delim is a slash
+ $tmp = array();
+ $list = $this->getConf('schemes', array());
+ foreach ($list as $val) {
+ $tmp[] = preg_quote($val, '/');
+ }
+ $schemes = implode('|', $tmp);
+
+ // build the regex
+ $this->regex =
+ "($schemes)" . // allowed schemes
+ "(" . // start pattern
+ "[^ \\/\"\'{$this->wiki->delim}]*\\/" . // no spaces, backslashes, slashes, double-quotes, single quotes, or delimiters;
+ ")*" . // end pattern
+ "[^ \\t\\n\\/\"\'{$this->wiki->delim}]*" .
+ "[A-Za-z0-9\\/?=&~_#]";
+ }
+
+
+ /**
+ *
+ * Find three different kinds of URLs in the source text.
+ *
+ * @access public
+ *
+ */
+
+ function parse()
+ {
+ // -------------------------------------------------------------
+ //
+ // Described-reference (named) URLs.
+ //
+
+ // the regular expression for this kind of URL
+ $tmp_regex = '/\[(' . $this->regex . ') ([^\]]+)\]/';
+
+ // use a custom callback processing method to generate
+ // the replacement text for matches.
+ $this->wiki->source = preg_replace_callback(
+ $tmp_regex,
+ array(&$this, 'processDescr'),
+ $this->wiki->source
+ );
+
+
+ // -------------------------------------------------------------
+ //
+ // Numbered-reference (footnote-style) URLs.
+ //
+
+ // the regular expression for this kind of URL
+ $tmp_regex = '/\[(' . $this->regex . ')\]/U';
+
+ // use a custom callback processing method to generate
+ // the replacement text for matches.
+ $this->wiki->source = preg_replace_callback(
+ $tmp_regex,
+ array(&$this, 'processFootnote'),
+ $this->wiki->source
+ );
+
+
+ // -------------------------------------------------------------
+ //
+ // Normal inline URLs.
+ //
+
+ // the regular expression for this kind of URL
+
+ $tmp_regex = '/(^|[^A-Za-z])(' . $this->regex . ')(.*?)/';
+
+ // use the standard callback for inline URLs
+ $this->wiki->source = preg_replace_callback(
+ $tmp_regex,
+ array(&$this, 'process'),
+ $this->wiki->source
+ );
+ }
+
+
+ /**
+ *
+ * Process inline URLs.
+ *
+ * @param array &$matches
+ *
+ * @param array $matches An array of matches from the parse() method
+ * as generated by preg_replace_callback. $matches[0] is the full
+ * matched string, $matches[1] is the first matched pattern,
+ * $matches[2] is the second matched pattern, and so on.
+ *
+ * @return string The processed text replacement.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // set options
+ $options = array(
+ 'type' => 'inline',
+ 'href' => $matches[2],
+ 'text' => $matches[2]
+ );
+
+ // tokenize
+ return $matches[1] . $this->wiki->addToken($this->rule, $options) . $matches[5];
+ }
+
+
+ /**
+ *
+ * Process numbered (footnote) URLs.
+ *
+ * Token options are:
+ * @param array &$matches
+ *
+ * @param array $matches An array of matches from the parse() method
+ * as generated by preg_replace_callback. $matches[0] is the full
+ * matched string, $matches[1] is the first matched pattern,
+ * $matches[2] is the second matched pattern, and so on.
+ *
+ * @return string The processed text replacement.
+ *
+ */
+
+ function processFootnote(&$matches)
+ {
+ // keep a running count for footnotes
+ $this->footnoteCount++;
+
+ // set options
+ $options = array(
+ 'type' => 'footnote',
+ 'href' => $matches[1],
+ 'text' => $this->footnoteCount
+ );
+
+ // tokenize
+ return $this->wiki->addToken($this->rule, $options);
+ }
+
+
+ /**
+ *
+ * Process described-reference (named-reference) URLs.
+ *
+ * Token options are:
+ * 'type' => ['inline'|'footnote'|'descr'] the type of URL
+ * 'href' => the URL link href portion
+ * 'text' => the displayed text of the URL link
+ *
+ * @param array &$matches
+ *
+ * @param array $matches An array of matches from the parse() method
+ * as generated by preg_replace_callback. $matches[0] is the full
+ * matched string, $matches[1] is the first matched pattern,
+ * $matches[2] is the second matched pattern, and so on.
+ *
+ * @return string The processed text replacement.
+ *
+ */
+
+ function processDescr(&$matches)
+ {
+ // set options
+ $options = array(
+ 'type' => 'descr',
+ 'href' => $matches[1],
+ 'text' => $matches[4]
+ );
+
+ // tokenize
+ return $this->wiki->addToken($this->rule, $options);
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Parse/Default/Wikilink.php b/includes/pear/Text/Wiki/Parse/Default/Wikilink.php
new file mode 100644
index 0000000..c3d81c3
--- /dev/null
+++ b/includes/pear/Text/Wiki/Parse/Default/Wikilink.php
@@ -0,0 +1,204 @@
+<?php
+
+/**
+*
+* Parse for links to wiki pages.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+* @license LGPL
+*
+* @version $Id: Wikilink.php 224598 2006-12-08 08:23:51Z justinpatrin $
+*
+*/
+
+/**
+*
+* Parse for links to wiki pages.
+*
+* Wiki page names are typically in StudlyCapsStyle made of
+* WordsSmashedTogether.
+*
+* You can also create described links to pages in this style:
+* [WikiPageName nice text link to use for display]
+*
+* The token options for this rule are:
+*
+* 'page' => the wiki page name.
+*
+* 'text' => the displayed link text.
+*
+* 'anchor' => a named anchor on the target wiki page.
+*
+* @category Text
+*
+* @package Text_Wiki
+*
+* @author Paul M. Jones <pmjones@php.net>
+*
+*/
+
+class Text_Wiki_Parse_Wikilink extends Text_Wiki_Parse {
+
+ var $conf = array (
+ 'ext_chars' => false,
+ 'utf-8' => false
+ );
+
+ /**
+ *
+ * Constructor.
+ *
+ * We override the Text_Wiki_Parse constructor so we can
+ * explicitly comment each part of the $regex property.
+ *
+ * @access public
+ *
+ * @param object &$obj The calling "parent" Text_Wiki object.
+ *
+ */
+
+ function Text_Wiki_Parse_Wikilink(&$obj)
+ {
+ parent::Text_Wiki_Parse($obj);
+
+ if ($this->getConf('utf-8')) {
+ $upper = 'A-Z\p{Lu}';
+ $lower = 'a-z0-9\p{Ll}';
+ $either = 'A-Za-z0-9\p{L}';
+ } else if ($this->getConf('ext_chars')) {
+ // use an extended character set; this should
+ // allow for umlauts and so on. taken from the
+ // Tavi project defaults.php file.
+ $upper = 'A-Z\xc0-\xde';
+ $lower = 'a-z0-9\xdf-\xfe';
+ $either = 'A-Za-z0-9\xc0-\xfe';
+ } else {
+ // the default character set, should be fine
+ // for most purposes.
+ $upper = "A-Z";
+ $lower = "a-z0-9";
+ $either = "A-Za-z0-9";
+ }
+
+ // build the regular expression for finding WikiPage names.
+ $this->regex =
+ "(!?" . // START WikiPage pattern (1)
+ "[$upper]" . // 1 upper
+ "[$either]*" . // 0+ alpha or digit
+ "[$lower]+" . // 1+ lower or digit
+ "[$upper]" . // 1 upper
+ "[$either]*" . // 0+ or more alpha or digit
+ ")" . // END WikiPage pattern (/1)
+ "((\#" . // START Anchor pattern (2)(3)
+ "[$either]" . // 1 alpha
+ "(" . // start sub pattern (4)
+ "[-_$either:.]*" . // 0+ dash, alpha, digit, underscore, colon, dot
+ "[-_$either]" . // 1 dash, alpha, digit, or underscore
+ ")?)?)"; // end subpatterns (/4)(/3)(/2)
+ }
+
+
+ /**
+ *
+ * First parses for described links, then for standalone links.
+ *
+ * @access public
+ *
+ * @return void
+ *
+ */
+
+ function parse()
+ {
+ // described wiki links
+ $tmp_regex = '/\[' . $this->regex . ' (.+?)\]/'.($this->getConf('utf-8') ? 'u' : '');
+ $this->wiki->source = preg_replace_callback(
+ $tmp_regex,
+ array(&$this, 'processDescr'),
+ $this->wiki->source
+ );
+
+ // standalone wiki links
+ if ($this->getConf('utf-8')) {
+ $either = 'A-Za-z0-9\p{L}';
+ } else if ($this->getConf('ext_chars')) {
+ $either = "A-Za-z0-9\xc0-\xfe";
+ } else {
+ $either = "A-Za-z0-9";
+ }
+
+ $tmp_regex = "/(^|[^{$either}\-_]){$this->regex}/".($this->getConf('utf-8') ? 'u' : '');
+ $this->wiki->source = preg_replace_callback(
+ $tmp_regex,
+ array(&$this, 'process'),
+ $this->wiki->source
+ );
+ }
+
+
+ /**
+ *
+ * Generate a replacement for described links.
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token to be used as a placeholder in
+ * the source text, plus any text priot to the match.
+ *
+ */
+
+ function processDescr(&$matches)
+ {
+ // set the options
+ $options = array(
+ 'page' => $matches[1],
+ 'text' => $matches[5],
+ 'anchor' => $matches[3]
+ );
+
+ // create and return the replacement token and preceding text
+ return $this->wiki->addToken($this->rule, $options); // . $matches[7];
+ }
+
+
+ /**
+ *
+ * Generate a replacement for standalone links.
+ *
+ *
+ * @access public
+ *
+ * @param array &$matches The array of matches from parse().
+ *
+ * @return A delimited token to be used as a placeholder in
+ * the source text, plus any text prior to the match.
+ *
+ */
+
+ function process(&$matches)
+ {
+ // when prefixed with !, it's explicitly not a wiki link.
+ // return everything as it was.
+ if ($matches[2]{0} == '!') {
+ return $matches[1] . substr($matches[2], 1) . $matches[3];
+ }
+
+ // set the options
+ $options = array(
+ 'page' => $matches[2],
+ 'text' => $matches[2] . $matches[3],
+ 'anchor' => $matches[3]
+ );
+
+ // create and return the replacement token and preceding text
+ return $matches[1] . $this->wiki->addToken($this->rule, $options);
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render.php b/includes/pear/Text/Wiki/Render.php
new file mode 100644
index 0000000..5bc26f7
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render.php
@@ -0,0 +1,218 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Base rendering class for parsed and tokenized text.
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Render.php 209118 2006-03-11 07:12:13Z justinpatrin $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * Base rendering class for parsed and tokenized text.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render {
+
+
+ /**
+ *
+ * Configuration options for this render rule.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ var $conf = array();
+
+
+ /**
+ *
+ * The name of this rule's format.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ var $format = null;
+
+
+ /**
+ *
+ * The name of this rule's token array elements.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ var $rule = null;
+
+
+ /**
+ *
+ * A reference to the calling Text_Wiki object.
+ *
+ * This is needed so that each rule has access to the same source
+ * text, token set, URLs, interwiki maps, page names, etc.
+ *
+ * @access public
+ *
+ * @var object
+ */
+
+ var $wiki = null;
+
+
+ /**
+ *
+ * Constructor for this render format or rule.
+ *
+ * @access public
+ *
+ * @param object &$obj The calling "parent" Text_Wiki object.
+ *
+ */
+
+ function Text_Wiki_Render(&$obj)
+ {
+ // keep a reference to the calling Text_Wiki object
+ $this->wiki =& $obj;
+
+ // get the config-key-name for this object,
+ // strip the Text_Wiki_Render_ part
+ // 01234567890123456
+ $tmp = get_class($this);
+ $tmp = substr($tmp, 17);
+
+ // split into pieces at the _ mark.
+ // first part is format, second part is rule.
+ $part = explode('_', $tmp);
+ $this->format = isset($part[0]) ? ucwords(strtolower($part[0])) : null;
+ $this->rule = isset($part[1]) ? ucwords(strtolower($part[1])) : null;
+
+ // is there a format but no rule?
+ // then this is the "main" render object, with
+ // pre() and post() methods.
+ if ($this->format && ! $this->rule &&
+ isset($this->wiki->formatConf[$this->format]) &&
+ is_array($this->wiki->formatConf[$this->format])) {
+
+ // this is a format render object
+ $this->conf = array_merge(
+ $this->conf,
+ $this->wiki->formatConf[$this->format]
+ );
+
+ }
+
+ // is there a format and a rule?
+ if ($this->format && $this->rule &&
+ isset($this->wiki->renderConf[$this->format][$this->rule]) &&
+ is_array($this->wiki->renderConf[$this->format][$this->rule])) {
+
+ // this is a rule render object
+ $this->conf = array_merge(
+ $this->conf,
+ $this->wiki->renderConf[$this->format][$this->rule]
+ );
+ }
+ }
+
+
+ /**
+ *
+ * Simple method to safely get configuration key values.
+ *
+ * @access public
+ *
+ * @param string $key The configuration key.
+ *
+ * @param mixed $default If the key does not exist, return this value
+ * instead.
+ *
+ * @return mixed The configuration key value (if it exists) or the
+ * default value (if not).
+ *
+ */
+
+ function getConf($key, $default = null)
+ {
+ if (isset($this->conf[$key])) {
+ return $this->conf[$key];
+ } else {
+ return $default;
+ }
+ }
+
+
+ /**
+ *
+ * Simple method to wrap a configuration in an sprintf() format.
+ *
+ * @access public
+ *
+ * @param string $key The configuration key.
+ *
+ * @param string $format The sprintf() format string.
+ *
+ * @return mixed The formatted configuration key value (if it exists)
+ * or null (if it does not).
+ *
+ */
+
+ function formatConf($format, $key)
+ {
+ if (isset($this->conf[$key])) {
+ //$this->conf[$key] needs a textEncode....at least for Xhtml output...
+ return sprintf($format, $this->conf[$key]);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Default method to render url
+ *
+ * @access public
+ * @param string $urlChunk a part of an url to render
+ * @return rendered url
+ *
+ */
+
+ function urlEncode($urlChunk)
+ {
+ return rawurlencode($urlChunk);
+ }
+
+ /**
+ * Default method to render text (htmlspecialchars)
+ *
+ * @access public
+ * @param string $text the text to render
+ * @return rendered text
+ *
+ */
+
+ function textEncode($text)
+ {
+ return htmlspecialchars($text);
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Creole.php b/includes/pear/Text/Wiki/Render/Creole.php
new file mode 100644
index 0000000..4eb9a5e
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole.php
@@ -0,0 +1,16 @@
+<?php
+
+class Text_Wiki_Render_Creole extends Text_Wiki_Render {
+
+ function pre()
+ {
+ return;
+ }
+
+ function post()
+ {
+ return;
+ }
+
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Address.php b/includes/pear/Text/Wiki/Render/Creole/Address.php
new file mode 100644
index 0000000..21726a6
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Address.php
@@ -0,0 +1,29 @@
+<?php
+
+class Text_Wiki_Render_Creole_Address extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return "-- ";
+ }
+
+ if ($options['type'] == 'end') {
+ return "\n\n";
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Anchor.php b/includes/pear/Text/Wiki/Render/Creole/Anchor.php
new file mode 100644
index 0000000..59dd668
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Anchor.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+*
+* This class renders an anchor target name in Creole.
+*
+* @author Manuel Holtgrewe <purestorm at ggnore dot net>
+*
+* @author Paul M. Jones <pmjones at ciaweb dot net>
+*
+* @package Text_Wiki
+*
+*/
+
+class Text_Wiki_Render_Creole_Anchor extends Text_Wiki_Render {
+
+ function token($options)
+ {
+ return '';
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Blockquote.php b/includes/pear/Text/Wiki/Render/Creole/Blockquote.php
new file mode 100644
index 0000000..f6a3f06
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Blockquote.php
@@ -0,0 +1,47 @@
+<?php
+
+class Text_Wiki_Render_Creole_Blockquote extends Text_Wiki_Render {
+
+ var $css_stack = array();
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ // starting
+ if ($options['type'] == 'start') {
+ if (empty($options['css'])) $options['css'] = '';
+ array_push($this->css_stack, $options['css']);
+ $this->wiki->registerRenderCallback(array(&$this, 'renderInsideText'));
+ return '';
+ }
+ // ending
+ if ($options['type'] == 'end') {
+ $this->wiki->popRenderCallback();
+ return '';
+ }
+ }
+
+ function renderInsideText($text) {
+ $text = trim($text);
+ if (array_pop($this->css_stack) == 'remark') {
+ $text = preg_replace('/(^|\n)([\>\:]*) */', '\1:\2 ', $text);
+ }
+ else {
+ $text = preg_replace('/(^|\n)([\>\:]*) */', '\1>\2 ', $text);
+ }
+ return $text . "\n\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Bold.php b/includes/pear/Text/Wiki/Render/Creole/Bold.php
new file mode 100644
index 0000000..fac63c9
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Bold.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Bold extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return '**';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Box.php b/includes/pear/Text/Wiki/Render/Creole/Box.php
new file mode 100644
index 0000000..c977c25
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Box.php
@@ -0,0 +1,30 @@
+<?php
+
+//There is no box rule for Creole, so use a simple table
+class Text_Wiki_Render_Creole_Box extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return "|| ";
+ }
+
+ if ($options['type'] == 'end') {
+ return "\n\n";
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Break.php b/includes/pear/Text/Wiki/Render/Creole/Break.php
new file mode 100644
index 0000000..375d53a
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Break.php
@@ -0,0 +1,24 @@
+<?php
+
+class Text_Wiki_Render_Creole_Break extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return "\\\\";
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Center.php b/includes/pear/Text/Wiki/Render/Creole/Center.php
new file mode 100644
index 0000000..bed9c5e
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Center.php
@@ -0,0 +1,30 @@
+<?php
+
+//There is no center rule for Creole, so use a simple table
+class Text_Wiki_Render_Creole_Center extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return "| ";
+ }
+
+ if ($options['type'] == 'end') {
+ return "\n\n";
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Code.php b/includes/pear/Text/Wiki/Render/Creole/Code.php
new file mode 100644
index 0000000..a31bea5
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Code.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Code extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return "{{{\n" . $options['text'] . "\n}}}\n\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Colortext.php b/includes/pear/Text/Wiki/Render/Creole/Colortext.php
new file mode 100644
index 0000000..19091c6
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Colortext.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Colortext extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return '';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Deflist.php b/includes/pear/Text/Wiki/Render/Creole/Deflist.php
new file mode 100644
index 0000000..05f47d0
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Deflist.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Deflist extends Text_Wiki_Render {
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return "\n";
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Delimiter.php b/includes/pear/Text/Wiki/Render/Creole/Delimiter.php
new file mode 100644
index 0000000..900448c
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Delimiter.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Delimiter extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return $options['text'];
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Embed.php b/includes/pear/Text/Wiki/Render/Creole/Embed.php
new file mode 100644
index 0000000..dcf2cc1
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Embed.php
@@ -0,0 +1,29 @@
+<?php
+
+class Text_Wiki_Render_Creole_Embed extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return "{{";
+ }
+
+ if ($options['type'] == 'end') {
+ return "}}";
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Emphasis.php b/includes/pear/Text/Wiki/Render/Creole/Emphasis.php
new file mode 100644
index 0000000..ddc9237
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Emphasis.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Emphasis extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return '//';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Freelink.php b/includes/pear/Text/Wiki/Render/Creole/Freelink.php
new file mode 100644
index 0000000..0439ac5
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Freelink.php
@@ -0,0 +1,9 @@
+<?php
+
+require_once 'Text/Wiki/Render/Creole/Wikilink.php';
+
+class Text_Wiki_Render_Creole_Freelink extends Text_Wiki_Render_Creole_Wikilink {
+ // renders identically to wikilinks, only the parsing is different :-)
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Function.php b/includes/pear/Text/Wiki/Render/Creole/Function.php
new file mode 100644
index 0000000..0f07917
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Function.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Function extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return '';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Heading.php b/includes/pear/Text/Wiki/Render/Creole/Heading.php
new file mode 100644
index 0000000..94ebf84
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Heading.php
@@ -0,0 +1,16 @@
+<?php
+
+class Text_Wiki_Render_Creole_Heading extends Text_Wiki_Render {
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return str_pad('', $options['level'], '=') . ' ';
+ }
+ else if ($options['type'] == 'end') {
+ // next line would add trailing '=' signs
+ // return ' ' . str_pad('', $options['level'], '=') . "\n\n";
+ return "\n\n";
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Horiz.php b/includes/pear/Text/Wiki/Render/Creole/Horiz.php
new file mode 100644
index 0000000..2937beb
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Horiz.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Horiz extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return "----\n\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Html.php b/includes/pear/Text/Wiki/Render/Creole/Html.php
new file mode 100644
index 0000000..f76a90e
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Html.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Html extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return "{{{" . $options['text'] . "}}}";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Image.php b/includes/pear/Text/Wiki/Render/Creole/Image.php
new file mode 100644
index 0000000..1c4476f
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Image.php
@@ -0,0 +1,26 @@
+<?php
+class Text_Wiki_Render_Creole_Image extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if (!strlen($options['attr']['alt']) || $options['src'] == $options['attr']['alt']) {
+ return '{{'.$options['src'].'}}';
+ } else {
+ return '{{'.$options['src'].'|'.$options['attr']['alt'].'}}';
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Include.php b/includes/pear/Text/Wiki/Render/Creole/Include.php
new file mode 100644
index 0000000..a0d30cf
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Include.php
@@ -0,0 +1,16 @@
+<?php
+
+class Text_Wiki_Render_Creole_Include extends Text_Wiki_Render {
+
+ function token()
+ {
+ if ($options['type'] == 'start') {
+ return "{{";
+ }
+
+ if ($options['type'] == 'end') {
+ return "}}";
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Interwiki.php b/includes/pear/Text/Wiki/Render/Creole/Interwiki.php
new file mode 100644
index 0000000..d97eec4
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Interwiki.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Interwiki extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return '[['.$options['site'].'>'.$options['page'].(strlen($options['text']) ? '|'.$options['text'] : '').']]';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Italic.php b/includes/pear/Text/Wiki/Render/Creole/Italic.php
new file mode 100644
index 0000000..6d8d0a0
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Italic.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Italic extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return '//';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/List.php b/includes/pear/Text/Wiki/Render/Creole/List.php
new file mode 100644
index 0000000..bbb9b76
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/List.php
@@ -0,0 +1,53 @@
+<?php
+
+class Text_Wiki_Render_Creole_List extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * This rendering method is syntactically and semantically compliant
+ * with XHTML 1.1 in that sub-lists are part of the previous list item.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ // make nice variables (type, level, count)
+
+ switch ($options['type']) {
+
+ case 'bullet_list_start':
+ case 'number_list_start':
+ return '';
+ break;
+ case 'bullet_list_end':
+ case 'number_list_end':
+ if ($options['level'] == 0) {
+ return "\n";
+ }
+ break;
+ case 'bullet_item_start':
+ $pad = str_pad('', $options['level'], '*');
+ return $pad . ' ';
+ break;
+ case 'number_item_start':
+ $pad = str_pad('', $options['level'], '#');
+ return $pad . ' ';
+ break;
+ case 'bullet_item_end':
+ case 'number_item_end':
+ default:
+ return "\n";
+ break;
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Newline.php b/includes/pear/Text/Wiki/Render/Creole/Newline.php
new file mode 100644
index 0000000..697d8eb
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Newline.php
@@ -0,0 +1,12 @@
+<?php
+
+class Text_Wiki_Render_Creole_Newline extends Text_Wiki_Render {
+
+
+ function token($options)
+ {
+ return "\n";
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Paragraph.php b/includes/pear/Text/Wiki/Render/Creole/Paragraph.php
new file mode 100644
index 0000000..7d3840d
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Paragraph.php
@@ -0,0 +1,29 @@
+<?php
+
+class Text_Wiki_Render_Creole_Paragraph extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return "";
+ }
+
+ if ($options['type'] == 'end') {
+ return "\n\n";
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Phplookup.php b/includes/pear/Text/Wiki/Render/Creole/Phplookup.php
new file mode 100644
index 0000000..b667238
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Phplookup.php
@@ -0,0 +1,25 @@
+<?php
+
+// $Id: Phplookup.php 222265 2006-10-23 13:11:27Z mic $
+
+class Text_Wiki_Render_Creole_Phplookup extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return $options['text'];
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Prefilter.php b/includes/pear/Text/Wiki/Render/Creole/Prefilter.php
new file mode 100644
index 0000000..d997af0
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Prefilter.php
@@ -0,0 +1,10 @@
+<?php
+
+class Text_Wiki_Render_Creole_Prefilter extends Text_Wiki_Render {
+ function token()
+ {
+ return '';
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Preformatted.php b/includes/pear/Text/Wiki/Render/Creole/Preformatted.php
new file mode 100644
index 0000000..4d0796f
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Preformatted.php
@@ -0,0 +1,27 @@
+<?php
+
+class Text_Wiki_Render_Creole_Preformatted extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $text = $options['text'];
+
+ $text = preg_replace("/\n( +)}}}/s", "\n$1 }}}", $text);
+
+ return "{{{\n" . $text . "\n}}}\n\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Raw.php b/includes/pear/Text/Wiki/Render/Creole/Raw.php
new file mode 100644
index 0000000..7fd8b19
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Raw.php
@@ -0,0 +1,33 @@
+<?php
+
+class Text_Wiki_Render_Creole_Raw extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $text = $options['text'];
+ if ($text == '\\') $text = ' ';
+ if (isset($options['type']) && $options['type'] == 'escape') {
+ $text = '~' . $text;
+ }
+ else {
+ $find = "/}}}(?!})/";
+ $replace = "}}}}}}{{{";
+ $text = preg_replace($find, $replace, $text);
+ }
+ return $text;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Revise.php b/includes/pear/Text/Wiki/Render/Creole/Revise.php
new file mode 100644
index 0000000..1497c42
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Revise.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Revise extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return '';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Strong.php b/includes/pear/Text/Wiki/Render/Creole/Strong.php
new file mode 100644
index 0000000..de7da41
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Strong.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Strong extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return '**';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Subscript.php b/includes/pear/Text/Wiki/Render/Creole/Subscript.php
new file mode 100644
index 0000000..640cfb7
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Subscript.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Subscript extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return ',,';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Superscript.php b/includes/pear/Text/Wiki/Render/Creole/Superscript.php
new file mode 100644
index 0000000..c9dd995
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Superscript.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Superscript extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return '^^';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Table.php b/includes/pear/Text/Wiki/Render/Creole/Table.php
new file mode 100644
index 0000000..e24ea37
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Table.php
@@ -0,0 +1,70 @@
+<?php
+
+class Text_Wiki_Render_Creole_Table extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+
+
+ switch ($options['type']) {
+
+ case 'table_start':
+ return '';
+ break;
+
+ case 'table_end':
+ return "\n";
+ break;
+
+ case 'caption_start':
+ return '|= ';
+ break;
+
+ case 'caption_end':
+ return "\n";
+ break;
+
+ case 'row_start':
+ return '';
+ break;
+
+ case 'row_end':
+ return "\n";
+ break;
+
+ case 'cell_start':
+ // is this a TH or TD cell?
+ if ($options['attr'] == 'header') {
+ // start a header cell
+ $output = '|= ';
+ } else {
+ // start a normal cell
+ $output = '| ';
+ }
+ return $output;
+ break;
+
+ case 'cell_end':
+ return ' ';
+ break;
+
+ default:
+ return '';
+
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Tighten.php b/includes/pear/Text/Wiki/Render/Creole/Tighten.php
new file mode 100644
index 0000000..67200d0
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Tighten.php
@@ -0,0 +1,10 @@
+<?php
+
+class Text_Wiki_Render_Creole_Tighten extends Text_Wiki_Render {
+
+ function token()
+ {
+ return '';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Toc.php b/includes/pear/Text/Wiki/Render/Creole/Toc.php
new file mode 100644
index 0000000..715292d
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Toc.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Toc extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return '';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Tt.php b/includes/pear/Text/Wiki/Render/Creole/Tt.php
new file mode 100644
index 0000000..8dbc371
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Tt.php
@@ -0,0 +1,29 @@
+<?php
+
+class Text_Wiki_Render_Creole_Tt extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return "{{{";
+ }
+
+ if ($options['type'] == 'end') {
+ return "}}}";
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Underline.php b/includes/pear/Text/Wiki/Render/Creole/Underline.php
new file mode 100644
index 0000000..e239555
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Underline.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Creole_Underline extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return '__';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Url.php b/includes/pear/Text/Wiki/Render/Creole/Url.php
new file mode 100644
index 0000000..ca4ed2f
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Url.php
@@ -0,0 +1,40 @@
+<?php
+
+class Text_Wiki_Render_Creole_Url extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ extract($options);
+ if ($type == 'start') {
+ return '[['.$href.'|';
+ }
+ else if ($type == 'end') {
+ return ']]';
+ }
+ else {
+ $noprot = str_replace('http://', '', str_replace('mailto:', '', $href));
+ if (strpos($href, "#ref") === 0 || strpos($href, "#fn") === 0) {
+ return $text;
+ }
+ else if (! strlen($text) || $text == $href || $text == $noprot) {
+ return '[['.$href.']]';
+ } else {
+ return '[['.$href.'|'.$text.']]';
+ }
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Creole/Wikilink.php b/includes/pear/Text/Wiki/Render/Creole/Wikilink.php
new file mode 100644
index 0000000..fe4fc37
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Creole/Wikilink.php
@@ -0,0 +1,43 @@
+<?php
+
+class Text_Wiki_Render_Creole_Wikilink extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into XHTML.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $dup = (($options['page'] == $options['text']) || ($options['page'] == preg_replace('/\s+/', '_', $options['text'])));
+
+ if ($options['type'] == 'start') {
+ if ($dup) return '[[';
+ else return '[['.$options['page'].
+ (strlen($options['anchor']) ? $options['anchor'] : '').
+ (strlen($options['text']) && (strlen($options['page']) || strlen($options['anchor'])) ? '|' : '');
+ } else if ($options['type'] == 'end') {
+ if ($dup && strlen($options['anchor'])) return $options['anchor'].']]';
+ else return ']]';
+ } else {
+ if ($dup) return '[['.
+ (strlen($options['text']) ? $options['text'] : '').
+ (strlen($options['anchor']) ? $options['anchor'] : '').
+ ']]';
+ else return '[['.$options['page'].
+ (strlen($options['anchor']) ? $options['anchor'] : '').
+ (strlen($options['text']) && strlen($options['page']) && strlen($options['anchor']) ? '|' : '').
+ (strlen($options['text']) ? $options['text'] : '').
+ ']]';
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex.php b/includes/pear/Text/Wiki/Render/Latex.php
new file mode 100644
index 0000000..7eca39e
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+*
+* Formats parsed Text_Wiki for LaTeX rendering.
+*
+* $Id: Latex.php 169211 2004-09-25 19:05:14Z pmjones $
+*
+* @author Jeremy Cowgar <jeremy@cowgar.com>
+*
+* @package Text_Wiki
+*
+* @todo [http://google.com] becomes 1 with a LaTeX footnote in subscript.
+* This should be a normal LaTeX footnote associated with the
+* previous word?
+*
+* @todo parse "..." to be ``...''
+*
+* @todo parse '...' to be `...'
+*
+* @todo move escape_latex to a static function, move escaping to the
+* individual .php files they are associated with
+*
+* @todo allow the user to add conf items to do things like
+* + A custom document header
+* + Custom page headings
+* + Include packages
+* + Set Title, Author, Date
+* + Include a title page
+* + Not output Document Head/Foot (maybe combinding many pages?)
+*
+*/
+
+class Text_Wiki_Render_Latex extends Text_Wiki_Render {
+ function escape_latex ($txt) {
+ $txt = str_replace("\\", "\\\\", $txt);
+ $txt = str_replace('#', '\#', $txt);
+ $txt = str_replace('$', '\$', $txt);
+ $txt = str_replace('%', '\%', $txt);
+ $txt = str_replace('^', '\^', $txt);
+ $txt = str_replace('&', '\&', $txt);
+ $txt = str_replace('_', '\_', $txt);
+ $txt = str_replace('{', '\{', $txt);
+ $txt = str_replace('}', '\}', $txt);
+
+ // Typeset things a bit prettier than normas
+ $txt = str_replace('~', '$\sim$', $txt);
+ $txt = str_replace('...', '\ldots', $txt);
+
+ return $txt;
+ }
+
+ function escape($tok, $ele) {
+ if (isset($tok[$ele])) {
+ $tok[$ele] = $this->escape_latex($tok[$ele]);
+ }
+
+ return $tok;
+ }
+
+ function pre()
+ {
+ foreach ($this->wiki->tokens as $k => $tok) {
+ if ($tok[0] == 'Code') {
+ continue;
+ }
+
+ $tok[1] = $this->escape($tok[1], 'text');
+ $tok[1] = $this->escape($tok[1], 'page');
+ $tok[1] = $this->escape($tok[1], 'href');
+
+ $this->wiki->tokens[$k] = $tok;
+ }
+
+ $this->wiki->source = $this->escape_latex($this->wiki->source);
+
+ return
+ "\\documentclass{article}\n".
+ "\\usepackage{ulem}\n".
+ "\\pagestyle{headings}\n".
+ "\\begin{document}\n";
+ }
+
+ function post()
+ {
+ return "\\end{document}\n";
+ }
+
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Anchor.php b/includes/pear/Text/Wiki/Render/Latex/Anchor.php
new file mode 100644
index 0000000..fcf487f
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Anchor.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+*
+* This class renders an anchor target name in LaTeX.
+*
+* $Id: Anchor.php 169211 2004-09-25 19:05:14Z pmjones $
+*
+* @author Jeremy Cowgar <jeremy@cowgar.com>
+*
+* @package Text_Wiki
+*
+*/
+
+class Text_Wiki_Render_Latex_Anchor extends Text_Wiki_Render {
+
+ function token($options)
+ {
+ extract($options); // $type, $name
+
+ if ($type == 'start') {
+ //return sprintf('<a id="%s">',$name);
+ return '';
+ }
+
+ if ($type == 'end') {
+ //return '</a>';
+ return '';
+ }
+ }
+}
+
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/Blockquote.php b/includes/pear/Text/Wiki/Render/Latex/Blockquote.php
new file mode 100644
index 0000000..9810622
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Blockquote.php
@@ -0,0 +1,36 @@
+<?php
+
+class Text_Wiki_Render_Latex_Blockquote extends Text_Wiki_Render {
+
+ var $conf = array('css' => null);
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $type = $options['type'];
+ $level = $options['level'];
+
+ // starting
+ if ($type == 'start') {
+ return "\\begin{quote}\n";
+ }
+
+ // ending
+ if ($type == 'end') {
+ return "\\end{quote}\n\n";
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Bold.php b/includes/pear/Text/Wiki/Render/Latex/Bold.php
new file mode 100644
index 0000000..16427a9
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Bold.php
@@ -0,0 +1,28 @@
+<?php
+class Text_Wiki_Render_Latex_Bold extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return '\textbf{';
+ }
+
+ if ($options['type'] == 'end') {
+ return '}';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/Box.php b/includes/pear/Text/Wiki/Render/Latex/Box.php
new file mode 100644
index 0000000..fff4331
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Box.php
@@ -0,0 +1,54 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Box rule end renderer for Latex
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Box.php 193489 2005-08-15 09:50:57Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders a box drawn in Latex.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Latex_Box extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return '\framebox[\textwidth]{';
+ }
+
+ if ($options['type'] == 'end') {
+ return "}\n";
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/Break.php b/includes/pear/Text/Wiki/Render/Latex/Break.php
new file mode 100644
index 0000000..1f3fdd3
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Break.php
@@ -0,0 +1,24 @@
+<?php
+
+class Text_Wiki_Render_Latex_Break extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return "\\newline\n";
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Center.php b/includes/pear/Text/Wiki/Render/Latex/Center.php
new file mode 100644
index 0000000..59adffd
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Center.php
@@ -0,0 +1,33 @@
+<?php
+
+class Text_Wiki_Render_Latex_Center extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return 'Center: NI';
+
+ if ($options['type'] == 'start') {
+ //return "\n<center>\n";
+ return '<div style="text-align: center;">';
+ }
+
+ if ($options['type'] == 'end') {
+ //return "</center>\n";
+ return '</div>';
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Code.php b/includes/pear/Text/Wiki/Render/Latex/Code.php
new file mode 100644
index 0000000..40bb45a
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Code.php
@@ -0,0 +1,26 @@
+<?php
+
+class Text_Wiki_Render_Latex_Code extends Text_Wiki_Render {
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $text = $options['text'];
+
+ return "\\begin{verbatim}\n$text\n\\end{verbatim}\n\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Colortext.php b/includes/pear/Text/Wiki/Render/Latex/Colortext.php
new file mode 100644
index 0000000..86ff095
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Colortext.php
@@ -0,0 +1,58 @@
+<?php
+
+class Text_Wiki_Render_Latex_Colortext extends Text_Wiki_Render {
+
+ var $colors = array(
+ 'aqua',
+ 'black',
+ 'blue',
+ 'fuchsia',
+ 'gray',
+ 'green',
+ 'lime',
+ 'maroon',
+ 'navy',
+ 'olive',
+ 'purple',
+ 'red',
+ 'silver',
+ 'teal',
+ 'white',
+ 'yellow'
+ );
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return 'Colortext: NI';
+
+ $type = $options['type'];
+ $color = $options['color'];
+
+ if (! in_array($color, $this->colors)) {
+ $color = '#' . $color;
+ }
+
+ if ($type == 'start') {
+ return "<span style=\"color: $color;\">";
+ }
+
+ if ($options['type'] == 'end') {
+ return '</span>';
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Deflist.php b/includes/pear/Text/Wiki/Render/Latex/Deflist.php
new file mode 100644
index 0000000..8dbfe97
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Deflist.php
@@ -0,0 +1,53 @@
+<?php
+
+class Text_Wiki_Render_Latex_Deflist extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css_dl' => null,
+ 'css_dt' => null,
+ 'css_dd' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $type = $options['type'];
+ switch ($type)
+ {
+ case 'list_start':
+ return "\\begin{description}\n";
+
+ case 'list_end':
+ return "\\end{description}\n\n";
+
+ case 'term_start':
+ return '\item[';
+
+ case 'term_end':
+ return '] ';
+
+ case 'narr_start':
+ return '{';
+
+ case 'narr_end':
+ return "}\n";
+
+ default:
+ return '';
+
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Delimiter.php b/includes/pear/Text/Wiki/Render/Latex/Delimiter.php
new file mode 100644
index 0000000..49a50aa
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Delimiter.php
@@ -0,0 +1,25 @@
+<?php
+
+class Text_Wiki_Render_Latex_Delimiter extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ // TODO: Is this where I can do some LaTeX escaping for items
+ // such as $ { } _ ?
+ return "Delimiter: ".$options['text'];
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Embed.php b/includes/pear/Text/Wiki/Render/Latex/Embed.php
new file mode 100644
index 0000000..0936c7d
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Embed.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Latex_Embed extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return "Embed: ".$options['text'];
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Emphasis.php b/includes/pear/Text/Wiki/Render/Latex/Emphasis.php
new file mode 100644
index 0000000..d949b42
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Emphasis.php
@@ -0,0 +1,29 @@
+<?php
+
+class Text_Wiki_Render_Latex_Emphasis extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return '\textsl{';
+ }
+
+ if ($options['type'] == 'end') {
+ return '}';
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Font.php b/includes/pear/Text/Wiki/Render/Latex/Font.php
new file mode 100644
index 0000000..7009f52
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Font.php
@@ -0,0 +1,73 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * BBCode: extra Font rules renderer to size the text
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Font.php 209123 2006-03-11 11:14:23Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * Font rule render class (used for BBCode)
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ * @see Text_Wiki::Text_Wiki_Render()
+ */
+class Text_Wiki_Render_Latex_Font extends Text_Wiki_Render {
+
+ /**
+ * A table to translate the sizes
+ *
+ * @access public
+ * @var array
+ */
+ var $sizes = array(
+ 'tiny' => 5,
+ 'scriptsize' => 7,
+ 'footnotesize' => 8,
+ 'small' => 9,
+ 'normalsize' => 11,
+ 'large' => 13,
+ 'Large' => 16,
+ 'LARGE' => 19,
+ 'huge' => 22,
+ 'Huge' => 9999);
+
+ /**
+ * Renders a token into text matching the requested format.
+ * process the font size option
+ *
+ * @access public
+ * @param array $options The "options" portion of the token (second element).
+ * @return string The text rendered from the token options.
+ */
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ foreach ($this->sizes as $key => $lim) {
+ if ($options['size'] < $lim) {
+ break;
+ }
+ }
+ return '\{' . $key . '}{';
+ }
+
+ if ($options['type'] == 'end') {
+ return '}';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/Freelink.php b/includes/pear/Text/Wiki/Render/Latex/Freelink.php
new file mode 100644
index 0000000..fdeae54
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Freelink.php
@@ -0,0 +1,6 @@
+<?php
+
+class Text_Wiki_Render_Latex_Freelink extends Text_Wiki_Render_Latex_Wikilink {
+ // renders identically to wikilinks, only the parsing is different :-)
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/Function.php b/includes/pear/Text/Wiki/Render/Latex/Function.php
new file mode 100644
index 0000000..5f4c488
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Function.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Latex_Function extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return "Function: NI";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Heading.php b/includes/pear/Text/Wiki/Render/Latex/Heading.php
new file mode 100644
index 0000000..eab4d54
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Heading.php
@@ -0,0 +1,33 @@
+<?php
+
+class Text_Wiki_Render_Latex_Heading extends Text_Wiki_Render {
+
+ function token($options)
+ {
+ // get nice variable names (type, level)
+ extract($options);
+
+ if ($type == 'start') {
+ switch ($level)
+ {
+ case '1':
+ return '\part{';
+ case '2':
+ return '\section{';
+ case '3':
+ return '\subsection{';
+ case '4':
+ return '\subsubsection{';
+ case '5':
+ return '\paragraph{';
+ case '6':
+ return '\subparagraph{';
+ }
+ }
+
+ if ($type == 'end') {
+ return "}\n";
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Horiz.php b/includes/pear/Text/Wiki/Render/Latex/Horiz.php
new file mode 100644
index 0000000..bb159d4
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Horiz.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Latex_Horiz extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return "\n\\noindent\\rule{\\textwidth}{1pt}\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Html.php b/includes/pear/Text/Wiki/Render/Latex/Html.php
new file mode 100644
index 0000000..350de4a
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Html.php
@@ -0,0 +1,25 @@
+<?php
+
+class Text_Wiki_Render_Latex_Html extends Text_Wiki_Render {
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ print_r($this);
+ return '';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Image.php b/includes/pear/Text/Wiki/Render/Latex/Image.php
new file mode 100644
index 0000000..e6c084b
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Image.php
@@ -0,0 +1,70 @@
+<?php
+class Text_Wiki_Render_Latex_Image extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'base' => '/'
+ );
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return 'Image: NI';
+
+ $src = '"' .
+ $this->getConf('base', '/') .
+ $options['src'] . '"';
+
+ if (isset($options['attr']['link'])) {
+
+ // this image has a link
+ if (strpos($options['attr']['link'], '://')) {
+ // it's a URL
+ $href = $options['attr']['link'];
+ } else {
+ $href = $this->wiki->getRenderConf('xhtml', 'wikilink', 'view_url') .
+ $options['attr']['link'];
+ }
+
+ } else {
+ // image is not linked
+ $href = null;
+ }
+
+ // unset these so they don't show up as attributes
+ unset($options['attr']['link']);
+
+ $attr = '';
+ $alt = false;
+ foreach ($options['attr'] as $key => $val) {
+ if (strtolower($key) == 'alt') {
+ $alt = true;
+ }
+ $attr .= " $key=\"$val\"";
+ }
+
+ // always add an "alt" attribute per Stephane Solliec
+ if (! $alt) {
+ $attr .= ' alt="' . basename($options['src']) . '"';
+ }
+
+ if ($href) {
+ return "<a href=\"$href\"><img src=$src$attr/></a>";
+ } else {
+ return "<img src=$src$attr/>";
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Include.php b/includes/pear/Text/Wiki/Render/Latex/Include.php
new file mode 100644
index 0000000..f23d7a5
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Include.php
@@ -0,0 +1,8 @@
+<?php
+class Text_Wiki_Render_Latex_Include extends Text_Wiki_Render {
+ function token()
+ {
+ return '';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Interwiki.php b/includes/pear/Text/Wiki/Render/Latex/Interwiki.php
new file mode 100644
index 0000000..8f1da63
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Interwiki.php
@@ -0,0 +1,58 @@
+<?php
+
+class Text_Wiki_Render_Latex_Interwiki extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'sites' => array(
+ 'MeatBall' => 'http://www.usemod.com/cgi-bin/mb.pl?%s',
+ 'Advogato' => 'http://advogato.org/%s',
+ 'Wiki' => 'http://c2.com/cgi/wiki?%s'
+ )
+ );
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $text = $options['text'];
+ if (isset($options['url'])) {
+ // calculated by the parser (e.g. Mediawiki)
+ $href = $options['url'];
+ } else {
+ $site = $options['site'];
+ // toggg 2006/02/05 page name must be url encoded (e.g. may contain spaces)
+ $page = $this->urlEncode($options['page']);
+
+ if (isset($this->conf['sites'][$site])) {
+ $href = $this->conf['sites'][$site];
+ } else {
+ return $text;
+ }
+
+ // old form where page is at end,
+ // or new form with %s placeholder for sprintf()?
+ if (strpos($href, '%s') === false) {
+ // use the old form
+ $href = $href . $page;
+ } else {
+ // use the new form
+ $href = sprintf($href, $page);
+ }
+ }
+
+ return $text . '\footnote{' . $href . '}';
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/Italic.php b/includes/pear/Text/Wiki/Render/Latex/Italic.php
new file mode 100644
index 0000000..2b18888
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Italic.php
@@ -0,0 +1,28 @@
+<?php
+class Text_Wiki_Render_Latex_Italic extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return '\textit{';
+ }
+
+ if ($options['type'] == 'end') {
+ return '}';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/List.php b/includes/pear/Text/Wiki/Render/Latex/List.php
new file mode 100644
index 0000000..ff76aed
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/List.php
@@ -0,0 +1,76 @@
+<?php
+
+
+class Text_Wiki_Render_Latex_List extends Text_Wiki_Render {
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * This rendering method is syntactically and semantically compliant
+ * with XHTML 1.1 in that sub-lists are part of the previous list item.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ // make nice variables (type, level, count)
+ extract($options);
+
+ switch ($type)
+ {
+ case 'bullet_list_start':
+ return "\\begin{itemize}\n";
+
+ case 'bullet_list_end':
+ return "\\end{itemize}\n";
+
+ case 'number_list_start':
+ $depth = 'enumi' . str_pad('', $level, 'i');
+ $enum = '\arabic';
+ if (isset($format)) {
+ switch ($format) {
+ case 'a':
+ $enum = '\alph';
+ break;
+ case 'A':
+ $enum = '\Alph';
+ break;
+ case 'i':
+ $enum = '\roman';
+ break;
+ case 'I':
+ $enum = '\Roman';
+ break;
+ }
+ }
+ return '\renewcommand{\labelenumi}{' . $enum . '{' . $depth .
+ "}}\n\\begin{enumerate}\n";
+
+ case 'number_list_end':
+ return "\\end{enumerate}\n";
+
+ case 'bullet_item_start':
+ case 'number_item_start':
+ return '\item{';
+
+ case 'bullet_item_end':
+ case 'number_item_end':
+ return "}\n";
+
+ default:
+ // ignore item endings and all other types.
+ // item endings are taken care of by the other types
+ // depending on their place in the list.
+ return '';
+ break;
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/Newline.php b/includes/pear/Text/Wiki/Render/Latex/Newline.php
new file mode 100644
index 0000000..b1b918a
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Newline.php
@@ -0,0 +1,12 @@
+<?php
+
+class Text_Wiki_Render_Latex_Newline extends Text_Wiki_Render {
+
+
+ function token($options)
+ {
+ return "\\newline\n";
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Page.php b/includes/pear/Text/Wiki/Render/Latex/Page.php
new file mode 100644
index 0000000..41fc708
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Page.php
@@ -0,0 +1,48 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Page rule end renderer for Latex
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Page.php 193514 2005-08-15 15:27:19Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders page markers in Latex.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Latex_Page extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return "\\newpage\n";
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/Paragraph.php b/includes/pear/Text/Wiki/Render/Latex/Paragraph.php
new file mode 100644
index 0000000..45d487e
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Paragraph.php
@@ -0,0 +1,31 @@
+<?php
+
+class Text_Wiki_Render_Latex_Paragraph extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ extract($options); //type
+
+ if ($type == 'start') {
+ return '';
+ }
+
+ if ($type == 'end') {
+ return "\n\n";
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Phplookup.php b/includes/pear/Text/Wiki/Render/Latex/Phplookup.php
new file mode 100644
index 0000000..8c75604
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Phplookup.php
@@ -0,0 +1,34 @@
+<?php
+
+class Text_Wiki_Render_Latex_Phplookup extends Text_Wiki_Render {
+
+ var $conf = array('target' => '_blank');
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return 'Phplookup: NI';
+
+ $text = trim($options['text']);
+
+ $target = $this->getConf('target', '');
+ if ($target) {
+ $target = " target=\"$target\"";
+ }
+
+ return "<a$target href=\"http://php.net/$text\">$text</a>";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Plugin.php b/includes/pear/Text/Wiki/Render/Latex/Plugin.php
new file mode 100644
index 0000000..220b101
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Plugin.php
@@ -0,0 +1,49 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Plugin rule end renderer for Latex
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Plugin.php 193730 2005-08-17 09:16:36Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders wiki plugins in Latex. (empty)
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Latex_Plugin extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ * Plugins produce wiki markup so are processed by parsing, no tokens produced
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return '';
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/Prefilter.php b/includes/pear/Text/Wiki/Render/Latex/Prefilter.php
new file mode 100644
index 0000000..e833cd9
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Prefilter.php
@@ -0,0 +1,56 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * This class implements a Text_Wiki_Render_Xhtml to "pre-filter" source text so
+ * that line endings are consistently \n, lines ending in a backslash \
+ * are concatenated with the next line, and tabs are converted to spaces.
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Jeremy Cowgar <jeremy@cowgar.com>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Prefilter.php 248435 2007-12-17 16:19:44Z justinpatrin $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.0 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Authors: |
+// +----------------------------------------------------------------------+
+//
+// $Id: Prefilter.php 248435 2007-12-17 16:19:44Z justinpatrin $
+
+
+/**
+*
+* This class implements a Text_Wiki_Render_Latex to "pre-filter" source text so
+* that line endings are consistently \n, lines ending in a backslash \
+* are concatenated with the next line, and tabs are converted to spaces.
+*
+* @author Jeremy Cowgar <jeremy@cowgar.com>
+*
+* @package Text_Wiki
+*
+*/
+
+class Text_Wiki_Render_Latex_Prefilter extends Text_Wiki_Render {
+ function token()
+ {
+ return '';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Preformatted.php b/includes/pear/Text/Wiki/Render/Latex/Preformatted.php
new file mode 100644
index 0000000..25216b9
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Preformatted.php
@@ -0,0 +1,48 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Preformatted rule end renderer for Latex
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Preformatted.php 193477 2005-08-15 06:15:41Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders preformated text in Latex.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Latex_Preformatted extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return "\\begin{verbatim}\n" . $options['text'] . "\n\\end{verbatim}\n";
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/Raw.php b/includes/pear/Text/Wiki/Render/Latex/Raw.php
new file mode 100644
index 0000000..4ef4cbe
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Raw.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Latex_Raw extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return "Raw: ".$options['text'];
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Revise.php b/includes/pear/Text/Wiki/Render/Latex/Revise.php
new file mode 100644
index 0000000..f880156
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Revise.php
@@ -0,0 +1,38 @@
+<?php
+
+class Text_Wiki_Render_Latex_Revise extends Text_Wiki_Render {
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'del_start') {
+ return '\sout{';
+ }
+
+ if ($options['type'] == 'del_end') {
+ return '}';
+ }
+
+ if ($options['type'] == 'ins_start') {
+ return '\underline{';
+ }
+
+ if ($options['type'] == 'ins_end') {
+ return '}';
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Smiley.php b/includes/pear/Text/Wiki/Render/Latex/Smiley.php
new file mode 100644
index 0000000..c049cc3
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Smiley.php
@@ -0,0 +1,44 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Smiley rule Latex renderer
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Smiley.php 192951 2005-08-10 11:42:08Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * Smiley rule Latex render class
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ * @see Text_Wiki::Text_Wiki_Render()
+ */
+class Text_Wiki_Render_Latex_Smiley extends Text_Wiki_Render {
+
+ /**
+ * Renders a token into text matching the requested format.
+ * process the Smileys
+ *
+ * @access public
+ * @param array $options The "options" portion of the token (second element).
+ * @return string The text rendered from the token options.
+ */
+ function token($options)
+ {
+ return $options['symbol'];
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/Specialchar.php b/includes/pear/Text/Wiki/Render/Latex/Specialchar.php
new file mode 100644
index 0000000..23358af
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Specialchar.php
@@ -0,0 +1,54 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Specialchar rule end renderer for Latex
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Specialchar.php 193499 2005-08-15 11:10:38Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders special characters in Latex.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Latex_SpecialChar extends Text_Wiki_Render {
+
+ var $types = array('~bs~' => '\\\\',
+ '~hs~' => '\hspace{1em}',
+ '~amp~' => '\&',
+ '~ldq~' => '``',
+ '~rdq~' => "''",
+ '~lsq~' => '`',
+ '~rsq~' => "'",
+ '~c~' => '\copyright',
+ '~--~' => '---',
+ '" -- "' => '---',
+ '&quot; -- &quot;' => '---',
+ '~lt~' => '<',
+ '~gt~' => '>');
+
+ function token($options)
+ {
+ if (isset($this->types[$options['char']])) {
+ return $this->types[$options['char']];
+ } else {
+ return $options['char'];
+ }
+ }
+}
+
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/Strong.php b/includes/pear/Text/Wiki/Render/Latex/Strong.php
new file mode 100644
index 0000000..721ffa3
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Strong.php
@@ -0,0 +1,30 @@
+<?php
+
+class Text_Wiki_Render_Latex_Strong extends Text_Wiki_Render {
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return '\textbf{';
+ }
+
+ if ($options['type'] == 'end') {
+ return '}';
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Subscript.php b/includes/pear/Text/Wiki/Render/Latex/Subscript.php
new file mode 100644
index 0000000..f265308
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Subscript.php
@@ -0,0 +1,54 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Subscript rule end renderer for Latex
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Subscript.php 193490 2005-08-15 10:09:06Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders subscript text in Latex.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Latex_Subscript extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return '_{';
+ }
+
+ if ($options['type'] == 'end') {
+ return '}';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/Superscript.php b/includes/pear/Text/Wiki/Render/Latex/Superscript.php
new file mode 100644
index 0000000..887a8b3
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Superscript.php
@@ -0,0 +1,31 @@
+<?php
+
+class Text_Wiki_Render_Latex_Superscript extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return 'Superscript: NI';
+
+ if ($options['type'] == 'start') {
+ return '<sup>';
+ }
+
+ if ($options['type'] == 'end') {
+ return '</sup>';
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Table.php b/includes/pear/Text/Wiki/Render/Latex/Table.php
new file mode 100644
index 0000000..ad7866f
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Table.php
@@ -0,0 +1,99 @@
+<?php
+
+class Text_Wiki_Render_Latex_Table extends Text_Wiki_Render {
+ var $cell_id = 0;
+ var $cell_count = 0;
+ var $is_spanning = false;
+
+ var $conf = array(
+ 'css_table' => null,
+ 'css_tr' => null,
+ 'css_th' => null,
+ 'css_td' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ // make nice variable names (type, attr, span)
+ extract($options);
+
+ switch ($type)
+ {
+ case 'table_start':
+ $this->cell_count = $cols;
+
+ $tbl_start = '\begin{tabular}{|';
+ for ($a=0; $a < $this->cell_count; $a++) {
+ $tbl_start .= 'l|';
+ }
+ $tbl_start .= "}\n";
+
+ return $tbl_start;
+
+ case 'table_end':
+ return "\\hline\n\\end{tabular}\n";
+
+ case 'caption_start':
+ return "\\caption{";
+
+ case 'caption_end':
+ return "}\n";
+
+ case 'row_start':
+ $this->is_spanning = false;
+ $this->cell_id = 0;
+ return "\\hline\n";
+
+ case 'row_end':
+ return "\\\\\n";
+
+ case 'cell_start':
+ if ($span > 1) {
+ $col_spec = '';
+ if ($this->cell_id == 0) {
+ $col_spec = '|';
+ }
+ $col_spec .= 'l|';
+
+ $this->cell_id += $span;
+ $this->is_spanning = true;
+
+ return '\multicolumn{' . $span . '}{' . $col_spec . '}{';
+ }
+
+ $this->cell_id += 1;
+ return '';
+
+ case 'cell_end':
+ $out = '';
+ if ($this->is_spanning) {
+ $this->is_spanning = false;
+ $out = '}';
+ }
+
+ if ($this->cell_id != $this->cell_count) {
+ $out .= ' & ';
+ }
+
+ return $out;
+
+ default:
+ return '';
+
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/Tighten.php b/includes/pear/Text/Wiki/Render/Latex/Tighten.php
new file mode 100644
index 0000000..7755e94
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Tighten.php
@@ -0,0 +1,9 @@
+<?php
+class Text_Wiki_Render_Latex_Tighten extends Text_Wiki_Render {
+
+ function token()
+ {
+ return '';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Titlebar.php b/includes/pear/Text/Wiki/Render/Latex/Titlebar.php
new file mode 100644
index 0000000..e58bda2
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Titlebar.php
@@ -0,0 +1,54 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Titlebar rule end renderer for Latex
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Titlebar.php 193500 2005-08-15 11:36:55Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders a title bar in Latex.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Latex_Titlebar extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return '\framebox[\textwidth]{\textbf{';
+ }
+
+ if ($options['type'] == 'end') {
+ return "}}\n";
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/Toc.php b/includes/pear/Text/Wiki/Render/Latex/Toc.php
new file mode 100644
index 0000000..fc5846f
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Toc.php
@@ -0,0 +1,30 @@
+<?php
+
+class Text_Wiki_Render_Latex_Toc extends Text_Wiki_Render {
+
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if($options['type'] == 'list_start') {
+ return "\\tableofcontents\n\n";
+ }
+
+ return '';
+ }
+
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Tt.php b/includes/pear/Text/Wiki/Render/Latex/Tt.php
new file mode 100644
index 0000000..45073a7
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Tt.php
@@ -0,0 +1,30 @@
+<?php
+
+class Text_Wiki_Render_Latex_tt extends Text_Wiki_Render {
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return '\texttt{';
+ }
+
+ if ($options['type'] == 'end') {
+ return '}';
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Underline.php b/includes/pear/Text/Wiki/Render/Latex/Underline.php
new file mode 100644
index 0000000..81ed323
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Underline.php
@@ -0,0 +1,30 @@
+<?php
+
+class Text_Wiki_Render_Latex_Underline extends Text_Wiki_Render {
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return '\underline{';
+ }
+
+ if ($options['type'] == 'end') {
+ return '}';
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Latex/Url.php b/includes/pear/Text/Wiki/Render/Latex/Url.php
new file mode 100644
index 0000000..c81f9e6
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Url.php
@@ -0,0 +1,41 @@
+<?php
+
+
+class Text_Wiki_Render_Latex_Url extends Text_Wiki_Render {
+
+
+ var $conf = array(
+ 'target' => false,
+ 'images' => true,
+ 'img_ext' => array('jpg', 'jpeg', 'gif', 'png')
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ // create local variables from the options array (text,
+ // href, type)
+ extract($options);
+
+ if ($options['type'] == 'start') {
+ return '';
+ } else if ($options['type'] == 'end') {
+ return '\footnote{' . $href . '}';
+ } else {
+ return $text . '\footnote{' . $href . '}';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Latex/Wikilink.php b/includes/pear/Text/Wiki/Render/Latex/Wikilink.php
new file mode 100644
index 0000000..5b4cdba
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Latex/Wikilink.php
@@ -0,0 +1,60 @@
+<?php
+
+class Text_Wiki_Render_Latex_Wikilink extends Text_Wiki_Render {
+ var $conf = array(
+ 'pages' => array(),
+ 'view_url' => 'http://example.com/index.php?page=%s',
+ 'new_url' => 'http://example.com/new.php?page=%s',
+ 'new_text' => '?'
+ );
+
+ /**
+ *
+ * Renders a token into XHTML.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ // make nice variable names (page, anchor, text)
+ extract($options);
+
+ // are we checking page existence?
+ $list = $this->getConf('pages');
+ if (is_array($list)) {
+ // yes, check against the page list
+ $exists = in_array($page, $list);
+ } else {
+ // no, assume it exists
+ $exists = true;
+ }
+
+ // convert *after* checking against page names so as not to mess
+ // up what the user typed and what we're checking.
+ $page = $this->textEncode($page);
+ $anchor = $this->textEncode($anchor);
+ $text = $this->textEncode($text);
+
+ $href = $this->getConf('view_url');
+
+ if (strpos($href, '%s') === false) {
+ // use the old form (page-at-end)
+ $href = $href . $page . $anchor;
+ } else {
+ // use the new form (sprintf format string)
+ $href = sprintf($href, $page . $anchor);
+ }
+
+ // get the CSS class and generate output
+ $css = $this->formatConf(' class="%s"', 'css');
+ return $text . '\footnote{' . $href . '}';
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Plain.php b/includes/pear/Text/Wiki/Render/Plain.php
new file mode 100644
index 0000000..e1b02a8
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain.php
@@ -0,0 +1,16 @@
+<?php
+
+class Text_Wiki_Render_Plain extends Text_Wiki_Render {
+
+ function pre()
+ {
+ return;
+ }
+
+ function post()
+ {
+ return;
+ }
+
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Anchor.php b/includes/pear/Text/Wiki/Render/Plain/Anchor.php
new file mode 100644
index 0000000..f24f467
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Anchor.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+*
+* This class renders an anchor target name in XHTML.
+*
+* @author Manuel Holtgrewe <purestorm at ggnore dot net>
+*
+* @author Paul M. Jones <pmjones at ciaweb dot net>
+*
+* @package Text_Wiki
+*
+*/
+
+class Text_Wiki_Render_Plain_Anchor extends Text_Wiki_Render {
+
+ function token($options)
+ {
+ return $options['name'];
+ }
+}
+
+?>
diff --git a/includes/pear/Text/Wiki/Render/Plain/Blockquote.php b/includes/pear/Text/Wiki/Render/Plain/Blockquote.php
new file mode 100644
index 0000000..08da94a
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Blockquote.php
@@ -0,0 +1,39 @@
+<?php
+
+class Text_Wiki_Render_Plain_Blockquote extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $type = $options['type'];
+ $level = $options['level'];
+
+ // set up indenting so that the results look nice; we do this
+ // in two steps to avoid str_pad mathematics. ;-)
+ $pad = str_pad('', $level + 1, "\t");
+ $pad = str_replace("\t", ' ', $pad);
+
+ // starting
+ if ($type == 'start') {
+ return "\n$pad";
+ }
+
+ // ending
+ if ($type == 'end') {
+ return "\n$pad";
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Bold.php b/includes/pear/Text/Wiki/Render/Plain/Bold.php
new file mode 100644
index 0000000..628fb51
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Bold.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Plain_Bold extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Box.php b/includes/pear/Text/Wiki/Render/Plain/Box.php
new file mode 100644
index 0000000..623d073
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Box.php
@@ -0,0 +1,48 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Box rule end renderer for Plain
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Box.php 193489 2005-08-15 09:50:57Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders a box drawn in Plain.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Plain_Box extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return '';
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Plain/Break.php b/includes/pear/Text/Wiki/Render/Plain/Break.php
new file mode 100644
index 0000000..5705bed
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Break.php
@@ -0,0 +1,24 @@
+<?php
+
+class Text_Wiki_Render_Plain_Break extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return "\n";
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Center.php b/includes/pear/Text/Wiki/Render/Plain/Center.php
new file mode 100644
index 0000000..7b36367
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Center.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Plain_Center extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Code.php b/includes/pear/Text/Wiki/Render/Plain/Code.php
new file mode 100644
index 0000000..5f523a3
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Code.php
@@ -0,0 +1,24 @@
+<?php
+
+class Text_Wiki_Render_Plain_Code extends Text_Wiki_Render {
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return "\n" . $options['text'] . "\n\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Colortext.php b/includes/pear/Text/Wiki/Render/Plain/Colortext.php
new file mode 100644
index 0000000..d577fe0
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Colortext.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Plain_Colortext extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Deflist.php b/includes/pear/Text/Wiki/Render/Plain/Deflist.php
new file mode 100644
index 0000000..d05593b
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Deflist.php
@@ -0,0 +1,59 @@
+<?php
+
+class Text_Wiki_Render_Plain_Deflist extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $type = $options['type'];
+ $pad = " ";
+
+ switch ($type) {
+
+ case 'list_start':
+ return "\n";
+ break;
+
+ case 'list_end':
+ return "\n\n";
+ break;
+
+ case 'term_start':
+
+ // done!
+ return $pad;
+ break;
+
+ case 'term_end':
+ return "\n";
+ break;
+
+ case 'narr_start':
+
+ // done!
+ return $pad . $pad;
+ break;
+
+ case 'narr_end':
+ return "\n";
+ break;
+
+ default:
+ return '';
+
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Delimiter.php b/includes/pear/Text/Wiki/Render/Plain/Delimiter.php
new file mode 100644
index 0000000..0e436aa
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Delimiter.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Plain_Delimiter extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Embed.php b/includes/pear/Text/Wiki/Render/Plain/Embed.php
new file mode 100644
index 0000000..3a4304f
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Embed.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Plain_Embed extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return strip_tags($options['text']);
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Emphasis.php b/includes/pear/Text/Wiki/Render/Plain/Emphasis.php
new file mode 100644
index 0000000..8d32995
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Emphasis.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Plain_Emphasis extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Font.php b/includes/pear/Text/Wiki/Render/Plain/Font.php
new file mode 100644
index 0000000..ea0c432
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Font.php
@@ -0,0 +1,44 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * BBCode: extra Font rules renderer to size the text
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Font.php 192578 2005-08-06 11:36:45Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * Font rule render class (used for BBCode)
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ * @see Text_Wiki::Text_Wiki_Render()
+ */
+class Text_Wiki_Render_Plain_Font extends Text_Wiki_Render {
+
+ /**
+ * Renders a token into text matching the requested format.
+ * process the font size option
+ *
+ * @access public
+ * @param array $options The "options" portion of the token (second element).
+ * @return string The text rendered from the token options.
+ */
+ function token($options)
+ {
+ return;
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Plain/Freelink.php b/includes/pear/Text/Wiki/Render/Plain/Freelink.php
new file mode 100644
index 0000000..61de294
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Freelink.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Plain_Freelink extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return $options['text'];
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Function.php b/includes/pear/Text/Wiki/Render/Plain/Function.php
new file mode 100644
index 0000000..6ae5666
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Function.php
@@ -0,0 +1,39 @@
+<?php
+
+// $Id: Function.php 170080 2004-10-08 17:46:47Z pmjones $
+
+class Text_Wiki_Render_Plain_Function extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ extract($options); // access, return, name, params, throws
+
+ $output = "$access $return $name ( ";
+
+ foreach ($params as $key => $val) {
+ $output .= "{$val['type']} {$val['descr']} {$val['default']} ";
+ }
+
+ $output .= ') ';
+
+ foreach ($throws as $key => $val) {
+ $output .= "{$val['type']} {$val['descr']} ";
+ }
+
+ return $output;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Heading.php b/includes/pear/Text/Wiki/Render/Plain/Heading.php
new file mode 100644
index 0000000..306f2bf
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Heading.php
@@ -0,0 +1,14 @@
+<?php
+
+class Text_Wiki_Render_Plain_Heading extends Text_Wiki_Render {
+
+ function token($options)
+ {
+ if ($options['type'] == 'end') {
+ return "\n\n";
+ } else {
+ return "\n";
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Horiz.php b/includes/pear/Text/Wiki/Render/Plain/Horiz.php
new file mode 100644
index 0000000..9d93764
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Horiz.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Plain_Horiz extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return "\n";
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Html.php b/includes/pear/Text/Wiki/Render/Plain/Html.php
new file mode 100644
index 0000000..e64e94b
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Html.php
@@ -0,0 +1,24 @@
+<?php
+
+class Text_Wiki_Render_Plain_Html extends Text_Wiki_Render {
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return strip_tags($options['text']);
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Image.php b/includes/pear/Text/Wiki/Render/Plain/Image.php
new file mode 100644
index 0000000..c5ddd6f
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Image.php
@@ -0,0 +1,22 @@
+<?php
+class Text_Wiki_Render_Plain_Image extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Include.php b/includes/pear/Text/Wiki/Render/Plain/Include.php
new file mode 100644
index 0000000..445990a
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Include.php
@@ -0,0 +1,8 @@
+<?php
+class Text_Wiki_Render_Plain_Include extends Text_Wiki_Render {
+ function token()
+ {
+ return '';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Interwiki.php b/includes/pear/Text/Wiki/Render/Plain/Interwiki.php
new file mode 100644
index 0000000..762a2a0
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Interwiki.php
@@ -0,0 +1,29 @@
+<?php
+
+class Text_Wiki_Render_Plain_Interwiki extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if (isset($options['url'])) {
+ // calculated by the parser (e.g. Mediawiki)
+ $href = $options['url'];
+ } else {
+ $href = $options['site'] . ':' . $options['page'];
+ }
+ return $options['text'] . ' (' . $href . ')';
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Plain/Italic.php b/includes/pear/Text/Wiki/Render/Plain/Italic.php
new file mode 100644
index 0000000..e2a596c
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Italic.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Plain_Italic extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/List.php b/includes/pear/Text/Wiki/Render/Plain/List.php
new file mode 100644
index 0000000..9983689
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/List.php
@@ -0,0 +1,68 @@
+<?php
+
+
+class Text_Wiki_Render_Plain_List extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * This rendering method is syntactically and semantically compliant
+ * with XHTML 1.1 in that sub-lists are part of the previous list item.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ // make nice variables (type, level, count)
+ extract($options);
+
+ // set up indenting so that the results look nice; we do this
+ // in two steps to avoid str_pad mathematics. ;-)
+ $pad = str_pad('', $level, "\t");
+ $pad = str_replace("\t", ' ', $pad);
+
+ switch ($type) {
+
+ case 'bullet_list_start':
+ break;
+
+ case 'bullet_list_end':
+ if ($level == 0) {
+ return "\n\n";
+ }
+ break;
+
+ case 'number_list_start':
+ break;
+
+ case 'number_list_end':
+ if ($level == 0) {
+ return "\n\n";
+ }
+ break;
+
+ case 'bullet_item_start':
+ case 'number_item_start':
+ return "\n$pad";
+ break;
+
+ case 'bullet_item_end':
+ case 'number_item_end':
+ default:
+ // ignore item endings and all other types.
+ // item endings are taken care of by the other types
+ // depending on their place in the list.
+ return;
+ break;
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Newline.php b/includes/pear/Text/Wiki/Render/Plain/Newline.php
new file mode 100644
index 0000000..7c7903e
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Newline.php
@@ -0,0 +1,12 @@
+<?php
+
+class Text_Wiki_Render_Plain_Newline extends Text_Wiki_Render {
+
+
+ function token($options)
+ {
+ return "\n";
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Page.php b/includes/pear/Text/Wiki/Render/Plain/Page.php
new file mode 100644
index 0000000..052f566
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Page.php
@@ -0,0 +1,48 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Page rule end renderer for Plain
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Page.php 193514 2005-08-15 15:27:19Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders page markers in Plain.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Plain_Page extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return "\x0C";
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Plain/Paragraph.php b/includes/pear/Text/Wiki/Render/Plain/Paragraph.php
new file mode 100644
index 0000000..16e536f
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Paragraph.php
@@ -0,0 +1,31 @@
+<?php
+
+class Text_Wiki_Render_Plain_Paragraph extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ extract($options); //type
+
+ if ($type == 'start') {
+ return '';
+ }
+
+ if ($type == 'end') {
+ return "\n\n";
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Phplookup.php b/includes/pear/Text/Wiki/Render/Plain/Phplookup.php
new file mode 100644
index 0000000..a4268d1
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Phplookup.php
@@ -0,0 +1,25 @@
+<?php
+
+class Text_Wiki_Render_Plain_Phplookup extends Text_Wiki_Render {
+
+ var $conf = array('target' => '_blank');
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return trim($options['text']);
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Plugin.php b/includes/pear/Text/Wiki/Render/Plain/Plugin.php
new file mode 100644
index 0000000..224ab2d
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Plugin.php
@@ -0,0 +1,49 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Plugin rule end renderer for Plain
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Plugin.php 193730 2005-08-17 09:16:36Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders wiki plugins in Plain. (empty)
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Plain_Plugin extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ * Plugins produce wiki markup so are processed by parsing, no tokens produced
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return '';
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Plain/Prefilter.php b/includes/pear/Text/Wiki/Render/Plain/Prefilter.php
new file mode 100644
index 0000000..0c63792
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Prefilter.php
@@ -0,0 +1,24 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * This class implements a Text_Wiki_Render_Xhtml to "pre-filter" source text so
+ * that line endings are consistently \n, lines ending in a backslash \
+ * are concatenated with the next line, and tabs are converted to spaces.
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Prefilter.php 248435 2007-12-17 16:19:44Z justinpatrin $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+class Text_Wiki_Render_Plain_Prefilter extends Text_Wiki_Render {
+ function token()
+ {
+ return '';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Preformatted.php b/includes/pear/Text/Wiki/Render/Plain/Preformatted.php
new file mode 100644
index 0000000..db620d3
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Preformatted.php
@@ -0,0 +1,48 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Preformatted rule end renderer for Plain
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Preformatted.php 193477 2005-08-15 06:15:41Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders preformated text in Plain.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Plain_Preformatted extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return $options['text'];
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Plain/Raw.php b/includes/pear/Text/Wiki/Render/Plain/Raw.php
new file mode 100644
index 0000000..e7fa8a8
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Raw.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Plain_Raw extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return $options['text'];
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Revise.php b/includes/pear/Text/Wiki/Render/Plain/Revise.php
new file mode 100644
index 0000000..32bbcad
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Revise.php
@@ -0,0 +1,24 @@
+<?php
+
+class Text_Wiki_Render_Plain_Revise extends Text_Wiki_Render {
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Smiley.php b/includes/pear/Text/Wiki/Render/Plain/Smiley.php
new file mode 100644
index 0000000..cd16e7c
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Smiley.php
@@ -0,0 +1,44 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Smiley rule Plain renderer
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Smiley.php 192951 2005-08-10 11:42:08Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * Smiley rule Plain render class
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ * @see Text_Wiki::Text_Wiki_Render()
+ */
+class Text_Wiki_Render_Plain_Smiley extends Text_Wiki_Render {
+
+ /**
+ * Renders a token into text matching the requested format.
+ * process the Smileys
+ *
+ * @access public
+ * @param array $options The "options" portion of the token (second element).
+ * @return string The text rendered from the token options.
+ */
+ function token($options)
+ {
+ return $options['symbol'];
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Plain/Specialchar.php b/includes/pear/Text/Wiki/Render/Plain/Specialchar.php
new file mode 100644
index 0000000..1208a8d
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Specialchar.php
@@ -0,0 +1,54 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Specialchar rule end renderer for Plain
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Specialchar.php 193499 2005-08-15 11:10:38Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders special characters in Plain.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Plain_SpecialChar extends Text_Wiki_Render {
+
+ var $types = array('~bs~' => '\\',
+ '~hs~' => ' ',
+ '~amp~' => '&',
+ '~ldq~' => '"',
+ '~rdq~' => '"',
+ '~lsq~' => "'",
+ '~rsq~' => "'",
+ '~c~' => '©',
+ '~--~' => '-',
+ '" -- "' => '-',
+ '&quot; -- &quot;' => '-',
+ '~lt~' => '<',
+ '~gt~' => '>');
+
+ function token($options)
+ {
+ if (isset($this->types[$options['char']])) {
+ return $this->types[$options['char']];
+ } else {
+ return $options['char'];
+ }
+ }
+}
+
+?>
diff --git a/includes/pear/Text/Wiki/Render/Plain/Strong.php b/includes/pear/Text/Wiki/Render/Plain/Strong.php
new file mode 100644
index 0000000..7ff55a3
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Strong.php
@@ -0,0 +1,24 @@
+<?php
+
+class Text_Wiki_Render_Plain_Strong extends Text_Wiki_Render {
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Subscript.php b/includes/pear/Text/Wiki/Render/Plain/Subscript.php
new file mode 100644
index 0000000..222dbfc
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Subscript.php
@@ -0,0 +1,48 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Subscript rule end renderer for Plain
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Subscript.php 193490 2005-08-15 10:09:06Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders subscript text in Plain.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Plain_Subscript extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return '';
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Plain/Superscript.php b/includes/pear/Text/Wiki/Render/Plain/Superscript.php
new file mode 100644
index 0000000..d239ee3
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Superscript.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Plain_Superscript extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Table.php b/includes/pear/Text/Wiki/Render/Plain/Table.php
new file mode 100644
index 0000000..86f09aa
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Table.php
@@ -0,0 +1,65 @@
+<?php
+
+class Text_Wiki_Render_Plain_Table extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ // make nice variable names (type, attr, span)
+ extract($options);
+
+ $pad = ' ';
+
+ switch ($type) {
+
+ case 'table_start':
+ return;
+ break;
+
+ case 'table_end':
+ return;
+ break;
+
+ case 'caption_start':
+ return;
+ break;
+
+ case 'caption_end':
+ return "\n";
+ break;
+
+ case 'row_start':
+ return;
+ break;
+
+ case 'row_end':
+ return " ||\n";
+ break;
+
+ case 'cell_start':
+ return " || ";
+ break;
+
+ case 'cell_end':
+ return;
+ break;
+
+ default:
+ return '';
+
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Plain/Tighten.php b/includes/pear/Text/Wiki/Render/Plain/Tighten.php
new file mode 100644
index 0000000..e2babf4
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Tighten.php
@@ -0,0 +1,10 @@
+<?php
+class Text_Wiki_Render_Plain_Tighten extends Text_Wiki_Render {
+
+
+ function token()
+ {
+ return '';
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Titlebar.php b/includes/pear/Text/Wiki/Render/Plain/Titlebar.php
new file mode 100644
index 0000000..2399230
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Titlebar.php
@@ -0,0 +1,54 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Titlebar rule end renderer for Plain
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Titlebar.php 193500 2005-08-15 11:36:55Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders a title bar in Plain.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Plain_Titlebar extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ return '***** ';
+ }
+
+ if ($options['type'] == 'end') {
+ return " *****\n";
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Plain/Toc.php b/includes/pear/Text/Wiki/Render/Plain/Toc.php
new file mode 100644
index 0000000..34f3061
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Toc.php
@@ -0,0 +1,39 @@
+<?php
+
+class Text_Wiki_Render_Plain_Toc extends Text_Wiki_Render {
+
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ // type, count, level
+ extract($options);
+
+ if ($type == 'item_start') {
+
+ // build some indenting spaces for the text
+ $indent = ($level - 2) * 4;
+ $pad = str_pad('', $indent);
+ return $pad;
+ }
+
+ if ($type == 'item_end') {
+ return "\n";
+ }
+ }
+
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Tt.php b/includes/pear/Text/Wiki/Render/Plain/Tt.php
new file mode 100644
index 0000000..c59ccf4
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Tt.php
@@ -0,0 +1,24 @@
+<?php
+
+class Text_Wiki_Render_Plain_tt extends Text_Wiki_Render {
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Underline.php b/includes/pear/Text/Wiki/Render/Plain/Underline.php
new file mode 100644
index 0000000..8e4d18b
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Underline.php
@@ -0,0 +1,23 @@
+<?php
+
+class Text_Wiki_Render_Plain_Underline extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return;
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Url.php b/includes/pear/Text/Wiki/Render/Plain/Url.php
new file mode 100644
index 0000000..5c758e2
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Url.php
@@ -0,0 +1,29 @@
+<?php
+
+
+class Text_Wiki_Render_Plain_Url extends Text_Wiki_Render {
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start' || $options['type'] == 'end') {
+ return '';
+ } else {
+ return $options['text'];
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Plain/Wikilink.php b/includes/pear/Text/Wiki/Render/Plain/Wikilink.php
new file mode 100644
index 0000000..8337dda
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Plain/Wikilink.php
@@ -0,0 +1,24 @@
+<?php
+
+class Text_Wiki_Render_Plain_Wikilink extends Text_Wiki_Render {
+
+
+ /**
+ *
+ * Renders a token into plain text.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return $options['text'];
+ }
+}
+?> \ No newline at end of file
diff --git a/includes/pear/Text/Wiki/Render/Xhtml.php b/includes/pear/Text/Wiki/Render/Xhtml.php
new file mode 100644
index 0000000..ca611bc
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml.php
@@ -0,0 +1,107 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Format class for the Xhtml rendering
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Xhtml.php 206939 2006-02-10 22:31:50Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * Format class for the Xhtml rendering
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'translate' => HTML_ENTITIES,
+ 'quotes' => ENT_COMPAT,
+ 'charset' => 'ISO-8859-1'
+ );
+
+ function pre()
+ {
+ $this->wiki->source = $this->textEncode($this->wiki->source);
+ }
+
+ function post()
+ {
+ return;
+ }
+
+
+ /**
+ * Method to render text
+ *
+ * @access public
+ * @param string $text the text to render
+ * @return rendered text
+ *
+ */
+
+ function textEncode($text)
+ {
+ // attempt to translate HTML entities in the source.
+ // get the config options.
+ $type = $this->getConf('translate', HTML_ENTITIES);
+ $quotes = $this->getConf('quotes', ENT_COMPAT);
+ $charset = $this->getConf('charset', 'ISO-8859-1');
+
+ // have to check null and false because HTML_ENTITIES is a zero
+ if ($type === HTML_ENTITIES) {
+
+ // keep a copy of the translated version of the delimiter
+ // so we can convert it back.
+ $new_delim = htmlentities($this->wiki->delim, $quotes, $charset);
+
+ // convert the entities. we silence the call here so that
+ // errors about charsets don't pop up, per counsel from
+ // Jan at Horde. (http://pear.php.net/bugs/bug.php?id=4474)
+ $text = @htmlentities(
+ $text,
+ $quotes,
+ $charset
+ );
+
+ // re-convert the delimiter
+ $text = str_replace(
+ $new_delim, $this->wiki->delim, $text
+ );
+
+ } elseif ($type === HTML_SPECIALCHARS) {
+
+ // keep a copy of the translated version of the delimiter
+ // so we can convert it back.
+ $new_delim = htmlspecialchars($this->wiki->delim, $quotes,
+ $charset);
+
+ // convert the entities. we silence the call here so that
+ // errors about charsets don't pop up, per counsel from
+ // Jan at Horde. (http://pear.php.net/bugs/bug.php?id=4474)
+ $text = @htmlspecialchars(
+ $text,
+ $quotes,
+ $charset
+ );
+
+ // re-convert the delimiter
+ $text = str_replace(
+ $new_delim, $this->wiki->delim, $text
+ );
+ }
+ return $text;
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Address.php b/includes/pear/Text/Wiki/Render/Xhtml/Address.php
new file mode 100644
index 0000000..ec98eae
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Address.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ *
+ * Address rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ *
+ * @package Text_Wiki
+ *
+ * @author Michele Tomaiuolo <tomamic@yahoo.it>
+ *
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ *
+ * @version CVS: $Id: Address.php 228638 2007-02-01 09:33:00Z mic $
+ *
+ * @link http://pear.php.net/package/Text_Wiki
+ *
+ */
+
+class Text_Wiki_Render_Xhtml_Address extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ $css = $this->formatConf(' class="%s"', 'css');
+ return "<address$css>";
+ }
+
+ if ($options['type'] == 'end') {
+ return '</address>';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Anchor.php b/includes/pear/Text/Wiki/Render/Xhtml/Anchor.php
new file mode 100644
index 0000000..0a42cac
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Anchor.php
@@ -0,0 +1,48 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Anchor rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Anchor.php 206940 2006-02-10 23:07:03Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders an anchor target name in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Anchor extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css' => null
+ );
+
+ function token($options)
+ {
+ extract($options); // $type, $name
+
+ if ($type == 'start') {
+ $css = $this->formatConf(' class="%s"', 'css');
+ $format = "<a$css id=\"%s\">";
+ return sprintf($format, $this->textEncode($name));
+ }
+
+ if ($type == 'end') {
+ return '</a>';
+ }
+ }
+}
+
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Blockquote.php b/includes/pear/Text/Wiki/Render/Xhtml/Blockquote.php
new file mode 100644
index 0000000..edbb498
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Blockquote.php
@@ -0,0 +1,72 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Blockquote rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Blockquote.php 236408 2007-05-26 18:25:45Z mic $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders a blockquote in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Blockquote extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $type = $options['type'];
+ $level = $options['level'];
+
+ // set up indenting so that the results look nice; we do this
+ // in two steps to avoid str_pad mathematics. ;-)
+ $pad = str_pad('', $level, "\t");
+ $pad = str_replace("\t", ' ', $pad);
+
+ // pick the css type
+ $css = $this->formatConf(' class="%s"', 'css');
+
+ if (isset($options['css'])) {
+ $css = ' class="' . $options['css']. '"';
+ }
+ // starting
+ if ($type == 'start') {
+ return "$pad<blockquote$css>";
+ }
+
+ // ending
+ if ($type == 'end') {
+ return $pad . "</blockquote>\n";
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Bold.php b/includes/pear/Text/Wiki/Render/Xhtml/Bold.php
new file mode 100644
index 0000000..44b088e
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Bold.php
@@ -0,0 +1,57 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Bold rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Bold.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders bold text in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Bold extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ $css = $this->formatConf(' class="%s"', 'css');
+ return "<b$css>";
+ }
+
+ if ($options['type'] == 'end') {
+ return '</b>';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Box.php b/includes/pear/Text/Wiki/Render/Xhtml/Box.php
new file mode 100644
index 0000000..ef91883
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Box.php
@@ -0,0 +1,62 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Box rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Box.php 231098 2007-03-03 23:00:54Z mic $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders a box drawn in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Box extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css' => 'simplebox'
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ if ($options['css']) {
+ $css = ' class="' . $options['css']. '"';
+ }
+ else {
+ $css = $this->formatConf(' class="%s"', 'css');
+ }
+ return "<div $css>";
+ }
+
+ if ($options['type'] == 'end') {
+ return '</div>';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Break.php b/includes/pear/Text/Wiki/Render/Xhtml/Break.php
new file mode 100644
index 0000000..6b9b040
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Break.php
@@ -0,0 +1,52 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Break rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Break.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders line breaks in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Break extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $css = $this->formatConf(' class="%s"', 'css');
+ return "<br$css />\n";
+ }
+}
+
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Center.php b/includes/pear/Text/Wiki/Render/Xhtml/Center.php
new file mode 100644
index 0000000..b563b3f
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Center.php
@@ -0,0 +1,62 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Center rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Center.php 230212 2007-02-19 08:51:19Z mic $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders centered content in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Center extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ $css = $this->getConf('css');
+ if ($css) {
+ return "<div class=\"$css\">";
+ }
+ else {
+ return '<div style="text-align: center;">';
+ }
+ }
+
+ if ($options['type'] == 'end') {
+ return '</div>';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Code.php b/includes/pear/Text/Wiki/Render/Xhtml/Code.php
new file mode 100644
index 0000000..13eeef4
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Code.php
@@ -0,0 +1,133 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Code rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Code.php 206940 2006-02-10 23:07:03Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders code blocks in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Code extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css' => null, // class for <pre>
+ 'css_code' => null, // class for generic <code>
+ 'css_php' => null, // class for PHP <code>
+ 'css_html' => null, // class for HTML <code>
+ 'css_filename' => null // class for optional filename <div>
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $text = $options['text'];
+ $attr = $options['attr'];
+ $type = strtolower($attr['type']);
+
+ $css = $this->formatConf(' class="%s"', 'css');
+ $css_code = $this->formatConf(' class="%s"', 'css_code');
+ $css_php = $this->formatConf(' class="%s"', 'css_php');
+ $css_html = $this->formatConf(' class="%s"', 'css_html');
+ $css_filename = $this->formatConf(' class="%s"', 'css_filename');
+
+ if ($type == 'php') {
+ if (substr($options['text'], 0, 5) != '<?php') {
+ // PHP code example:
+ // add the PHP tags
+ $text = "<?php\n" . $options['text'] . "\n?>"; // <?php
+ }
+
+ // convert tabs to four spaces
+ $text = str_replace("\t", " ", $text);
+
+ // colorize the code block (also converts HTML entities and adds
+ // <code>...</code> tags)
+ ob_start();
+ highlight_string($text);
+ $text = ob_get_contents();
+ ob_end_clean();
+
+ // replace <br /> tags with simple newlines.
+ // replace non-breaking space with simple spaces.
+ // translate HTML <font> and color to XHTML <span> and style.
+ // courtesy of research by A. Kalin :-).
+ $map = array(
+ '<br />' => "\n",
+ '&nbsp;' => ' ',
+ '<font' => '<span',
+ '</font>' => '</span>',
+ 'color="' => 'style="color:'
+ );
+ $text = strtr($text, $map);
+
+ // get rid of the last newline inside the code block
+ // (becuase higlight_string puts one there)
+ if (substr($text, -8) == "\n</code>") {
+ $text = substr($text, 0, -8) . "</code>";
+ }
+
+ // replace all <code> tags with classed tags
+ if ($css_php) {
+ $text = str_replace('<code>', "<code$css_php>", $text);
+ }
+
+ // done
+ $text = "<pre$css>$text</pre>";
+
+ } elseif ($type == 'html' || $type == 'xhtml') {
+
+ // HTML code example:
+ // add <html> opening and closing tags,
+ // convert tabs to four spaces,
+ // convert entities.
+ $text = str_replace("\t", " ", $text);
+ $text = "<html>\n$text\n</html>";
+ $text = $this->textEncode($text);
+ $text = "<pre$css><code$css_html>$text</code></pre>";
+
+ } else {
+ // generic code example:
+ // convert tabs to four spaces,
+ // convert entities.
+ $text = str_replace("\t", " ", $text);
+ $text = $this->textEncode($text);
+ $text = "<pre$css><code$css_code>$text</code></pre>";
+ }
+
+ if ($css_filename && isset($attr['filename'])) {
+ $text = "<div$css_filename>" .
+ $attr['filename'] . '</div>' . $text;
+ }
+
+ return "\n$text\n\n";
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Colortext.php b/includes/pear/Text/Wiki/Render/Xhtml/Colortext.php
new file mode 100644
index 0000000..d8d8c7f
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Colortext.php
@@ -0,0 +1,79 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Colortext rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Colortext.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders colored text in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Colortext extends Text_Wiki_Render {
+
+ var $colors = array(
+ 'aqua',
+ 'black',
+ 'blue',
+ 'fuchsia',
+ 'gray',
+ 'green',
+ 'lime',
+ 'maroon',
+ 'navy',
+ 'olive',
+ 'purple',
+ 'red',
+ 'silver',
+ 'teal',
+ 'white',
+ 'yellow'
+ );
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $type = $options['type'];
+ $color = $options['color'];
+
+ if (! in_array($color, $this->colors) && $color{0} != '#') {
+ $color = '#' . $color;
+ }
+
+ if ($type == 'start') {
+ return "<span style=\"color: $color;\">";
+ }
+
+ if ($options['type'] == 'end') {
+ return '</span>';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Deflist.php b/includes/pear/Text/Wiki/Render/Xhtml/Deflist.php
new file mode 100644
index 0000000..35225c0
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Deflist.php
@@ -0,0 +1,87 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Deflist rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Deflist.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders definition lists in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Deflist extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css_dl' => null,
+ 'css_dt' => null,
+ 'css_dd' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $type = $options['type'];
+ $pad = " ";
+
+ switch ($type) {
+
+ case 'list_start':
+ $css = $this->formatConf(' class="%s"', 'css_dl');
+ return "<dl$css>\n";
+ break;
+
+ case 'list_end':
+ return "</dl>\n\n";
+ break;
+
+ case 'term_start':
+ $css = $this->formatConf(' class="%s"', 'css_dt');
+ return $pad . "<dt$css>";
+ break;
+
+ case 'term_end':
+ return "</dt>\n";
+ break;
+
+ case 'narr_start':
+ $css = $this->formatConf(' class="%s"', 'css_dd');
+ return $pad . $pad . "<dd$css>";
+ break;
+
+ case 'narr_end':
+ return "</dd>\n";
+ break;
+
+ default:
+ return '';
+
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Delimiter.php b/includes/pear/Text/Wiki/Render/Xhtml/Delimiter.php
new file mode 100644
index 0000000..e39bcc7
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Delimiter.php
@@ -0,0 +1,46 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Delimiter rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Delimiter.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class set back the replaced delimiters in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Delimiter extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return $options['text'];
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Embed.php b/includes/pear/Text/Wiki/Render/Xhtml/Embed.php
new file mode 100644
index 0000000..d425411
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Embed.php
@@ -0,0 +1,46 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Embed rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Embed.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class replaces the embedded php output in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Embed extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return $options['text'];
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Emphasis.php b/includes/pear/Text/Wiki/Render/Xhtml/Emphasis.php
new file mode 100644
index 0000000..a7910d7
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Emphasis.php
@@ -0,0 +1,58 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Emphasis rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Emphasis.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders emphasized text in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Emphasis extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css' => null
+ );
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ $css = $this->formatConf(' class="%s"', 'css');
+ return "<em$css>";
+ }
+
+ if ($options['type'] == 'end') {
+ return '</em>';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Font.php b/includes/pear/Text/Wiki/Render/Xhtml/Font.php
new file mode 100644
index 0000000..f4b78ce
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Font.php
@@ -0,0 +1,83 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * BBCode: extra Font rules renderer to size the text
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Font.php 192578 2005-08-06 11:36:45Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * Font rule render class (used for BBCode)
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ * @see Text_Wiki::Text_Wiki_Render()
+ */
+class Text_Wiki_Render_Xhtml_Font extends Text_Wiki_Render {
+
+/* var $size = array(
+ 'xx-small',
+ 'x-small',
+ 'small',
+ 'medium',
+ 'large',
+ 'x-large',
+ 'xx-large',
+ 'larger',
+ 'smaller'
+ );
+ var $units = array(
+ 'em',
+ 'ex',
+ 'px',
+ 'in',
+ 'cm',
+ 'mm',
+ 'pt',
+ 'pc'
+ );
+*/
+
+ /**
+ * Renders a token into text matching the requested format.
+ * process the font size option
+ *
+ * @access public
+ * @param array $options The "options" portion of the token (second element).
+ * @return string The text rendered from the token options.
+ */
+ function token($options)
+ {
+ if ($options['type'] == 'end') {
+ return '</span>';
+ }
+ if ($options['type'] != 'start') {
+ return '';
+ }
+
+ $ret = '<span style="';
+ if (isset($options['size'])) {
+ $size = trim($options['size']);
+ if (is_numeric($size)) {
+ $size .= 'px';
+ }
+ $ret .= "font-size: $size;";
+ }
+
+ return $ret.'">';
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Freelink.php b/includes/pear/Text/Wiki/Render/Xhtml/Freelink.php
new file mode 100644
index 0000000..f592fdf
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Freelink.php
@@ -0,0 +1,35 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Freelink rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Freelink.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * The wikilink render class.
+ */
+require_once 'Text/Wiki/Render/Xhtml/Wikilink.php';
+
+/**
+ * This class renders free links in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Freelink extends Text_Wiki_Render_Xhtml_Wikilink {
+ // renders identically to wikilinks, only the parsing is different :-)
+}
+
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Function.php b/includes/pear/Text/Wiki/Render/Xhtml/Function.php
new file mode 100644
index 0000000..1ed050e
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Function.php
@@ -0,0 +1,108 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Function rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Function.php 206940 2006-02-10 23:07:03Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders a function description in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Function extends Text_Wiki_Render {
+
+ var $conf = array(
+ // list separator for params and throws
+ 'list_sep' => ', ',
+
+ // the "main" format string
+ 'format_main' => '%access %return <b>%name</b> ( %params ) %throws',
+
+ // the looped format string for required params
+ 'format_param' => '%type <i>%descr</i>',
+
+ // the looped format string for params with default values
+ 'format_paramd' => '[%type <i>%descr</i> default %default]',
+
+ // the looped format string for throws
+ 'format_throws' => '<b>throws</b> %type <i>%descr</i>'
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ extract($options); // name, access, return, params, throws
+
+ // build the baseline output
+ $output = $this->conf['format_main'];
+ $output = str_replace('%access', $this->textEncode($access), $output);
+ $output = str_replace('%return', $this->textEncode($return), $output);
+ $output = str_replace('%name', $this->textEncode($name), $output);
+
+ // build the set of params
+ $list = array();
+ foreach ($params as $key => $val) {
+
+ // is there a default value?
+ if ($val['default']) {
+ $tmp = $this->conf['format_paramd'];
+ } else {
+ $tmp = $this->conf['format_param'];
+ }
+
+ // add the param elements
+ $tmp = str_replace('%type', $this->textEncode($val['type']), $tmp);
+ $tmp = str_replace('%descr', $this->textEncode($val['descr']), $tmp);
+ $tmp = str_replace('%default', $this->textEncode($val['default']), $tmp);
+ $list[] = $tmp;
+ }
+
+ // insert params into output
+ $tmp = implode($this->conf['list_sep'], $list);
+ $output = str_replace('%params', $tmp, $output);
+
+ // build the set of throws
+ $list = array();
+ foreach ($throws as $key => $val) {
+ $tmp = $this->conf['format_throws'];
+ $tmp = str_replace('%type', $this->textEncode($val['type']), $tmp);
+ $tmp = str_replace('%descr', $this->textEncode($val['descr']), $tmp);
+ $list[] = $tmp;
+ }
+
+ // insert throws into output
+ $tmp = implode($this->conf['list_sep'], $list);
+ $output = str_replace('%throws', $tmp, $output);
+
+ // close the div and return the output
+ $output .= '</div>';
+ return "\n$output\n\n";
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Heading.php b/includes/pear/Text/Wiki/Render/Xhtml/Heading.php
new file mode 100644
index 0000000..0d3c05f
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Heading.php
@@ -0,0 +1,88 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Heading rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Heading.php 196293 2005-09-18 13:39:39Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders headings in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Heading extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css_h1' => null,
+ 'css_h2' => null,
+ 'css_h3' => null,
+ 'css_h4' => null,
+ 'css_h5' => null,
+ 'css_h6' => null
+ );
+
+ function token($options)
+ {
+ $collapse = null;
+ static $jsOutput = false;
+ // get nice variable names (id, type, level)
+ extract($options);
+
+ switch($type) {
+ case 'start':
+ $css = $this->formatConf(' class="%s"', "css_h$level");
+ return '
+<h'.$level.$css.' id="'.$id.'"'.($collapse !== null ? ' onclick="hideTOC(\''.$id.'\');"' : '').'>';
+
+ case 'end':
+ return '</h'.$level.'>
+'.($collapse !== null ? '<a id="'.$id.'__link" href="javascript:void();" onclick="hideTOC(\''.$id.'\')">['.($collapse ? '+' : '-').']</a>
+' : '');
+ case 'startContent':
+ if ($collapse !== null) {
+ if ($jsOutput) {
+ $js = '';
+ } else {
+ $js = '
+<script language="javascript">
+function hideTOC(id) {
+ div = document.getElementById(id+"__content");
+ link = document.getElementById(id+"__link");
+ if (div.style.display == "none") {
+ div.style.display = "";
+ link.innerHTML = "[-]";
+ } else {
+ div.style.display = "none";
+ link.innerHTML = "[+]";
+ }
+}
+</script>
+';
+ }
+ } else {
+ $js = '';
+ }
+ return $js.'
+<div style="'.($collapse === true ? 'display: none; ' : '').'padding: 0px; margin: 0px; border: none;" id="'.$id.'__content">
+';
+ case 'endContent':
+ return '
+</div>
+';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Horiz.php b/includes/pear/Text/Wiki/Render/Xhtml/Horiz.php
new file mode 100644
index 0000000..e480f5e
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Horiz.php
@@ -0,0 +1,51 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Horiz rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Horiz.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders an horizontal bar in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Horiz extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $css = $this->formatConf(' class="%s"', 'css');
+ return "<hr$css />\n";
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Html.php b/includes/pear/Text/Wiki/Render/Xhtml/Html.php
new file mode 100644
index 0000000..7ca4218
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Html.php
@@ -0,0 +1,47 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Html rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Html.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders preformated html in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Html extends Text_Wiki_Render {
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return $options['text'];
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Image.php b/includes/pear/Text/Wiki/Render/Xhtml/Image.php
new file mode 100644
index 0000000..51def7b
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Image.php
@@ -0,0 +1,184 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Image rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Image.php 231923 2007-03-15 15:04:50Z justinpatrin $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class inserts an image in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Image extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'base' => '/',
+ 'url_base' => null,
+ 'css' => null,
+ 'css_link' => null
+ );
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ // note the image source
+ $src = $options['src'];
+
+ // is the source a local file or URL?
+ if (strpos($src, '://') === false) {
+ // the source refers to a local file.
+ // add the URL base to it.
+ $src = $this->getConf('base', '/') . $src;
+ }
+
+ // stephane@metacites.net
+ // is the image clickable?
+ if (isset($options['attr']['link'])) {
+ // yes, the image is clickable.
+ // are we linked to a URL or a wiki page?
+ if (strpos($options['attr']['link'], '://')) {
+ // it's a URL, prefix the URL base
+ $href = $this->getConf('url_base') . $options['attr']['link'];
+ } else {
+ // it's a WikiPage; assume it exists.
+ /** @todo This needs to honor sprintf wikilinks (pmjones) */
+ /** @todo This needs to honor interwiki (pmjones) */
+ /** @todo This needs to honor freelinks (pmjones) */
+ $href = $this->wiki->getRenderConf('xhtml', 'wikilink', 'view_url') .
+ $options['attr']['link'];
+ }
+ } else {
+ // image is not clickable.
+ $href = null;
+ }
+ // unset so it won't show up as an attribute
+ unset($options['attr']['link']);
+
+ // stephane@metacites.net -- 25/07/2004
+ // use CSS for all alignment
+ if (isset($options['attr']['align'])) {
+ // make sure we have a style attribute
+ if (!isset($options['attr']['style'])) {
+ // no style, set up a blank one
+ $options['attr']['style'] = '';
+ } else {
+ // style exists, add a space
+ $options['attr']['style'] .= ' ';
+ }
+
+ if ($options['attr']['align'] == 'center') {
+ // add a "center" style to the existing style.
+ $options['attr']['style'] .=
+ 'display: block; margin-left: auto; margin-right: auto;';
+ } else {
+ // add a float style to the existing style
+ $options['attr']['style'] .=
+ 'float: '.$options['attr']['align'];
+ }
+
+ // unset so it won't show up as an attribute
+ unset($options['attr']['align']);
+ }
+
+ // stephane@metacites.net -- 25/07/2004
+ // try to guess width and height
+ if (! isset($options['attr']['width']) &&
+ ! isset($options['attr']['height'])) {
+
+ // does the source refer to a local file or a URL?
+ if (strpos($src,'://')) {
+ // is a URL link
+ $imageFile = $src;
+ } elseif ($src[0] == '.') {
+ // reg at dav-muz dot net -- 2005-03-07
+ // is a local file on relative path.
+ $imageFile = $src; # ...don't do anything because it's perfect!
+ } else {
+ // is a local file on absolute path.
+ $imageFile = $_SERVER['DOCUMENT_ROOT'] . $src;
+ }
+
+ // attempt to get the image size
+ $imageSize = @getimagesize($imageFile);
+
+ if (is_array($imageSize)) {
+ $options['attr']['width'] = $imageSize[0];
+ $options['attr']['height'] = $imageSize[1];
+ }
+
+ }
+
+ // start the HTML output
+ $output = '<img src="' . $this->textEncode($src) . '"';
+
+ // get the CSS class but don't add it yet
+ $css = $this->formatConf(' class="%s"', 'css');
+
+ // add the attributes to the output, and be sure to
+ // track whether or not we find an "alt" attribute
+ $alt = false;
+ foreach ($options['attr'] as $key => $val) {
+
+ // track the 'alt' attribute
+ if (strtolower($key) == 'alt') {
+ $alt = true;
+ }
+
+ // the 'class' attribute overrides the CSS class conf
+ if (strtolower($key) == 'class') {
+ $css = null;
+ }
+
+ $key = $this->textEncode($key);
+ $val = $this->textEncode($val);
+ $output .= " $key=\"$val\"";
+ }
+
+ // always add an "alt" attribute per Stephane Solliec
+ if (! $alt) {
+ $alt = $this->textEncode(basename($options['src']));
+ $output .= " alt=\"$alt\"";
+ }
+
+ // end the image tag with the automatic CSS class (if any)
+ $output .= "$css />";
+
+ // was the image clickable?
+ if ($href) {
+ // yes, add the href and return
+ $href = $this->textEncode($href);
+ $css = $this->formatConf(' class="%s"', 'css_link');
+ $output = "<a$css href=\"$href\">$output</a>";
+ }
+
+ return $output;
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Include.php b/includes/pear/Text/Wiki/Render/Xhtml/Include.php
new file mode 100644
index 0000000..a152c40
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Include.php
@@ -0,0 +1,32 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Include rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Include.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders included maekup in XHTML. (empty)
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Include extends Text_Wiki_Render {
+ function token()
+ {
+ return '';
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Interwiki.php b/includes/pear/Text/Wiki/Render/Xhtml/Interwiki.php
new file mode 100644
index 0000000..9686b62
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Interwiki.php
@@ -0,0 +1,103 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Interwiki rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Interwiki.php 231896 2007-03-15 00:08:47Z justinpatrin $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders inter wikis links in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Interwiki extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'sites' => array(
+ 'MeatBall' => 'http://www.usemod.com/cgi-bin/mb.pl?%s',
+ 'Advogato' => 'http://advogato.org/%s',
+ 'Wiki' => 'http://c2.com/cgi/wiki?%s'
+ ),
+ 'target' => '_blank',
+ 'css' => null
+ );
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $text = $options['text'];
+ if (isset($options['url'])) {
+ // calculated by the parser (e.g. Mediawiki)
+ $href = $options['url'];
+ } else {
+ $site = $options['site'];
+ // toggg 2006/02/05 page name must be url encoded (e.g. may contain spaces)
+ $page = $this->urlEncode($options['page']);
+
+ if (isset($this->conf['sites'][$site])) {
+ $href = $this->conf['sites'][$site];
+ } else {
+ return $text;
+ }
+
+ // old form where page is at end,
+ // or new form with %s placeholder for sprintf()?
+ if (strpos($href, '%s') === false) {
+ // use the old form
+ $href = $href . $page;
+ } else {
+ // use the new form
+ $href = sprintf($href, $page);
+ }
+ }
+
+ // allow for alternative targets
+ $target = $this->getConf('target');
+
+ // build base link
+ $css = $this->formatConf(' class="%s"', 'css');
+ $text = $this->textEncode($text);
+ $output = "<a$css href=\"$href\"";
+
+ // are we targeting a specific window?
+ if ($target && $target != '_self') {
+ // this is XHTML compliant, suggested by Aaron Kalin.
+ // code tip is actually from youngpup.net, and it
+ // uses the $target as the new window name.
+ $target = $this->textEncode($target);
+ $output .= " onclick=\"window.open(this.href, '$target');";
+ $output .= " return false;\"";
+ }
+
+ $output .= ">$text</a>";
+
+ return $output;
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Italic.php b/includes/pear/Text/Wiki/Render/Xhtml/Italic.php
new file mode 100644
index 0000000..b5cbed5
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Italic.php
@@ -0,0 +1,57 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Italic rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Italic.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders italic text in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Italic extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ $css = $this->formatConf(' class="%s"', 'css');
+ return "<i$css>";
+ }
+
+ if ($options['type'] == 'end') {
+ return '</i>';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/List.php b/includes/pear/Text/Wiki/Render/Xhtml/List.php
new file mode 100644
index 0000000..8ec06cb
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/List.php
@@ -0,0 +1,172 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * List rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: List.php 200073 2005-11-06 10:38:25Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders bullet and ordered lists in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_List extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css_ol' => null,
+ 'css_ol_li' => null,
+ 'css_ul' => null,
+ 'css_ul_li' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * This rendering method is syntactically and semantically compliant
+ * with XHTML 1.1 in that sub-lists are part of the previous list item.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ // make nice variables (type, level, count)
+ extract($options);
+
+ // set up indenting so that the results look nice; we do this
+ // in two steps to avoid str_pad mathematics. ;-)
+ $pad = str_pad('', $level, "\t");
+ $pad = str_replace("\t", ' ', $pad);
+
+ switch ($type) {
+
+ case 'bullet_list_start':
+
+ // build the base HTML
+ $css = $this->formatConf(' class="%s"', 'css_ul');
+ $html = "<ul$css>";
+
+ /*
+ // if this is the opening block for the list,
+ // put an extra newline in front of it so the
+ // output looks nice.
+ if ($level == 0) {
+ $html = "\n$html";
+ }
+ */
+
+ // done!
+ return $html;
+ break;
+
+ case 'bullet_list_end':
+
+ // build the base HTML
+ $html = "</li>\n$pad</ul>";
+
+ // if this is the closing block for the list,
+ // put extra newlines after it so the output
+ // looks nice.
+ if ($level == 0) {
+ $html .= "\n\n";
+ }
+
+ // done!
+ return $html;
+ break;
+
+ case 'number_list_start':
+ if (isset($format)) {
+ $format = ' type="' . $format . '"';
+ } else {
+ $format = '';
+ }
+ // build the base HTML
+ $css = $this->formatConf(' class="%s"', 'css_ol');
+ $html = "<ol{$format}{$css}>";
+
+ /*
+ // if this is the opening block for the list,
+ // put an extra newline in front of it so the
+ // output looks nice.
+ if ($level == 0) {
+ $html = "\n$html";
+ }
+ */
+
+ // done!
+ return $html;
+ break;
+
+ case 'number_list_end':
+
+ // build the base HTML
+ $html = "</li>\n$pad</ol>";
+
+ // if this is the closing block for the list,
+ // put extra newlines after it so the output
+ // looks nice.
+ if ($level == 0) {
+ $html .= "\n\n";
+ }
+
+ // done!
+ return $html;
+ break;
+
+ case 'bullet_item_start':
+ case 'number_item_start':
+
+ // pick the proper CSS class
+ if ($type == 'bullet_item_start') {
+ $css = $this->formatConf(' class="%s"', 'css_ul_li');
+ } else {
+ $css = $this->formatConf(' class="%s"', 'css_ol_li');
+ }
+
+ // build the base HTML
+ $html = "\n$pad<li$css>";
+
+ // for the very first item in the list, do nothing.
+ // but for additional items, be sure to close the
+ // previous item.
+ if ($count > 0) {
+ $html = "</li>$html";
+ }
+
+ // done!
+ return $html;
+ break;
+
+ case 'bullet_item_end':
+ case 'number_item_end':
+ default:
+ // ignore item endings and all other types.
+ // item endings are taken care of by the other types
+ // depending on their place in the list.
+ return '';
+ break;
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Newline.php b/includes/pear/Text/Wiki/Render/Xhtml/Newline.php
new file mode 100644
index 0000000..19676c6
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Newline.php
@@ -0,0 +1,35 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Newline rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Newline.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders new lines in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Newline extends Text_Wiki_Render {
+
+
+ function token($options)
+ {
+ return "<br />\n";
+ }
+}
+
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Page.php b/includes/pear/Text/Wiki/Render/Xhtml/Page.php
new file mode 100644
index 0000000..d9f638b
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Page.php
@@ -0,0 +1,46 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Page rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Page.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders page markers in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Page extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return 'PAGE MARKER HERE*&^%$#^$%*PAGEMARKERHERE';
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Paragraph.php b/includes/pear/Text/Wiki/Render/Xhtml/Paragraph.php
new file mode 100644
index 0000000..fc73627
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Paragraph.php
@@ -0,0 +1,59 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Paragraph rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Paragraph.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders paragraphs in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Paragraph extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ extract($options); //type
+
+ if ($type == 'start') {
+ $css = $this->formatConf(' class="%s"', 'css');
+ return "<p$css>";
+ }
+
+ if ($type == 'end') {
+ return "</p>\n\n";
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Phplookup.php b/includes/pear/Text/Wiki/Render/Xhtml/Phplookup.php
new file mode 100644
index 0000000..e77b0b7
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Phplookup.php
@@ -0,0 +1,81 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Phplookup rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Phplookup.php 231896 2007-03-15 00:08:47Z justinpatrin $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders a link to php functions description in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Phplookup extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'target' => '_blank',
+ 'css' => null
+ );
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $text = trim($options['text']);
+ $css = $this->formatConf(' class="%s"', 'css');
+
+ // start the html
+ $output = "<a$css";
+
+ // are we targeting another window?
+ $target = $this->getConf('target', '');
+ if ($target && $target != '_self') {
+ // use a "popup" window. this is XHTML compliant, suggested by
+ // Aaron Kalin. uses the $target as the new window name.
+ $target = $this->textEncode($target);
+ $output .= " onclick=\"window.open(this.href, '$target');";
+ $output .= " return false;\"";
+ }
+
+ // take off the final parens for functions
+ if (substr($text, -2) == '()') {
+ $q = substr($text, 0, -2);
+ } else {
+ $q = $text;
+ }
+
+ // toggg 2006/02/05 page name must be url encoded (e.g. may contain spaces)
+ $q = $this->urlEncode($q);
+ $text = $this->textEncode($text);
+
+ // finish and return
+ $output .= " href=\"http://php.net/$q\">$text</a>";
+ return $output;
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Plugin.php b/includes/pear/Text/Wiki/Render/Xhtml/Plugin.php
new file mode 100644
index 0000000..048ba80
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Plugin.php
@@ -0,0 +1,47 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Plugin rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Plugin.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders wiki plugins in XHTML. (empty)
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Plugin extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ * Plugins produce wiki markup so are processed by parsing, no tokens produced
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return '';
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Prefilter.php b/includes/pear/Text/Wiki/Render/Xhtml/Prefilter.php
new file mode 100644
index 0000000..b0ae26e
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Prefilter.php
@@ -0,0 +1,34 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Prefilter rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Prefilter.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class implements a Text_Wiki_Render_Xhtml to "pre-filter" source text so
+ * that line endings are consistently \n, lines ending in a backslash \
+ * are concatenated with the next line, and tabs are converted to spaces.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Prefilter extends Text_Wiki_Render {
+ function token()
+ {
+ return '';
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Preformatted.php b/includes/pear/Text/Wiki/Render/Xhtml/Preformatted.php
new file mode 100644
index 0000000..a68ce76
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Preformatted.php
@@ -0,0 +1,47 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Preformatted rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Preformatted.php 229275 2007-02-07 13:40:44Z mic $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders preformated text in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Preformatted extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ $text = $this->textEncode($options['text']);
+ return '<pre>'.$text.'</pre>';
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Raw.php b/includes/pear/Text/Wiki/Render/Xhtml/Raw.php
new file mode 100644
index 0000000..3027153
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Raw.php
@@ -0,0 +1,46 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Raw rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Raw.php 214538 2006-06-09 21:32:24Z justinpatrin $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders not processed blocks in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Raw extends Text_Wiki_Render {
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ return $this->textEncode($options['text']);
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Revise.php b/includes/pear/Text/Wiki/Render/Xhtml/Revise.php
new file mode 100644
index 0000000..f81fd7f
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Revise.php
@@ -0,0 +1,68 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Revise rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Revise.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders revision marks in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Revise extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css_ins' => null,
+ 'css_del' => null
+ );
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'del_start') {
+ $css = $this->formatConf(' class="%s"', 'css_del');
+ return "<del$css>";
+ }
+
+ if ($options['type'] == 'del_end') {
+ return "</del>";
+ }
+
+ if ($options['type'] == 'ins_start') {
+ $css = $this->formatConf(' class="%s"', 'css_ins');
+ return "<ins$css>";
+ }
+
+ if ($options['type'] == 'ins_end') {
+ return "</ins>";
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Smiley.php b/includes/pear/Text/Wiki/Render/Xhtml/Smiley.php
new file mode 100644
index 0000000..778796b
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Smiley.php
@@ -0,0 +1,74 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Smiley rule Xhtml renderer
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Smiley.php 206940 2006-02-10 23:07:03Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * Smiley rule Xhtml render class
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Bertrand Gugger <bertrand@toggg.com>
+ * @copyright 2005 bertrand Gugger
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ * @see Text_Wiki::Text_Wiki_Render()
+ */
+class Text_Wiki_Render_Xhtml_Smiley extends Text_Wiki_Render {
+
+ /**
+ * Configuration keys for this rule
+ * 'prefix' => the path to smileys images inclusive file name prefix,
+ * starts with '/' ==> abolute reference
+ * if no file names prefix but some folder, terminates with '/'
+ * 'extension' => the file extension (inclusive '.'), e.g. :
+ * if prefix 'smileys/icon_' and extension '.gif'
+ * ':)' whose name is 'smile' will give relative file 'smileys/icon_smile.gif'
+ * if prefix '/image/smileys/' and extension '.png': absolute '/image/smileys/smile.gif'
+ * 'css' => optional style applied to smileys
+ *
+ * @access public
+ * @var array 'config-key' => mixed config-value
+ */
+ var $conf = array(
+ 'prefix' => 'images/smiles/icon_',
+ 'extension' => '.gif',
+ 'css' => null
+ );
+
+ /**
+ * Renders a token into text matching the requested format.
+ * process the Smileys
+ *
+ * @access public
+ * @param array $options The "options" portion of the token (second element).
+ * @return string The text rendered from the token options.
+ */
+ function token($options)
+ {
+ $imageFile = $this->getConf('prefix') . $options['name'] . $this->getConf('extension');
+
+ // attempt to get the image size
+ $imageSize = @getimagesize($imageFile);
+
+ // return the HTML output
+ return '<img src="' . $this->textEncode($imageFile) . '"' .
+ (is_array($imageSize) ?
+ ' width="' . $imageSize[0] . '" height="' . $imageSize[1] .'"' : '') .
+ ' alt="' . $options['desc'] . '"' .
+ $this->formatConf(' class="%s"', 'css') . ' />';
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Specialchar.php b/includes/pear/Text/Wiki/Render/Xhtml/Specialchar.php
new file mode 100644
index 0000000..4372c02
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Specialchar.php
@@ -0,0 +1,52 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Specialchar rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Specialchar.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders special characters in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_SpecialChar extends Text_Wiki_Render {
+
+ var $types = array('~bs~' => '&#92;',
+ '~hs~' => '&nbsp;',
+ '~amp~' => '&amp;',
+ '~ldq~' => '&ldquo;',
+ '~rdq~' => '&rdquo;',
+ '~lsq~' => '&lsquo;',
+ '~rsq~' => '&rsquo;',
+ '~c~' => '&copy;',
+ '~--~' => '&mdash;',
+ '" -- "' => '&mdash;',
+ '&quot; -- &quot;' => '&mdash;',
+ '~lt~' => '&lt;',
+ '~gt~' => '&gt;');
+
+ function token($options)
+ {
+ if (isset($this->types[$options['char']])) {
+ return $this->types[$options['char']];
+ } else {
+ return '&#'.substr($options['char'], 1, -1).';';
+ }
+ }
+}
+
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Strong.php b/includes/pear/Text/Wiki/Render/Xhtml/Strong.php
new file mode 100644
index 0000000..da816a7
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Strong.php
@@ -0,0 +1,58 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Strong rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Strong.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders text marked as strong in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Strong extends Text_Wiki_Render {
+
+
+ var $conf = array(
+ 'css' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ $css = $this->formatConf(' class="%s"', 'css');
+ return "<strong$css>";
+ }
+
+ if ($options['type'] == 'end') {
+ return '</strong>';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Subscript.php b/includes/pear/Text/Wiki/Render/Xhtml/Subscript.php
new file mode 100644
index 0000000..653a110
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Subscript.php
@@ -0,0 +1,57 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Subscript rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Subscript.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders subscript text in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Subscript extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ $css = $this->formatConf(' class="%s"', 'css');
+ return "<sub$css>";
+ }
+
+ if ($options['type'] == 'end') {
+ return '</sub>';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Superscript.php b/includes/pear/Text/Wiki/Render/Xhtml/Superscript.php
new file mode 100644
index 0000000..1a39453
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Superscript.php
@@ -0,0 +1,57 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Superscript rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Superscript.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders superscript text in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Superscript extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ $css = $this->formatConf(' class="%s"', 'css');
+ return "<sup$css>";
+ }
+
+ if ($options['type'] == 'end') {
+ return '</sup>';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Table.php b/includes/pear/Text/Wiki/Render/Xhtml/Table.php
new file mode 100644
index 0000000..670ff7b
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Table.php
@@ -0,0 +1,140 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Table rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Table.php 202250 2005-12-06 15:29:29Z ritzmo $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders tables in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Table extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css_table' => null,
+ 'css_caption' => null,
+ 'css_tr' => null,
+ 'css_th' => null,
+ 'css_td' => null
+ );
+
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ // make nice variable names (type, attr, span)
+ $span = $rowspan = 1;
+ extract($options);
+
+ // free format
+ $format = isset($format) ? ' '. $format : '';
+
+ $pad = ' ';
+
+ switch ($type) {
+
+ case 'table_start':
+ $css = $this->formatConf(' class="%s"', 'css_table');
+ return "\n\n<table$css$format>\n";
+ break;
+
+ case 'table_end':
+ return "</table>\n\n";
+ break;
+
+ case 'caption_start':
+ $css = $this->formatConf(' class="%s"', 'css_caption');
+ return "<caption$css$format>\n";
+ break;
+
+ case 'caption_end':
+ return "</caption>\n";
+ break;
+
+ case 'row_start':
+ $css = $this->formatConf(' class="%s"', 'css_tr');
+ return "$pad<tr$css$format>\n";
+ break;
+
+ case 'row_end':
+ return "$pad</tr>\n";
+ break;
+
+ case 'cell_start':
+
+ // base html
+ $html = $pad . $pad;
+
+ // is this a TH or TD cell?
+ if ($attr == 'header') {
+ // start a header cell
+ $css = $this->formatConf(' class="%s"', 'css_th');
+ $html .= "<th$css";
+ } else {
+ // start a normal cell
+ $css = $this->formatConf(' class="%s"', 'css_td');
+ $html .= "<td$css";
+ }
+
+ // add the column span
+ if ($span > 1) {
+ $html .= " colspan=\"$span\"";
+ }
+
+ // add the row span
+ if ($rowspan > 1) {
+ $html .= " rowspan=\"$rowspan\"";
+ }
+
+ // add alignment
+ if ($attr != 'header' && $attr != '') {
+ $html .= " style=\"text-align: $attr;\"";
+ }
+
+ // done!
+ $html .= "$format>";
+ return $html;
+ break;
+
+ case 'cell_end':
+ if ($attr == 'header') {
+ return "</th>\n";
+ } else {
+ return "</td>\n";
+ }
+ break;
+
+ default:
+ return '';
+
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Tighten.php b/includes/pear/Text/Wiki/Render/Xhtml/Tighten.php
new file mode 100644
index 0000000..30d6dfd
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Tighten.php
@@ -0,0 +1,34 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Tighten rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Tighten.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class makes the tightening in XHTML. (empty)
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Tighten extends Text_Wiki_Render {
+
+
+ function token()
+ {
+ return '';
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Titlebar.php b/includes/pear/Text/Wiki/Render/Xhtml/Titlebar.php
new file mode 100644
index 0000000..d6e24a8
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Titlebar.php
@@ -0,0 +1,57 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Titlebar rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Titlebar.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders a title bar in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Titlebar extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css' => 'titlebar'
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ $css = $this->formatConf(' class="%s"', 'css');
+ return "<div$css>";
+ }
+
+ if ($options['type'] == 'end') {
+ return '</div>';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Toc.php b/includes/pear/Text/Wiki/Render/Xhtml/Toc.php
new file mode 100644
index 0000000..2b68b68
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Toc.php
@@ -0,0 +1,115 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Toc rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Toc.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class inserts a table of content in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Toc extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css_list' => null,
+ 'css_item' => null,
+ 'title' => '<strong>Table of Contents</strong>',
+ 'div_id' => 'toc',
+ 'collapse' => true
+ );
+
+ var $min = 2;
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ // type, id, level, count, attr
+ extract($options);
+
+ switch ($type) {
+
+ case 'list_start':
+
+ $css = $this->getConf('css_list');
+ $html = '';
+
+ // collapse div within a table?
+ if ($this->getConf('collapse')) {
+ $html .= '<table border="0" cellspacing="0" cellpadding="0">';
+ $html .= "<tr><td>\n";
+ }
+
+ // add the div, class, and id
+ $html .= '<div';
+ if ($css) {
+ $html .= " class=\"$css\"";
+ }
+
+ $div_id = $this->getConf('div_id');
+ if ($div_id) {
+ $html .= " id=\"$div_id\"";
+ }
+
+ // add the title, and done
+ $html .= '>';
+ $html .= $this->getConf('title');
+ return $html;
+ break;
+
+ case 'list_end':
+ if ($this->getConf('collapse')) {
+ return "\n</div>\n</td></tr></table>\n\n";
+ } else {
+ return "\n</div>\n\n";
+ }
+ break;
+
+ case 'item_start':
+ $html = "\n\t<div";
+
+ $css = $this->getConf('css_item');
+ if ($css) {
+ $html .= " class=\"$css\"";
+ }
+
+ $pad = ($level - $this->min);
+ $html .= " style=\"margin-left: {$pad}em;\">";
+
+ $html .= "<a href=\"#$id\">";
+ return $html;
+ break;
+
+ case 'item_end':
+ return "</a></div>";
+ break;
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Tt.php b/includes/pear/Text/Wiki/Render/Xhtml/Tt.php
new file mode 100644
index 0000000..3cefe85
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Tt.php
@@ -0,0 +1,58 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Tt rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Tt.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders monospaced text in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Tt extends Text_Wiki_Render {
+
+
+ var $conf = array(
+ 'css' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ $css = $this->formatConf(' class="%s"', 'css');
+ return "<tt$css>";
+ }
+
+ if ($options['type'] == 'end') {
+ return '</tt>';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Underline.php b/includes/pear/Text/Wiki/Render/Xhtml/Underline.php
new file mode 100644
index 0000000..a6cbf59
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Underline.php
@@ -0,0 +1,57 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Underline rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Underline.php 191862 2005-07-30 08:03:29Z toggg $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders underlined text in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Underline extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'css' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ if ($options['type'] == 'start') {
+ $css = $this->formatConf(' class="%s"', 'css');
+ return "<u$css>";
+ }
+
+ if ($options['type'] == 'end') {
+ return '</u>';
+ }
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Url.php b/includes/pear/Text/Wiki/Render/Xhtml/Url.php
new file mode 100644
index 0000000..9139be7
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Url.php
@@ -0,0 +1,131 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Url rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Url.php 236400 2007-05-26 17:15:41Z mic $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders URL links in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Url extends Text_Wiki_Render {
+
+
+ var $conf = array(
+ 'target' => '_blank',
+ 'images' => true,
+ 'img_ext' => array('jpg', 'jpeg', 'gif', 'png'),
+ 'css_inline' => null,
+ 'css_footnote' => null,
+ 'css_descr' => null,
+ 'css_img' => null
+ );
+
+ /**
+ *
+ * Renders a token into text matching the requested format.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ // create local variables from the options array (text,
+ // href, type)
+ extract($options);
+
+ // find the rightmost dot and determine the filename
+ // extension.
+ $pos = strrpos($href, '.');
+ $ext = strtolower(substr($href, $pos + 1));
+ $href = $this->textEncode($href);
+
+ // does the filename extension indicate an image file?
+ if ($this->getConf('images') &&
+ in_array($ext, $this->getConf('img_ext', array()))) {
+
+ // create alt text for the image
+ if (! isset($text) || $text == '') {
+ $text = basename($href);
+ $text = $this->textEncode($text);
+ }
+
+ // generate an image tag
+ $css = $this->formatConf(' class="%s"', 'css_img');
+ $start = "<img$css src=\"$href\" alt=\"$text\" title=\"$text\" /><!-- ";
+ $end = " -->";
+
+ } else {
+
+ // should we build a target clause?
+ if ($href{0} == '#' ||
+ strtolower(substr($href, 0, 7)) == 'mailto:') {
+ // targets not allowed for on-page anchors
+ // and mailto: links.
+ $target = '';
+ } else {
+ // allow targets on non-anchor non-mailto links
+ $target = $this->getConf('target');
+ }
+
+ // generate a regular link (not an image)
+ $text = $this->textEncode($text);
+ $css = $this->formatConf(' class="%s"', "css_$type");
+ $start = "<a$css href=\"$href\"";
+
+ if ($target && $target != '_self') {
+ // use a "popup" window. this is XHTML compliant, suggested by
+ // Aaron Kalin. uses the $target as the new window name.
+ $target = $this->textEncode($target);
+ $start .= " onclick=\"window.open(this.href, '$target');";
+ $start .= " return false;\"";
+ }
+
+ if (isset($name)) {
+ $start .= " id=\"$name\"";
+ }
+
+ // finish up output
+ $start .= ">";
+ $end = "</a>";
+
+ // make numbered references look like footnotes when no
+ // CSS class specified, make them superscript by default
+ if ($type == 'footnote' && ! $css) {
+ $start = '<sup>' . $start;
+ $end = $end . '</sup>';
+ }
+ }
+
+ if ($options['type'] == 'start') {
+ $output = $start;
+ } else if ($options['type'] == 'end') {
+ $output = $end;
+ } else {
+ $output = $start . $text . $end;
+ }
+ return $output;
+ }
+}
+?>
diff --git a/includes/pear/Text/Wiki/Render/Xhtml/Wikilink.php b/includes/pear/Text/Wiki/Render/Xhtml/Wikilink.php
new file mode 100644
index 0000000..b93b000
--- /dev/null
+++ b/includes/pear/Text/Wiki/Render/Xhtml/Wikilink.php
@@ -0,0 +1,177 @@
+<?php
+// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+/**
+ * Wikilink rule end renderer for Xhtml
+ *
+ * PHP versions 4 and 5
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version CVS: $Id: Wikilink.php 224670 2006-12-08 21:25:24Z justinpatrin $
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+
+/**
+ * This class renders wiki links in XHTML.
+ *
+ * @category Text
+ * @package Text_Wiki
+ * @author Paul M. Jones <pmjones@php.net>
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Text_Wiki
+ */
+class Text_Wiki_Render_Xhtml_Wikilink extends Text_Wiki_Render {
+
+ var $conf = array(
+ 'pages' => array(), // set to null or false to turn off page checks
+ 'view_url' => 'http://example.com/index.php?page=%s',
+ 'new_url' => 'http://example.com/new.php?page=%s',
+ 'new_text' => '?',
+ 'new_text_pos' => 'after', // 'before', 'after', or null/false
+ 'css' => null,
+ 'css_new' => null,
+ 'exists_callback' => null // call_user_func() callback
+ );
+
+
+ /**
+ *
+ * Renders a token into XHTML.
+ *
+ * @access public
+ *
+ * @param array $options The "options" portion of the token (second
+ * element).
+ *
+ * @return string The text rendered from the token options.
+ *
+ */
+
+ function token($options)
+ {
+ // make nice variable names (page, anchor, text)
+ extract($options);
+
+ // is there a "page existence" callback?
+ // we need to access it directly instead of through
+ // getConf() because we'll need a reference (for
+ // object instance method callbacks).
+ if (isset($this->conf['exists_callback'])) {
+ $callback =& $this->conf['exists_callback'];
+ } else {
+ $callback = false;
+ }
+
+ if ($callback) {
+ // use the callback function
+ $exists = call_user_func($callback, $page);
+ } else {
+ // no callback, go to the naive page array.
+ $list = $this->getConf('pages');
+ if (is_array($list)) {
+ // yes, check against the page list
+ $exists = in_array($page, $list);
+ } else {
+ // no, assume it exists
+ $exists = true;
+ }
+ }
+
+ $anchor = '#'.$this->urlEncode(substr($anchor, 1));
+
+ // does the page exist?
+ if ($exists) {
+
+ // PAGE EXISTS.
+
+ // link to the page view, but we have to build
+ // the HREF. we support both the old form where
+ // the page always comes at the end, and the new
+ // form that uses %s for sprintf()
+ $href = $this->getConf('view_url');
+
+ if (strpos($href, '%s') === false) {
+ // use the old form (page-at-end)
+ $href = $href . $this->urlEncode($page) . $anchor;
+ } else {
+ // use the new form (sprintf format string)
+ $href = sprintf($href, $this->urlEncode($page)) . $anchor;
+ }
+
+ // get the CSS class and generate output
+ $css = ' class="'.$this->textEncode($this->getConf('css')).'"';
+
+ $start = '<a'.$css.' href="'.$this->textEncode($href).'">';
+ $end = '</a>';
+ } else {
+
+ // PAGE DOES NOT EXIST.
+
+ // link to a create-page url, but only if new_url is set
+ $href = $this->getConf('new_url', null);
+
+ // set the proper HREF
+ if (! $href || trim($href) == '') {
+
+ // no useful href, return the text as it is
+ //TODO: This is no longer used, need to look closer into this branch
+ $output = $text;
+
+ } else {
+
+ // yes, link to the new-page href, but we have to build
+ // it. we support both the old form where
+ // the page always comes at the end, and the new
+ // form that uses sprintf()
+ if (strpos($href, '%s') === false) {
+ // use the old form
+ $href = $href . $this->urlEncode($page);
+ } else {
+ // use the new form
+ $href = sprintf($href, $this->urlEncode($page));
+ }
+ }
+
+ // get the appropriate CSS class and new-link text
+ $css = ' class="'.$this->textEncode($this->getConf('css_new')).'"';
+ $new = $this->getConf('new_text');
+
+ // what kind of linking are we doing?
+ $pos = $this->getConf('new_text_pos');
+ if (! $pos || ! $new) {
+ // no position (or no new_text), use css only on the page name
+
+ $start = '<a'.$css.' href="'.$this->textEncode($href).'">';
+ $end = '</a>';
+ } elseif ($pos == 'before') {
+ // use the new_text BEFORE the page name
+ $start = '<a'.$css.' href="'.$this->textEncode($href).'">'.$this->textEncode($new).'</a>';
+ $end = '';
+ } else {
+ // default, use the new_text link AFTER the page name
+ $start = '';
+ $end = '<a'.$css.' href="'.$this->textEncode($href).'">'.$this->textEncode($new).'</a>';
+ }
+ }
+ if (!strlen($text)) {
+ $start .= $this->textEncode($page);
+ }
+ if (isset($type)) {
+ switch ($type) {
+ case 'start':
+ $output = $start;
+ break;
+ case 'end':
+ $output = $end;
+ break;
+ }
+ } else {
+ $output = $start.$this->textEncode($text).$end;
+ }
+ return $output;
+ }
+}
+?>
diff --git a/includes/pear/XML/Util.php b/includes/pear/XML/Util.php
new file mode 100644
index 0000000..f5927b1
--- /dev/null
+++ b/includes/pear/XML/Util.php
@@ -0,0 +1,911 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * XML_Util
+ *
+ * XML Utilities package
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2003-2008 Stephan Schmidt <schst@php.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * The name of the author may not 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.
+ *
+ * @category XML
+ * @package XML_Util
+ * @author Stephan Schmidt <schst@php.net>
+ * @copyright 2003-2008 Stephan Schmidt <schst@php.net>
+ * @license http://opensource.org/licenses/bsd-license New BSD License
+ * @version CVS: $Id: Util.php,v 1.38 2008/11/13 00:03:38 ashnazg Exp $
+ * @link http://pear.php.net/package/XML_Util
+ */
+
+/**
+ * error code for invalid chars in XML name
+ */
+define('XML_UTIL_ERROR_INVALID_CHARS', 51);
+
+/**
+ * error code for invalid chars in XML name
+ */
+define('XML_UTIL_ERROR_INVALID_START', 52);
+
+/**
+ * error code for non-scalar tag content
+ */
+define('XML_UTIL_ERROR_NON_SCALAR_CONTENT', 60);
+
+/**
+ * error code for missing tag name
+ */
+define('XML_UTIL_ERROR_NO_TAG_NAME', 61);
+
+/**
+ * replace XML entities
+ */
+define('XML_UTIL_REPLACE_ENTITIES', 1);
+
+/**
+ * embedd content in a CData Section
+ */
+define('XML_UTIL_CDATA_SECTION', 5);
+
+/**
+ * do not replace entitites
+ */
+define('XML_UTIL_ENTITIES_NONE', 0);
+
+/**
+ * replace all XML entitites
+ * This setting will replace <, >, ", ' and &
+ */
+define('XML_UTIL_ENTITIES_XML', 1);
+
+/**
+ * replace only required XML entitites
+ * This setting will replace <, " and &
+ */
+define('XML_UTIL_ENTITIES_XML_REQUIRED', 2);
+
+/**
+ * replace HTML entitites
+ * @link http://www.php.net/htmlentities
+ */
+define('XML_UTIL_ENTITIES_HTML', 3);
+
+/**
+ * Collapse all empty tags.
+ */
+define('XML_UTIL_COLLAPSE_ALL', 1);
+
+/**
+ * Collapse only empty XHTML tags that have no end tag.
+ */
+define('XML_UTIL_COLLAPSE_XHTML_ONLY', 2);
+
+/**
+ * utility class for working with XML documents
+ *
+
+ * @category XML
+ * @package XML_Util
+ * @author Stephan Schmidt <schst@php.net>
+ * @copyright 2003-2008 Stephan Schmidt <schst@php.net>
+ * @license http://opensource.org/licenses/bsd-license New BSD License
+ * @version Release: 1.2.1
+ * @link http://pear.php.net/package/XML_Util
+ */
+class XML_Util
+{
+ /**
+ * return API version
+ *
+ * @return string $version API version
+ * @access public
+ * @static
+ */
+ function apiVersion()
+ {
+ return '1.1';
+ }
+
+ /**
+ * replace XML entities
+ *
+ * With the optional second parameter, you may select, which
+ * entities should be replaced.
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // replace XML entites:
+ * $string = XML_Util::replaceEntities('This string contains < & >.');
+ * </code>
+ *
+ * With the optional third parameter, you may pass the character encoding
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // replace XML entites in UTF-8:
+ * $string = XML_Util::replaceEntities(
+ * 'This string contains < & > as well as ä, ö, ß, à and ê',
+ * XML_UTIL_ENTITIES_HTML,
+ * 'UTF-8'
+ * );
+ * </code>
+ *
+ * @param string $string string where XML special chars
+ * should be replaced
+ * @param int $replaceEntities setting for entities in attribute values
+ * (one of XML_UTIL_ENTITIES_XML,
+ * XML_UTIL_ENTITIES_XML_REQUIRED,
+ * XML_UTIL_ENTITIES_HTML)
+ * @param string $encoding encoding value (if any)...
+ * must be a valid encoding as determined
+ * by the htmlentities() function
+ *
+ * @return string string with replaced chars
+ * @access public
+ * @static
+ * @see reverseEntities()
+ */
+ function replaceEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML,
+ $encoding = 'ISO-8859-1')
+ {
+ switch ($replaceEntities) {
+ case XML_UTIL_ENTITIES_XML:
+ return strtr($string, array(
+ '&' => '&amp;',
+ '>' => '&gt;',
+ '<' => '&lt;',
+ '"' => '&quot;',
+ '\'' => '&apos;' ));
+ break;
+ case XML_UTIL_ENTITIES_XML_REQUIRED:
+ return strtr($string, array(
+ '&' => '&amp;',
+ '<' => '&lt;',
+ '"' => '&quot;' ));
+ break;
+ case XML_UTIL_ENTITIES_HTML:
+ return htmlentities($string, ENT_COMPAT, $encoding);
+ break;
+ }
+ return $string;
+ }
+
+ /**
+ * reverse XML entities
+ *
+ * With the optional second parameter, you may select, which
+ * entities should be reversed.
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // reverse XML entites:
+ * $string = XML_Util::reverseEntities('This string contains &lt; &amp; &gt;.');
+ * </code>
+ *
+ * With the optional third parameter, you may pass the character encoding
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // reverse XML entites in UTF-8:
+ * $string = XML_Util::reverseEntities(
+ * 'This string contains &lt; &amp; &gt; as well as'
+ * . ' &auml;, &ouml;, &szlig;, &agrave; and &ecirc;',
+ * XML_UTIL_ENTITIES_HTML,
+ * 'UTF-8'
+ * );
+ * </code>
+ *
+ * @param string $string string where XML special chars
+ * should be replaced
+ * @param int $replaceEntities setting for entities in attribute values
+ * (one of XML_UTIL_ENTITIES_XML,
+ * XML_UTIL_ENTITIES_XML_REQUIRED,
+ * XML_UTIL_ENTITIES_HTML)
+ * @param string $encoding encoding value (if any)...
+ * must be a valid encoding as determined
+ * by the html_entity_decode() function
+ *
+ * @return string string with replaced chars
+ * @access public
+ * @static
+ * @see replaceEntities()
+ */
+ function reverseEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML,
+ $encoding = 'ISO-8859-1')
+ {
+ switch ($replaceEntities) {
+ case XML_UTIL_ENTITIES_XML:
+ return strtr($string, array(
+ '&amp;' => '&',
+ '&gt;' => '>',
+ '&lt;' => '<',
+ '&quot;' => '"',
+ '&apos;' => '\'' ));
+ break;
+ case XML_UTIL_ENTITIES_XML_REQUIRED:
+ return strtr($string, array(
+ '&amp;' => '&',
+ '&lt;' => '<',
+ '&quot;' => '"' ));
+ break;
+ case XML_UTIL_ENTITIES_HTML:
+ return html_entity_decode($string, ENT_COMPAT, $encoding);
+ break;
+ }
+ return $string;
+ }
+
+ /**
+ * build an xml declaration
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // get an XML declaration:
+ * $xmlDecl = XML_Util::getXMLDeclaration('1.0', 'UTF-8', true);
+ * </code>
+ *
+ * @param string $version xml version
+ * @param string $encoding character encoding
+ * @param bool $standalone document is standalone (or not)
+ *
+ * @return string xml declaration
+ * @access public
+ * @static
+ * @uses attributesToString() to serialize the attributes of the XML declaration
+ */
+ function getXMLDeclaration($version = '1.0', $encoding = null,
+ $standalone = null)
+ {
+ $attributes = array(
+ 'version' => $version,
+ );
+ // add encoding
+ if ($encoding !== null) {
+ $attributes['encoding'] = $encoding;
+ }
+ // add standalone, if specified
+ if ($standalone !== null) {
+ $attributes['standalone'] = $standalone ? 'yes' : 'no';
+ }
+
+ return sprintf('<?xml%s?>',
+ XML_Util::attributesToString($attributes, false));
+ }
+
+ /**
+ * build a document type declaration
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // get a doctype declaration:
+ * $xmlDecl = XML_Util::getDocTypeDeclaration('rootTag','myDocType.dtd');
+ * </code>
+ *
+ * @param string $root name of the root tag
+ * @param string $uri uri of the doctype definition
+ * (or array with uri and public id)
+ * @param string $internalDtd internal dtd entries
+ *
+ * @return string doctype declaration
+ * @access public
+ * @static
+ * @since 0.2
+ */
+ function getDocTypeDeclaration($root, $uri = null, $internalDtd = null)
+ {
+ if (is_array($uri)) {
+ $ref = sprintf(' PUBLIC "%s" "%s"', $uri['id'], $uri['uri']);
+ } elseif (!empty($uri)) {
+ $ref = sprintf(' SYSTEM "%s"', $uri);
+ } else {
+ $ref = '';
+ }
+
+ if (empty($internalDtd)) {
+ return sprintf('<!DOCTYPE %s%s>', $root, $ref);
+ } else {
+ return sprintf("<!DOCTYPE %s%s [\n%s\n]>", $root, $ref, $internalDtd);
+ }
+ }
+
+ /**
+ * create string representation of an attribute list
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // build an attribute string
+ * $att = array(
+ * 'foo' => 'bar',
+ * 'argh' => 'tomato'
+ * );
+ *
+ * $attList = XML_Util::attributesToString($att);
+ * </code>
+ *
+ * @param array $attributes attribute array
+ * @param bool|array $sort sort attribute list alphabetically,
+ * may also be an assoc array containing
+ * the keys 'sort', 'multiline', 'indent',
+ * 'linebreak' and 'entities'
+ * @param bool $multiline use linebreaks, if more than
+ * one attribute is given
+ * @param string $indent string used for indentation of
+ * multiline attributes
+ * @param string $linebreak string used for linebreaks of
+ * multiline attributes
+ * @param int $entities setting for entities in attribute values
+ * (one of XML_UTIL_ENTITIES_NONE,
+ * XML_UTIL_ENTITIES_XML,
+ * XML_UTIL_ENTITIES_XML_REQUIRED,
+ * XML_UTIL_ENTITIES_HTML)
+ *
+ * @return string string representation of the attributes
+ * @access public
+ * @static
+ * @uses replaceEntities() to replace XML entities in attribute values
+ * @todo allow sort also to be an options array
+ */
+ function attributesToString($attributes, $sort = true, $multiline = false,
+ $indent = ' ', $linebreak = "\n", $entities = XML_UTIL_ENTITIES_XML)
+ {
+ /*
+ * second parameter may be an array
+ */
+ if (is_array($sort)) {
+ if (isset($sort['multiline'])) {
+ $multiline = $sort['multiline'];
+ }
+ if (isset($sort['indent'])) {
+ $indent = $sort['indent'];
+ }
+ if (isset($sort['linebreak'])) {
+ $multiline = $sort['linebreak'];
+ }
+ if (isset($sort['entities'])) {
+ $entities = $sort['entities'];
+ }
+ if (isset($sort['sort'])) {
+ $sort = $sort['sort'];
+ } else {
+ $sort = true;
+ }
+ }
+ $string = '';
+ if (is_array($attributes) && !empty($attributes)) {
+ if ($sort) {
+ ksort($attributes);
+ }
+ if ( !$multiline || count($attributes) == 1) {
+ foreach ($attributes as $key => $value) {
+ if ($entities != XML_UTIL_ENTITIES_NONE) {
+ if ($entities === XML_UTIL_CDATA_SECTION) {
+ $entities = XML_UTIL_ENTITIES_XML;
+ }
+ $value = XML_Util::replaceEntities($value, $entities);
+ }
+ $string .= ' ' . $key . '="' . $value . '"';
+ }
+ } else {
+ $first = true;
+ foreach ($attributes as $key => $value) {
+ if ($entities != XML_UTIL_ENTITIES_NONE) {
+ $value = XML_Util::replaceEntities($value, $entities);
+ }
+ if ($first) {
+ $string .= ' ' . $key . '="' . $value . '"';
+ $first = false;
+ } else {
+ $string .= $linebreak . $indent . $key . '="' . $value . '"';
+ }
+ }
+ }
+ }
+ return $string;
+ }
+
+ /**
+ * Collapses empty tags.
+ *
+ * @param string $xml XML
+ * @param int $mode Whether to collapse all empty tags (XML_UTIL_COLLAPSE_ALL)
+ * or only XHTML (XML_UTIL_COLLAPSE_XHTML_ONLY) ones.
+ *
+ * @return string XML
+ * @access public
+ * @static
+ * @todo PEAR CS - unable to avoid "space after open parens" error
+ * in the IF branch
+ */
+ function collapseEmptyTags($xml, $mode = XML_UTIL_COLLAPSE_ALL)
+ {
+ if ($mode == XML_UTIL_COLLAPSE_XHTML_ONLY) {
+ return preg_replace(
+ '/<(area|base(?:font)?|br|col|frame|hr|img|input|isindex|link|meta|'
+ . 'param)([^>]*)><\/\\1>/s',
+ '<\\1\\2 />',
+ $xml);
+ } else {
+ return preg_replace('/<(\w+)([^>]*)><\/\\1>/s', '<\\1\\2 />', $xml);
+ }
+ }
+
+ /**
+ * create a tag
+ *
+ * This method will call XML_Util::createTagFromArray(), which
+ * is more flexible.
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // create an XML tag:
+ * $tag = XML_Util::createTag('myNs:myTag',
+ * array('foo' => 'bar'),
+ * 'This is inside the tag',
+ * 'http://www.w3c.org/myNs#');
+ * </code>
+ *
+ * @param string $qname qualified tagname (including namespace)
+ * @param array $attributes array containg attributes
+ * @param mixed $content the content
+ * @param string $namespaceUri URI of the namespace
+ * @param int $replaceEntities whether to replace XML special chars in
+ * content, embedd it in a CData section
+ * or none of both
+ * @param bool $multiline whether to create a multiline tag where
+ * each attribute gets written to a single line
+ * @param string $indent string used to indent attributes
+ * (_auto indents attributes so they start
+ * at the same column)
+ * @param string $linebreak string used for linebreaks
+ * @param bool $sortAttributes Whether to sort the attributes or not
+ *
+ * @return string XML tag
+ * @access public
+ * @static
+ * @see createTagFromArray()
+ * @uses createTagFromArray() to create the tag
+ */
+ function createTag($qname, $attributes = array(), $content = null,
+ $namespaceUri = null, $replaceEntities = XML_UTIL_REPLACE_ENTITIES,
+ $multiline = false, $indent = '_auto', $linebreak = "\n",
+ $sortAttributes = true)
+ {
+ $tag = array(
+ 'qname' => $qname,
+ 'attributes' => $attributes
+ );
+
+ // add tag content
+ if ($content !== null) {
+ $tag['content'] = $content;
+ }
+
+ // add namespace Uri
+ if ($namespaceUri !== null) {
+ $tag['namespaceUri'] = $namespaceUri;
+ }
+
+ return XML_Util::createTagFromArray($tag, $replaceEntities, $multiline,
+ $indent, $linebreak, $sortAttributes);
+ }
+
+ /**
+ * create a tag from an array
+ * this method awaits an array in the following format
+ * <pre>
+ * array(
+ * // qualified name of the tag
+ * 'qname' => $qname
+ *
+ * // namespace prefix (optional, if qname is specified or no namespace)
+ * 'namespace' => $namespace
+ *
+ * // local part of the tagname (optional, if qname is specified)
+ * 'localpart' => $localpart,
+ *
+ * // array containing all attributes (optional)
+ * 'attributes' => array(),
+ *
+ * // tag content (optional)
+ * 'content' => $content,
+ *
+ * // namespaceUri for the given namespace (optional)
+ * 'namespaceUri' => $namespaceUri
+ * )
+ * </pre>
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * $tag = array(
+ * 'qname' => 'foo:bar',
+ * 'namespaceUri' => 'http://foo.com',
+ * 'attributes' => array('key' => 'value', 'argh' => 'fruit&vegetable'),
+ * 'content' => 'I\'m inside the tag',
+ * );
+ * // creating a tag with qualified name and namespaceUri
+ * $string = XML_Util::createTagFromArray($tag);
+ * </code>
+ *
+ * @param array $tag tag definition
+ * @param int $replaceEntities whether to replace XML special chars in
+ * content, embedd it in a CData section
+ * or none of both
+ * @param bool $multiline whether to create a multiline tag where each
+ * attribute gets written to a single line
+ * @param string $indent string used to indent attributes
+ * (_auto indents attributes so they start
+ * at the same column)
+ * @param string $linebreak string used for linebreaks
+ * @param bool $sortAttributes Whether to sort the attributes or not
+ *
+ * @return string XML tag
+ * @access public
+ * @static
+ * @see createTag()
+ * @uses attributesToString() to serialize the attributes of the tag
+ * @uses splitQualifiedName() to get local part and namespace of a qualified name
+ * @uses createCDataSection()
+ * @uses raiseError()
+ */
+ function createTagFromArray($tag, $replaceEntities = XML_UTIL_REPLACE_ENTITIES,
+ $multiline = false, $indent = '_auto', $linebreak = "\n",
+ $sortAttributes = true)
+ {
+ if (isset($tag['content']) && !is_scalar($tag['content'])) {
+ return XML_Util::raiseError('Supplied non-scalar value as tag content',
+ XML_UTIL_ERROR_NON_SCALAR_CONTENT);
+ }
+
+ if (!isset($tag['qname']) && !isset($tag['localPart'])) {
+ return XML_Util::raiseError('You must either supply a qualified name '
+ . '(qname) or local tag name (localPart).',
+ XML_UTIL_ERROR_NO_TAG_NAME);
+ }
+
+ // if no attributes hav been set, use empty attributes
+ if (!isset($tag['attributes']) || !is_array($tag['attributes'])) {
+ $tag['attributes'] = array();
+ }
+
+ if (isset($tag['namespaces'])) {
+ foreach ($tag['namespaces'] as $ns => $uri) {
+ $tag['attributes']['xmlns:' . $ns] = $uri;
+ }
+ }
+
+ if (!isset($tag['qname'])) {
+ // qualified name is not given
+
+ // check for namespace
+ if (isset($tag['namespace']) && !empty($tag['namespace'])) {
+ $tag['qname'] = $tag['namespace'] . ':' . $tag['localPart'];
+ } else {
+ $tag['qname'] = $tag['localPart'];
+ }
+ } elseif (isset($tag['namespaceUri']) && !isset($tag['namespace'])) {
+ // namespace URI is set, but no namespace
+
+ $parts = XML_Util::splitQualifiedName($tag['qname']);
+
+ $tag['localPart'] = $parts['localPart'];
+ if (isset($parts['namespace'])) {
+ $tag['namespace'] = $parts['namespace'];
+ }
+ }
+
+ if (isset($tag['namespaceUri']) && !empty($tag['namespaceUri'])) {
+ // is a namespace given
+ if (isset($tag['namespace']) && !empty($tag['namespace'])) {
+ $tag['attributes']['xmlns:' . $tag['namespace']] =
+ $tag['namespaceUri'];
+ } else {
+ // define this Uri as the default namespace
+ $tag['attributes']['xmlns'] = $tag['namespaceUri'];
+ }
+ }
+
+ // check for multiline attributes
+ if ($multiline === true) {
+ if ($indent === '_auto') {
+ $indent = str_repeat(' ', (strlen($tag['qname'])+2));
+ }
+ }
+
+ // create attribute list
+ $attList = XML_Util::attributesToString($tag['attributes'],
+ $sortAttributes, $multiline, $indent, $linebreak, $replaceEntities);
+ if (!isset($tag['content']) || (string)$tag['content'] == '') {
+ $tag = sprintf('<%s%s />', $tag['qname'], $attList);
+ } else {
+ switch ($replaceEntities) {
+ case XML_UTIL_ENTITIES_NONE:
+ break;
+ case XML_UTIL_CDATA_SECTION:
+ $tag['content'] = XML_Util::createCDataSection($tag['content']);
+ break;
+ default:
+ $tag['content'] = XML_Util::replaceEntities($tag['content'],
+ $replaceEntities);
+ break;
+ }
+ $tag = sprintf('<%s%s>%s</%s>', $tag['qname'], $attList, $tag['content'],
+ $tag['qname']);
+ }
+ return $tag;
+ }
+
+ /**
+ * create a start element
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // create an XML start element:
+ * $tag = XML_Util::createStartElement('myNs:myTag',
+ * array('foo' => 'bar') ,'http://www.w3c.org/myNs#');
+ * </code>
+ *
+ * @param string $qname qualified tagname (including namespace)
+ * @param array $attributes array containg attributes
+ * @param string $namespaceUri URI of the namespace
+ * @param bool $multiline whether to create a multiline tag where each
+ * attribute gets written to a single line
+ * @param string $indent string used to indent attributes (_auto indents
+ * attributes so they start at the same column)
+ * @param string $linebreak string used for linebreaks
+ * @param bool $sortAttributes Whether to sort the attributes or not
+ *
+ * @return string XML start element
+ * @access public
+ * @static
+ * @see createEndElement(), createTag()
+ */
+ function createStartElement($qname, $attributes = array(), $namespaceUri = null,
+ $multiline = false, $indent = '_auto', $linebreak = "\n",
+ $sortAttributes = true)
+ {
+ // if no attributes hav been set, use empty attributes
+ if (!isset($attributes) || !is_array($attributes)) {
+ $attributes = array();
+ }
+
+ if ($namespaceUri != null) {
+ $parts = XML_Util::splitQualifiedName($qname);
+ }
+
+ // check for multiline attributes
+ if ($multiline === true) {
+ if ($indent === '_auto') {
+ $indent = str_repeat(' ', (strlen($qname)+2));
+ }
+ }
+
+ if ($namespaceUri != null) {
+ // is a namespace given
+ if (isset($parts['namespace']) && !empty($parts['namespace'])) {
+ $attributes['xmlns:' . $parts['namespace']] = $namespaceUri;
+ } else {
+ // define this Uri as the default namespace
+ $attributes['xmlns'] = $namespaceUri;
+ }
+ }
+
+ // create attribute list
+ $attList = XML_Util::attributesToString($attributes, $sortAttributes,
+ $multiline, $indent, $linebreak);
+ $element = sprintf('<%s%s>', $qname, $attList);
+ return $element;
+ }
+
+ /**
+ * create an end element
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // create an XML start element:
+ * $tag = XML_Util::createEndElement('myNs:myTag');
+ * </code>
+ *
+ * @param string $qname qualified tagname (including namespace)
+ *
+ * @return string XML end element
+ * @access public
+ * @static
+ * @see createStartElement(), createTag()
+ */
+ function createEndElement($qname)
+ {
+ $element = sprintf('</%s>', $qname);
+ return $element;
+ }
+
+ /**
+ * create an XML comment
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // create an XML start element:
+ * $tag = XML_Util::createComment('I am a comment');
+ * </code>
+ *
+ * @param string $content content of the comment
+ *
+ * @return string XML comment
+ * @access public
+ * @static
+ */
+ function createComment($content)
+ {
+ $comment = sprintf('<!-- %s -->', $content);
+ return $comment;
+ }
+
+ /**
+ * create a CData section
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // create a CData section
+ * $tag = XML_Util::createCDataSection('I am content.');
+ * </code>
+ *
+ * @param string $data data of the CData section
+ *
+ * @return string CData section with content
+ * @access public
+ * @static
+ */
+ function createCDataSection($data)
+ {
+ return sprintf('<![CDATA[%s]]>',
+ preg_replace('/\]\]>/', ']]]]><![CDATA[>', strval($data)));
+
+ }
+
+ /**
+ * split qualified name and return namespace and local part
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // split qualified tag
+ * $parts = XML_Util::splitQualifiedName('xslt:stylesheet');
+ * </code>
+ * the returned array will contain two elements:
+ * <pre>
+ * array(
+ * 'namespace' => 'xslt',
+ * 'localPart' => 'stylesheet'
+ * );
+ * </pre>
+ *
+ * @param string $qname qualified tag name
+ * @param string $defaultNs default namespace (optional)
+ *
+ * @return array array containing namespace and local part
+ * @access public
+ * @static
+ */
+ function splitQualifiedName($qname, $defaultNs = null)
+ {
+ if (strstr($qname, ':')) {
+ $tmp = explode(':', $qname);
+ return array(
+ 'namespace' => $tmp[0],
+ 'localPart' => $tmp[1]
+ );
+ }
+ return array(
+ 'namespace' => $defaultNs,
+ 'localPart' => $qname
+ );
+ }
+
+ /**
+ * check, whether string is valid XML name
+ *
+ * <p>XML names are used for tagname, attribute names and various
+ * other, lesser known entities.</p>
+ * <p>An XML name may only consist of alphanumeric characters,
+ * dashes, undescores and periods, and has to start with a letter
+ * or an underscore.</p>
+ *
+ * <code>
+ * require_once 'XML/Util.php';
+ *
+ * // verify tag name
+ * $result = XML_Util::isValidName('invalidTag?');
+ * if (is_a($result, 'PEAR_Error')) {
+ * print 'Invalid XML name: ' . $result->getMessage();
+ * }
+ * </code>
+ *
+ * @param string $string string that should be checked
+ *
+ * @return mixed true, if string is a valid XML name, PEAR error otherwise
+ * @access public
+ * @static
+ * @todo support for other charsets
+ * @todo PEAR CS - unable to avoid 85-char limit on second preg_match
+ */
+ function isValidName($string)
+ {
+ // check for invalid chars
+ if (!preg_match('/^[[:alpha:]_]$/', $string{0})) {
+ return XML_Util::raiseError('XML names may only start with letter '
+ . 'or underscore', XML_UTIL_ERROR_INVALID_START);
+ }
+
+ // check for invalid chars
+ if (!preg_match('/^([[:alpha:]_]([[:alnum:]\-\.]*)?:)?[[:alpha:]_]([[:alnum:]\_\-\.]+)?$/',
+ $string)
+ ) {
+ return XML_Util::raiseError('XML names may only contain alphanumeric '
+ . 'chars, period, hyphen, colon and underscores',
+ XML_UTIL_ERROR_INVALID_CHARS);
+ }
+ // XML name is valid
+ return true;
+ }
+
+ /**
+ * replacement for XML_Util::raiseError
+ *
+ * Avoids the necessity to always require
+ * PEAR.php
+ *
+ * @param string $msg error message
+ * @param int $code error code
+ *
+ * @return PEAR_Error
+ * @access public
+ * @static
+ * @todo PEAR CS - should this use include_once instead?
+ */
+ function raiseError($msg, $code)
+ {
+ require_once 'PEAR.php';
+ return PEAR::raiseError($msg, $code);
+ }
+}
+?>