From 97c69349e5c3cbfdf6aee65e7ec02b2a5faee8f1 Mon Sep 17 00:00:00 2001 From: Sudaraka Wijesinghe Date: Mon, 13 Aug 2012 03:51:49 +0530 Subject: [PATCH] Creating repository for dokuwiki modifications for sudaraka.org --- .gitignore | 4 + .htaccess | 43 + COPYING | 339 + README | 10 + VERSION | 1 + bin/.htaccess | 2 + bin/dwpage.php | 378 + bin/indexer.php | 158 + bin/render.php | 67 + bin/striplangs.php | 148 + bin/wantedpages.php | 134 + conf/.htaccess | 4 + conf/acl.auth.php.dist | 21 + conf/acronyms.conf | 157 + conf/dokuwiki.php | 165 + conf/entities.conf | 22 + conf/interwiki.conf | 134 + conf/license.php | 36 + conf/local.php.dist | 16 + conf/mediameta.php | 91 + conf/mime.conf | 64 + conf/mysql.conf.php.example | 253 + conf/plugins.local.php | 8 + conf/plugins.required.php | 11 + conf/scheme.conf | 11 + conf/smileys.conf | 28 + conf/users.auth.php.dist | 10 + conf/wordblock.conf | 32 + doku.php | 93 + inc/.htaccess | 4 + inc/DifferenceEngine.php | 1163 +++ inc/EmailAddressValidator.php | 191 + inc/FeedParser.php | 76 + inc/HTTPClient.php | 686 ++ inc/IXR_Library.php | 816 +++ inc/JSON.php | 661 ++ inc/JpegMeta.php | 3008 ++++++++ inc/PassHash.class.php | 382 + inc/SafeFN.class.php | 157 + inc/SimplePie.php | 15299 +++++++++++++++++++++++++++++++++++++++ inc/Sitemapper.php | 203 + inc/TarLib.class.php | 795 ++ inc/ZipLib.class.php | 504 ++ inc/actions.php | 762 ++ inc/adLDAP.php | 2422 +++++++ inc/auth.php | 1024 +++ inc/blowfish.php | 514 ++ inc/cache.php | 270 + inc/changelog.php | 539 ++ inc/cliopts.php | 504 ++ inc/common.php | 1573 ++++ inc/config_cascade.php | 78 + inc/confutils.php | 334 + inc/events.php | 197 + inc/feedcreator.class.php | 1580 ++++ inc/form.php | 950 +++ inc/fulltext.php | 753 ++ inc/geshi.php | 4755 ++++++++++++ inc/geshi/asm.php | 225 + inc/geshi/bash.php | 440 ++ inc/geshi/ini.php | 128 + inc/geshi/pascal.php | 152 + inc/geshi/php.php | 1114 +++ inc/geshi/python.php | 244 + inc/html.php | 1867 +++++ inc/httputils.php | 251 + inc/indexer.php | 1395 ++++ inc/infoutils.php | 423 ++ inc/init.php | 561 ++ inc/io.php | 610 ++ inc/lang/.htaccess | 4 + inc/lang/en/lang.php | 344 + inc/load.php | 102 + inc/mail.php | 318 + inc/media.php | 2017 ++++++ inc/pageutils.php | 622 ++ inc/parser/code.php | 58 + inc/parser/handler.php | 1623 +++++ inc/parser/lexer.php | 600 ++ inc/parser/metadata.php | 485 ++ inc/parser/parser.php | 959 +++ inc/parser/renderer.php | 322 + inc/parser/xhtml.php | 1231 ++++ inc/parser/xhtmlsummary.php | 90 + inc/parserutils.php | 781 ++ inc/plugin.php | 259 + inc/plugincontroller.class.php | 286 + inc/pluginutils.php | 42 + inc/search.php | 610 ++ inc/subscription.php | 394 + inc/template.php | 1544 ++++ inc/toolbar.php | 255 + inc/utf8.php | 1624 +++++ index.php | 8 + lib/exe/fetch.php | 205 + lib/index.html | 12 + lib/tpl/index.php | 68 + 97 files changed, 64913 insertions(+) create mode 100644 .gitignore create mode 100644 .htaccess create mode 100644 COPYING create mode 100644 README create mode 100644 VERSION create mode 100644 bin/.htaccess create mode 100755 bin/dwpage.php create mode 100755 bin/indexer.php create mode 100755 bin/render.php create mode 100644 bin/striplangs.php create mode 100755 bin/wantedpages.php create mode 100644 conf/.htaccess create mode 100644 conf/acl.auth.php.dist create mode 100644 conf/acronyms.conf create mode 100644 conf/dokuwiki.php create mode 100644 conf/entities.conf create mode 100644 conf/interwiki.conf create mode 100644 conf/license.php create mode 100644 conf/local.php.dist create mode 100644 conf/mediameta.php create mode 100644 conf/mime.conf create mode 100644 conf/mysql.conf.php.example create mode 100644 conf/plugins.local.php create mode 100644 conf/plugins.required.php create mode 100644 conf/scheme.conf create mode 100644 conf/smileys.conf create mode 100644 conf/users.auth.php.dist create mode 100644 conf/wordblock.conf create mode 100644 doku.php create mode 100644 inc/.htaccess create mode 100644 inc/DifferenceEngine.php create mode 100644 inc/EmailAddressValidator.php create mode 100644 inc/FeedParser.php create mode 100644 inc/HTTPClient.php create mode 100644 inc/IXR_Library.php create mode 100644 inc/JSON.php create mode 100644 inc/JpegMeta.php create mode 100644 inc/PassHash.class.php create mode 100644 inc/SafeFN.class.php create mode 100644 inc/SimplePie.php create mode 100644 inc/Sitemapper.php create mode 100644 inc/TarLib.class.php create mode 100644 inc/ZipLib.class.php create mode 100644 inc/actions.php create mode 100644 inc/adLDAP.php create mode 100644 inc/auth.php create mode 100644 inc/blowfish.php create mode 100644 inc/cache.php create mode 100644 inc/changelog.php create mode 100644 inc/cliopts.php create mode 100644 inc/common.php create mode 100644 inc/config_cascade.php create mode 100644 inc/confutils.php create mode 100644 inc/events.php create mode 100644 inc/feedcreator.class.php create mode 100644 inc/form.php create mode 100644 inc/fulltext.php create mode 100644 inc/geshi.php create mode 100644 inc/geshi/asm.php create mode 100644 inc/geshi/bash.php create mode 100644 inc/geshi/ini.php create mode 100644 inc/geshi/pascal.php create mode 100644 inc/geshi/php.php create mode 100644 inc/geshi/python.php create mode 100644 inc/html.php create mode 100644 inc/httputils.php create mode 100644 inc/indexer.php create mode 100644 inc/infoutils.php create mode 100644 inc/init.php create mode 100644 inc/io.php create mode 100644 inc/lang/.htaccess create mode 100644 inc/lang/en/lang.php create mode 100644 inc/load.php create mode 100644 inc/mail.php create mode 100644 inc/media.php create mode 100644 inc/pageutils.php create mode 100644 inc/parser/code.php create mode 100644 inc/parser/handler.php create mode 100644 inc/parser/lexer.php create mode 100644 inc/parser/metadata.php create mode 100644 inc/parser/parser.php create mode 100644 inc/parser/renderer.php create mode 100644 inc/parser/xhtml.php create mode 100644 inc/parser/xhtmlsummary.php create mode 100644 inc/parserutils.php create mode 100644 inc/plugin.php create mode 100644 inc/plugincontroller.class.php create mode 100644 inc/pluginutils.php create mode 100644 inc/search.php create mode 100644 inc/subscription.php create mode 100644 inc/template.php create mode 100644 inc/toolbar.php create mode 100644 inc/utf8.php create mode 100644 index.php create mode 100644 lib/exe/fetch.php create mode 100644 lib/index.html create mode 100644 lib/tpl/index.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0df62b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +data +conf/local.php +lib/tpl/* +!lib/tpl/index.php diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..06d1551 --- /dev/null +++ b/.htaccess @@ -0,0 +1,43 @@ +## Enable this to restrict editing to logged in users only + +## You should disable Indexes and MultiViews either here or in the +## global config. Symlinks maybe needed for URL rewriting. +#Options -Indexes -MultiViews +FollowSymLinks + +## make sure nobody gets the htaccess, README, COPYING or VERSION files + + Order allow,deny + Deny from all + Satisfy All + + +## Uncomment these rules if you want to have nice URLs using +## $conf['userewrite'] = 1 - not needed for rewrite mode 2 +RewriteEngine on +# +## Not all installations will require the following line. If you do, +## change "/dokuwiki" to the path to your dokuwiki directory relative +## to your document root. +#RewriteBase /dokuwiki +# +## If you enable DokuWikis XML-RPC interface, you should consider to +## restrict access to it over HTTPS only! Uncomment the following two +## rules if your server setup allows HTTPS. +#RewriteCond %{HTTPS} !=on +#RewriteRule ^lib/exe/xmlrpc.php$ https://%{SERVER_NAME}%{REQUEST_URI} [L,R=301] +## + +RewriteRule ^category/(.*)$ /$1 [R=302,L] +RewriteRule ^n2s/?$ /note-to-self [R=302,L] +RewriteRule ^n2s/(.*)$ /note-to-self/$1 [R=302,L] +RewriteRule ^(.*/)?snippets/(.*)$ /code-snippets/$2 [R=302,L] + +RewriteRule ^_media/(.*) lib/exe/fetch.php?media=$1 [QSA,L] +RewriteRule ^_detail/(.*) lib/exe/detail.php?media=$1 [QSA,L] +RewriteRule ^_export/([^/]+)/(.*) doku.php?do=export_$1&id=$2 [QSA,L] +RewriteRule (.+)\/$ /$1 [R,QSA,L] +RewriteRule ^$ doku.php [L] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule (.*) doku.php?id=$1 [QSA,L] +RewriteRule ^index.php$ doku.php diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README b/README new file mode 100644 index 0000000..0490040 --- /dev/null +++ b/README @@ -0,0 +1,10 @@ +All documentation for DokuWiki is available online +at http://www.dokuwiki.org/ + +For Installation Instructions see +http://www.dokuwiki.org/install + +DokuWiki - 2004-2012 (c) Andreas Gohr + and the DokuWiki Community +See COPYING and file headers for license info + diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..140eff5 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +2012-01-25b "Angua" diff --git a/bin/.htaccess b/bin/.htaccess new file mode 100644 index 0000000..9c96d37 --- /dev/null +++ b/bin/.htaccess @@ -0,0 +1,2 @@ +order allow,deny +deny from all diff --git a/bin/dwpage.php b/bin/dwpage.php new file mode 100755 index 0000000..211bc5a --- /dev/null +++ b/bin/dwpage.php @@ -0,0 +1,378 @@ +#!/usr/bin/php + [working_file] + + Checks out a file from the repository, using the wiki id and obtaining + a lock for the page. + If a working_file is specified, this is where the page is copied to. + Otherwise defaults to the same as the wiki page in the current + working directory. + + EXAMPLE + $ ./dwpage.php checkout wiki:syntax ./new_syntax.txt + + OPTIONS + -h, --help=: get help + -f: force obtaining a lock for the page (generally bad idea) +"; + break; + case 'commit': + print "Usage: dwpage.php [opts] -m \"Msg\" commit + + Checks in the working_file into the repository using the specified + wiki id, archiving the previous version. + + EXAMPLE + $ ./dwpage.php -m \"Some message\" commit ./new_syntax.txt wiki:syntax + + OPTIONS + -h, --help=: get help + -f: force obtaining a lock for the page (generally bad idea) + -t, trivial: minor change + -m (required): Summary message describing the change +"; + break; + case 'lock': + print "Usage: dwpage.php [opts] lock + + Obtains or updates a lock for a wiki page + + EXAMPLE + $ ./dwpage.php lock wiki:syntax + + OPTIONS + -h, --help=: get help + -f: force obtaining a lock for the page (generally bad idea) +"; + break; + case 'unlock': + print "Usage: dwpage.php [opts] unlock + + Removes a lock for a wiki page. + + EXAMPLE + $ ./dwpage.php unlock wiki:syntax + + OPTIONS + -h, --help=: get help + -f: force obtaining a lock for the page (generally bad idea) +"; + break; + default: + print "Usage: dwpage.php [opts] + + Utility to help command line Dokuwiki page editing, allow + pages to be checked out for editing then committed after changes + + Normal operation would be; + + + + ACTIONS + checkout: see $ dwpage.php --help=checkout + commit: see $ dwpage.php --help=commit + lock: see $ dwpage.php --help=lock + + OPTIONS + -h, --help=: get help + e.g. $ ./dwpage.php -hcommit + e.g. $ ./dwpage.php --help=commit +"; + break; + } +} + +#------------------------------------------------------------------------------ +function getUser() { + $user = getenv('USER'); + if (empty ($user)) { + $user = getenv('USERNAME'); + } else { + return $user; + } + if (empty ($user)) { + $user = 'admin'; + } + return $user; +} + +#------------------------------------------------------------------------------ +function getSuppliedArgument($OPTS, $short, $long) { + $arg = $OPTS->get($short); + if ( is_null($arg) ) { + $arg = $OPTS->get($long); + } + return $arg; +} + +#------------------------------------------------------------------------------ +function obtainLock($WIKI_ID) { + + global $USERNAME; + + if ( !file_exists(wikiFN($WIKI_ID)) ) { + fwrite( STDERR, "$WIKI_ID does not yet exist\n"); + } + + $_SERVER['REMOTE_USER'] = $USERNAME; + if ( checklock($WIKI_ID) ) { + fwrite( STDERR, "Page $WIKI_ID is already locked by another user\n"); + exit(1); + } + + lock($WIKI_ID); + + $_SERVER['REMOTE_USER'] = '_'.$USERNAME.'_'; + + if ( checklock($WIKI_ID) != $USERNAME ) { + + fwrite( STDERR, "Unable to obtain lock for $WIKI_ID\n" ); + exit(1); + + } +} + +#------------------------------------------------------------------------------ +function clearLock($WIKI_ID) { + + global $USERNAME ; + + if ( !file_exists(wikiFN($WIKI_ID)) ) { + fwrite( STDERR, "$WIKI_ID does not yet exist\n"); + } + + $_SERVER['REMOTE_USER'] = $USERNAME; + if ( checklock($WIKI_ID) ) { + fwrite( STDERR, "Page $WIKI_ID is locked by another user\n"); + exit(1); + } + + unlock($WIKI_ID); + + if ( file_exists(wikiLockFN($WIKI_ID)) ) { + fwrite( STDERR, "Unable to clear lock for $WIKI_ID\n" ); + exit(1); + } + +} + +#------------------------------------------------------------------------------ +function deleteLock($WIKI_ID) { + + $wikiLockFN = wikiLockFN($WIKI_ID); + + if ( file_exists($wikiLockFN) ) { + if ( !unlink($wikiLockFN) ) { + fwrite( STDERR, "Unable to delete $wikiLockFN\n" ); + exit(1); + } + } + +} + +#------------------------------------------------------------------------------ +$USERNAME = getUser(); +$CWD = getcwd(); +$SYSTEM_ID = '127.0.0.1'; + +#------------------------------------------------------------------------------ +$OPTS = Doku_Cli_Opts::getOptions( + __FILE__, + 'h::fm:u:s:t', + array( + 'help==', + 'user=', + 'system=', + 'trivial', + ) +); + +if ( $OPTS->isError() ) { + print $OPTS->getMessage()."\n"; + exit(1); +} + +if ( $OPTS->has('h') or $OPTS->has('help') or !$OPTS->hasArgs() ) { + usage(getSuppliedArgument($OPTS,'h','help')); + exit(0); +} + +if ( $OPTS->has('u') or $OPTS->has('user') ) { + $USERNAME = getSuppliedArgument($OPTS,'u','user'); +} + +if ( $OPTS->has('s') or $OPTS->has('system') ) { + $SYSTEM_ID = getSuppliedArgument($OPTS,'s','system'); +} + +#------------------------------------------------------------------------------ +switch ( $OPTS->arg(0) ) { + + #---------------------------------------------------------------------- + case 'checkout': + + $WIKI_ID = $OPTS->arg(1); + + if ( !$WIKI_ID ) { + fwrite( STDERR, "Wiki page ID required\n"); + exit(1); + } + + $WIKI_FN = wikiFN($WIKI_ID); + + if ( !file_exists($WIKI_FN) ) { + fwrite( STDERR, "$WIKI_ID does not yet exist\n"); + exit(1); + } + + $TARGET_FN = $OPTS->arg(2); + + if ( empty($TARGET_FN) ) { + $TARGET_FN = getcwd().'/'.basename($WIKI_FN); + } + + if ( !file_exists(dirname($TARGET_FN)) ) { + fwrite( STDERR, "Directory ".dirname($TARGET_FN)." does not exist\n"); + exit(1); + } + + if ( stristr( realpath(dirname($TARGET_FN)), realpath($conf['datadir']) ) !== false ) { + fwrite( STDERR, "Attempt to check out file into data directory - not allowed\n"); + exit(1); + } + + if ( $OPTS->has('f') ) { + deleteLock($WIKI_ID); + } + + obtainLock($WIKI_ID); + + # Need to lock the file first? + if ( !copy($WIKI_FN, $TARGET_FN) ) { + fwrite( STDERR, "Unable to copy $WIKI_FN to $TARGET_FN\n"); + clearLock($WIKI_ID); + exit(1); + } + + print "$WIKI_ID > $TARGET_FN\n"; + exit(0); + + break; + + #---------------------------------------------------------------------- + case 'commit': + + $TARGET_FN = $OPTS->arg(1); + + if ( !$TARGET_FN ) { + fwrite( STDERR, "Target filename required\n"); + exit(1); + } + + if ( !file_exists($TARGET_FN) ) { + fwrite( STDERR, "$TARGET_FN does not exist\n"); + exit(1); + } + + if ( !is_readable($TARGET_FN) ) { + fwrite( STDERR, "Cannot read from $TARGET_FN\n"); + exit(1); + } + + $WIKI_ID = $OPTS->arg(2); + + if ( !$WIKI_ID ) { + fwrite( STDERR, "Wiki page ID required\n"); + exit(1); + } + + if ( !$OPTS->has('m') ) { + fwrite( STDERR, "Summary message required\n"); + exit(1); + } + + if ( $OPTS->has('f') ) { + deleteLock($WIKI_ID); + } + + $_SERVER['REMOTE_USER'] = $USERNAME; + if ( checklock($WIKI_ID) ) { + fwrite( STDERR, "$WIKI_ID is locked by another user\n"); + exit(1); + } + + obtainLock($WIKI_ID); + + saveWikiText($WIKI_ID, file_get_contents($TARGET_FN), $OPTS->get('m'), $OPTS->has('t')); + + clearLock($WIKI_ID); + + exit(0); + + break; + + #---------------------------------------------------------------------- + case 'lock': + + $WIKI_ID = $OPTS->arg(1); + + if ( !$WIKI_ID ) { + fwrite( STDERR, "Wiki page ID required\n"); + exit(1); + } + + if ( $OPTS->has('f') ) { + deleteLock($WIKI_ID); + } + + obtainLock($WIKI_ID); + + print "Locked : $WIKI_ID\n"; + exit(0); + + break; + + #---------------------------------------------------------------------- + case 'unlock': + + $WIKI_ID = $OPTS->arg(1); + + if ( !$WIKI_ID ) { + fwrite( STDERR, "Wiki page ID required\n"); + exit(1); + } + + if ( $OPTS->has('f') ) { + deleteLock($WIKI_ID); + } else { + clearLock($WIKI_ID); + } + + print "Unlocked : $WIKI_ID\n"; + exit(0); + + break; + + #---------------------------------------------------------------------- + default: + + fwrite( STDERR, "Invalid action ".$OPTS->arg(0)."\n" ); + exit(1); + + break; + +} + diff --git a/bin/indexer.php b/bin/indexer.php new file mode 100755 index 0000000..f6aeb4f --- /dev/null +++ b/bin/indexer.php @@ -0,0 +1,158 @@ +#!/usr/bin/php +isError() ) { + fwrite( STDERR, $OPTS->getMessage() . "\n"); + _usage(); + exit(1); +} +$CLEAR = false; +$QUIET = false; +$INDEXER = null; +foreach ($OPTS->options as $key => $val) { + switch ($key) { + case 'h': + case 'help': + _usage(); + exit; + case 'c': + case 'clear': + $CLEAR = true; + break; + case 'q': + case 'quiet': + $QUIET = true; + break; + } +} + +#------------------------------------------------------------------------------ +# Action + +if($CLEAR) _clearindex(); +_update(); + + + +#------------------------------------------------------------------------------ + +function _usage() { + print "Usage: indexer.php + + Updates the searchindex by indexing all new or changed pages + when the -c option is given the index is cleared first. + + OPTIONS + -h, --help show this help and exit + -c, --clear clear the index before updating + -q, --quiet don't produce any output +"; +} + +function _update(){ + global $conf; + global $INDEXER; + + $INDEXER = idx_get_indexer(); + + $data = array(); + _quietecho("Searching pages... "); + search($data,$conf['datadir'],'search_allpages',array('skipacl' => true)); + _quietecho(count($data)." pages found.\n"); + + foreach($data as $val){ + _index($val['id']); + } +} + +function _index($id){ + global $INDEXER; + global $CLEAR; + global $QUIET; + + _quietecho("$id... "); + idx_addPage($id, !$QUIET, $CLEAR); + _quietecho("done.\n"); +} + +/** + * lock the indexer system + */ +function _lock(){ + global $conf; + $lock = $conf['lockdir'].'/_indexer.lock'; + $said = false; + while(!@mkdir($lock, $conf['dmode'])){ + if(time()-@filemtime($lock) > 60*5){ + // looks like a stale lock - remove it + @rmdir($lock); + }else{ + if($said){ + _quietecho("."); + }else{ + _quietecho("Waiting for lockfile (max. 5 min)"); + $said = true; + } + sleep(15); + } + } + if($conf['dperm']) chmod($lock, $conf['dperm']); + if($said) _quietecho("\n"); +} + +/** + * unlock the indexer sytem + */ +function _unlock(){ + global $conf; + $lock = $conf['lockdir'].'/_indexer.lock'; + @rmdir($lock); +} + +/** + * Clear all index files + */ +function _clearindex(){ + global $conf; + _lock(); + _quietecho("Clearing index... "); + io_saveFile($conf['indexdir'].'/page.idx',''); + io_saveFile($conf['indexdir'].'/title.idx',''); + io_saveFile($conf['indexdir'].'/pageword.idx',''); + io_saveFile($conf['indexdir'].'/metadata.idx',''); + $dir = @opendir($conf['indexdir']); + if($dir!==false){ + while(($f = readdir($dir)) !== false){ + if(substr($f,-4)=='.idx' && + (substr($f,0,1)=='i' || substr($f,0,1)=='w' + || substr($f,-6)=='_w.idx' || substr($f,-6)=='_i.idx' || substr($f,-6)=='_p.idx')) + @unlink($conf['indexdir']."/$f"); + } + } + @unlink($conf['indexdir'].'/lengths.idx'); + _quietecho("done.\n"); + _unlock(); +} + +function _quietecho($msg) { + global $QUIET; + if(!$QUIET) echo $msg; +} + +//Setup VIM: ex: et ts=2 : diff --git a/bin/render.php b/bin/render.php new file mode 100755 index 0000000..d30ef29 --- /dev/null +++ b/bin/render.php @@ -0,0 +1,67 @@ +#!/usr/bin/php + + */ +if ('cli' != php_sapi_name()) die(); + +ini_set('memory_limit','128M'); +if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/'); +define('NOSESSION',1); +require_once(DOKU_INC.'inc/init.php'); +require_once(DOKU_INC.'inc/common.php'); +require_once(DOKU_INC.'inc/parserutils.php'); +require_once(DOKU_INC.'inc/cliopts.php'); + +// handle options +$short_opts = 'hr:'; +$long_opts = array('help','renderer:'); +$OPTS = Doku_Cli_Opts::getOptions(__FILE__,$short_opts,$long_opts); +if ( $OPTS->isError() ) { + fwrite( STDERR, $OPTS->getMessage() . "\n"); + _usage(); + exit(1); +} +$RENDERER = 'xhtml'; +foreach ($OPTS->options as $key => $val) { + switch ($key) { + case 'h': + case 'help': + _usage(); + exit; + case 'r': + case 'renderer': + $RENDERER = $val; + } +} + + +// do the action +$source = stream_get_contents(STDIN); +$info = array(); +$result = p_render($RENDERER,p_get_instructions($source),$info); +if(is_null($result)) die("No such renderer $RENDERER\n"); +echo $result; + +/** + * Print usage info + */ +function _usage(){ + print "Usage: render.php + + Reads DokuWiki syntax from STDIN and renders it with the given renderer + to STDOUT + + OPTIONS + -h, --help show this help and exit + -r, --renderer the render mode (default: xhtml) +"; +} diff --git a/bin/striplangs.php b/bin/striplangs.php new file mode 100644 index 0000000..40cef50 --- /dev/null +++ b/bin/striplangs.php @@ -0,0 +1,148 @@ +#!/usr/bin/php + + */ +if ('cli' != php_sapi_name()) die(); + +#------------------------------------------------------------------------------ +if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../').'/'); +require_once DOKU_INC.'inc/cliopts.php'; + +#------------------------------------------------------------------------------ +function usage($show_examples = false) { + print "Usage: striplangs.php [-h [-x]] [-e] [-k lang1[,lang2]..[,langN]] + + Removes all languages from the instalation, besides the ones + after the -k option. English language is never removed! + + OPTIONS + -h, --help get this help + -x, --examples get also usage examples + -k, --keep comma separated list of languages, -e is always implied + -e, --english keeps english, dummy to use without -k\n"; + if ( $show_examples ) { + print "\n + EXAMPLES + Strips all languages, but keeps 'en' and 'de': + striplangs -k de + + Strips all but 'en','ca-valencia','cs','de','is','sk': + striplangs --keep ca-valencia,cs,de,is,sk + + Strips all but 'en': + striplangs -e + + No option specified, prints usage and throws error: + striplangs\n"; + } +} + +function getSuppliedArgument($OPTS, $short, $long) { + $arg = $OPTS->get($short); + if ( is_null($arg) ) { + $arg = $OPTS->get($long); + } + return $arg; +} + +function processPlugins($path, $keep_langs) { + if (is_dir($path)) { + $entries = scandir($path); + + foreach ($entries as $entry) { + if ($entry != "." && $entry != "..") { + if ( is_dir($path.'/'.$entry) ) { + + $plugin_langs = $path.'/'.$entry.'/lang'; + + if ( is_dir( $plugin_langs ) ) { + stripDirLangs($plugin_langs, $keep_langs); + } + } + } + } + } +} + +function stripDirLangs($path, $keep_langs) { + $dir = dir($path); + + while(($cur_dir = $dir->read()) !== false) { + if( $cur_dir != '.' and $cur_dir != '..' and is_dir($path.'/'.$cur_dir)) { + + if ( !in_array($cur_dir, $keep_langs, true ) ) { + killDir($path.'/'.$cur_dir); + } + } + } + $dir->close(); +} + +function killDir($dir) { + if (is_dir($dir)) { + $entries = scandir($dir); + + foreach ($entries as $entry) { + if ($entry != "." && $entry != "..") { + if ( is_dir($dir.'/'.$entry) ) { + killDir($dir.'/'.$entry); + } else { + unlink($dir.'/'.$entry); + } + } + } + reset($entries); + rmdir($dir); + } +} +#------------------------------------------------------------------------------ + +// handle options +$short_opts = 'hxk:e'; +$long_opts = array('help', 'examples', 'keep=','english'); + +$OPTS = Doku_Cli_Opts::getOptions(__FILE__, $short_opts, $long_opts); + +if ( $OPTS->isError() ) { + fwrite( STDERR, $OPTS->getMessage() . "\n"); + exit(1); +} + +// handle '--examples' option +$show_examples = ( $OPTS->has('x') or $OPTS->has('examples') ) ? true : false; + +// handle '--help' option +if ( $OPTS->has('h') or $OPTS->has('help') ) { + usage($show_examples); + exit(0); +} + +// handle both '--keep' and '--english' options +if ( $OPTS->has('k') or $OPTS->has('keep') ) { + $preserved_langs = getSuppliedArgument($OPTS,'k','keep'); + $langs = explode(',', $preserved_langs); + + // ! always enforce 'en' lang when using '--keep' (DW relies on it) + if ( !isset($langs['en']) ) { + $langs[]='en'; + } +} elseif ( $OPTS->has('e') or $OPTS->has('english') ) { + // '--english' was specified strip everything besides 'en' + $langs = array ('en'); +} else { + // no option was specified, print usage but don't do anything as + // this run might not be intented + usage(); + print "\n + ERROR + No option specified, use either -h -x to get more info, + or -e to strip every language besides english.\n"; + exit(1); +} + +// Kill all language directories in /inc/lang and /lib/plugins besides those in $langs array +stripDirLangs(realpath(dirname(__FILE__).'/../inc/lang'), $langs); +processPlugins(realpath(dirname(__FILE__).'/../lib/plugins'), $langs); diff --git a/bin/wantedpages.php b/bin/wantedpages.php new file mode 100755 index 0000000..30171fc --- /dev/null +++ b/bin/wantedpages.php @@ -0,0 +1,134 @@ +#!/usr/bin/php + pathID(substr($dir.'/'.$entry,$trunclen)), + 'file'=> $dir.'/'.$entry, + ); + $pages[] = $page; + } + } + closedir($dh); + return $pages; +} + +#------------------------------------------------------------------------------ +function dw_internal_links($page) { + global $conf; + $instructions = p_get_instructions(file_get_contents($page['file'])); + $links = array(); + $cns = getNS($page['id']); + $exists = false; + foreach($instructions as $ins){ + if($ins[0] == 'internallink' || ($conf['camelcase'] && $ins[0] == 'camelcaselink') ){ + $mid = $ins[1][0]; + resolve_pageid($cns,$mid,$exists); + if ( !$exists ) { + list($mid) = explode('#',$mid); //record pages without hashs + $links[] = $mid; + } + } + } + return $links; +} + +#------------------------------------------------------------------------------ +$OPTS = Doku_Cli_Opts::getOptions(__FILE__,'h',array('help')); + +if ( $OPTS->isError() ) { + fwrite( STDERR, $OPTS->getMessage() . "\n"); + exit(1); +} + +if ( $OPTS->has('h') or $OPTS->has('help') ) { + usage(); + exit(0); +} + +$START_DIR = $conf['datadir']; + +if ( $OPTS->numArgs() == 1 ) { + $START_DIR .= '/' . $OPTS->arg(0); +} + +#------------------------------------------------------------------------------ +$WANTED_PAGES = array(); + +foreach ( dw_get_pages($START_DIR) as $WIKI_PAGE ) { + $WANTED_PAGES = array_merge($WANTED_PAGES,dw_internal_links($WIKI_PAGE)); +} +$WANTED_PAGES = array_unique($WANTED_PAGES); +sort($WANTED_PAGES); + +foreach ( $WANTED_PAGES as $WANTED_PAGE ) { + print $WANTED_PAGE."\n"; +} +exit(0); diff --git a/conf/.htaccess b/conf/.htaccess new file mode 100644 index 0000000..f5dda60 --- /dev/null +++ b/conf/.htaccess @@ -0,0 +1,4 @@ +## no access to the conf directory +order allow,deny +deny from all +Satisfy All diff --git a/conf/acl.auth.php.dist b/conf/acl.auth.php.dist new file mode 100644 index 0000000..14344d7 --- /dev/null +++ b/conf/acl.auth.php.dist @@ -0,0 +1,21 @@ +# acl.auth.php +# +# Don't modify the lines above +# +# Access Control Lists +# +# Editing this file by hand shouldn't be necessary. Use the ACL +# Manager interface instead. +# +# If your auth backend allows special char like spaces in groups +# or user names you need to urlencode them (only chars <128, leave +# UTF-8 multibyte chars as is) +# +# none 0 +# read 1 +# edit 2 +# create 4 +# upload 8 +# delete 16 + +* @ALL 8 diff --git a/conf/acronyms.conf b/conf/acronyms.conf new file mode 100644 index 0000000..0ff1a3b --- /dev/null +++ b/conf/acronyms.conf @@ -0,0 +1,157 @@ +# Acronyms. + +A/L Advanced Level +ACL Access Control List +AFAICS As far as I can see +AFAIK As far as I know +AFAIR As far as I remember +AJAX Asynchronous JavaScript and XML +AIM AOL (America Online) Instant Messenger +AOL America Online +API Application Programming Interface +ASAP As soon as possible +ASCII American Standard Code for Information Interchange +ASP Active Server Pages +BTW By the way +CGI Common Gateway Interface +CMS Content Management System +CSR Certificate Sign Request +CSS Cascading Style Sheets +CVS Concurrent Versions System +DBA Database Administrator +DHCP Dynamic Host Configuration Protocol +DHTML Dynamic HyperText Markup Language +DMCA Digital Millenium Copyright Act +DNS Domain Name System +DOM Document Object Model +DTD Document Type Definition +EOF End of file +EOL End of line +EOM End of message +EOT End of text +ESMTP Extended Simple Mail Transfer Protocol +FAQ Frequently Asked Questions +FDL GNU Free Documentation License +FTP File Transfer Protocol +FOSS Free & Open-Source Software +FLOSS Free/Libre and Open Source Software +FUD Fear, Uncertainty, and Doubt +GB Gigabyte +GCE General Certificate of Education +GHz Gigahertz +GIF Graphics Interchange Format +GNU GNU is Not Unix +GPL GNU General Public License +GUI Graphical User Interface +HTML HyperText Markup Language +HTTP Hyper Text Transfer Protocol +IANAL I am not a lawyer (but) +ICANN Internet Corporation for Assigned Names and Numbers +ICQ I seek you (Instant Messenger) +IE5 Internet Explorer 5 +IE6 Internet Explorer 6 +IE Internet Explorer +IIRC If I remember correctly +IIS Internet Information Services +IMAP Internet Message Access Protocol +IMHO In my humble opinion +IMO In my opinion +IOW In other words +IRC Internet Relay Chat +IRL In real life +ISO International Organization for Standardization +ISP Internet Service Provider +JDK Java Development Kit +JPEG Joint Photographics Experts Group +JPG Joint Photographics Experts Group +JS JavaScript +KISS Keep it simple stupid +LAN Local Area Network +LDAP Lightweight Directory Access Protocol +LGPL GNU Lesser General Public License +LOL Laughing out loud +MathML Mathematical Markup Language +MB Megabyte +MHz Megahertz +MIME Multipurpose Internet Mail Extension +MIT Massachusetts Institute of Technology +MML Mathematical Markup Language +MP3 Moving Picture Experts Group Layer 3 +MPEG Moving Picture Experts Group +MSDN Microsoft Developer Network +MS Microsoft +MSIE Microsoft Internet Explorer +NIBM National Institute of Business Management +NIS Network Information Service +NS4.7 Netscape 4.7 +NS4 Netscape 4 +NS6 Netscape 6 +NS7 Netscape 7 +O/L Ordinary Level +OMG Oh my God +OPML Outline Processor Markup Language +OS Operating System +OSD On Screen Display +OSS Open Source Software +OTOH On the other hand +P2P Peer to Peer +PDA Personal Digital Assistant +PDF Portable Document Format +Perl Practical Extraction and Report Language +PERL Practical Extraction and Report Language +PHP PHP: Hypertext Preprocessor +PICS Platform for Internet Content Selection +PIN Personal Identification Number +PITA Pain in the Ass +PNG Portable Network Graphics +POP3 Post Office Protocol 3 +POP Post Office Protocol +QoS Quality of Service +RAID Redundant Array of Inexpensive Disks +RDF Resource Description Framework +RFC Request for Comments +ROTFL Rolling on the floor laughing +RPC Remote Procedure Call +RSS Rich Site Summary +RTFM Read The Fine Manual +RTF Rich Text File +SCSI Small Computer System Interface +SDK Software Development Kit +SGML Standard General Markup Language +SMIL Synchronized Multimedia Integration Language +SMS Short Message Service +SMTP Simple Mail Transfer Protocol +SOAP Simple Object Access Protocol +spec specification +SQL Structured Query Language +SSH Secure Shell +SSI Server Side Includes +SSL Secure Sockets Layer +SVG Scalable Vector Graphics +TIA Thanks in advance +TIFF Tagged Image File Format +TLD Top Level Domain +TOC Table of Contents +URI Uniform Resource Identifier +URL Uniform Resource Locator +URN Uniform Resource Name +USB Universal Serial Bus +VBA Visual Basic for Applications +VB Visual Basic +VOIP Voice Over IP +W3C World Wide Web Consortium +WAN Wide Area Network +WAP Wireless Access Protocol +WML Wireless Markup Language +WTF? What the f*** +WWW World Wide Web +WYSIWYG What You See Is What You Get +XHTML Extensible HyperText Markup Language +XML Extensible Markup Language +XSD XML (Extensible Markup Language) Schema Definition +XSL Extensible Stylesheet Language +XSLT Extensible Stylesheet Language Transformations +XUL XML User Interface Language +YMMV Your mileage may vary + +LAMP Linux, Apache, MySQL and PHP diff --git a/conf/dokuwiki.php b/conf/dokuwiki.php new file mode 100644 index 0000000..41f0fd5 --- /dev/null +++ b/conf/dokuwiki.php @@ -0,0 +1,165 @@ + tags + // 'htmldiff' - diff as HTML table + // 'html' - the full page rendered in XHTML +$conf['rss_update'] = 5*60; //Update the RSS feed every n seconds (defaults to 5 minutes) +$conf['recent_days'] = 7; //How many days of recent changes to keep. (days) +$conf['rss_show_summary'] = 1; //Add revision summary to title? 0|1 +$conf['broken_iua'] = 0; //Platform with broken ignore_user_abort (IIS+CGI) 0|1 +$conf['xsendfile'] = 0; //Use X-Sendfile (1 = lighttpd, 2 = standard) +$conf['renderer_xhtml'] = 'xhtml'; //renderer to use for main page generation +$conf['rememberme'] = 1; //Enable/disable remember me on login + +//Set target to use when creating links - leave empty for same window +$conf['target']['wiki'] = ''; +$conf['target']['interwiki'] = ''; +$conf['target']['extern'] = ''; +$conf['target']['media'] = ''; +$conf['target']['windows'] = ''; + +//Proxy setup - if your Server needs a proxy to access the web set these +$conf['proxy']['host'] = ''; +$conf['proxy']['port'] = ''; +$conf['proxy']['user'] = ''; +$conf['proxy']['pass'] = ''; +$conf['proxy']['ssl'] = 0; +$conf['proxy']['except'] = ''; + +/* Safemode Hack */ + +$conf['safemodehack'] = 0; //read http://www.dokuwiki.org/config:safemodehack ! +$conf['ftp']['host'] = 'localhost'; +$conf['ftp']['port'] = '21'; +$conf['ftp']['user'] = 'user'; +$conf['ftp']['pass'] = 'password'; +$conf['ftp']['root'] = '/home/user/htdocs'; + +$conf['readdircache'] = 0; //time cache in second for the readdir opération, 0 to deactivate. diff --git a/conf/entities.conf b/conf/entities.conf new file mode 100644 index 0000000..be9ed6d --- /dev/null +++ b/conf/entities.conf @@ -0,0 +1,22 @@ +# Typography replacements +# +# Order does matter! +# +# You can use HTML entities here, but it is not recomended because it may break +# non-HTML renderers. Use UTF-8 chars directly instead. + +<-> ↔ +-> → +<- ← +<=> ⇔ +=> ⇒ +<= ⇐ +>> » +<< « +--- — +-- – +(c) © +(tm) ™ +(r) ® +... … + diff --git a/conf/interwiki.conf b/conf/interwiki.conf new file mode 100644 index 0000000..8c3b96e --- /dev/null +++ b/conf/interwiki.conf @@ -0,0 +1,134 @@ +# Each URL may contain one of the placeholders {URL} or {NAME} +# {URL} is replaced by the URL encoded representation of the wikiname +# this is the right thing to do in most cases +# {NAME} this is replaced by the wikiname as given in the document +# no further encoding is done +# If no placeholder is defined the urlencoded name is appended to the URL + +# To prevent losing your added InterWiki shortcuts after an upgrade, +# you should add new ones to interwiki.local.conf + +hanthana http://hanthana.org/wiki/{NAME} +hanthana.si http://hanthana.org/wiki/Si:{NAME} + +wp http://en.wikipedia.org/wiki/{NAME} +wpfr http://fr.wikipedia.org/wiki/{NAME} +wpde http://de.wikipedia.org/wiki/{NAME} +wpes http://es.wikipedia.org/wiki/{NAME} +wppl http://pl.wikipedia.org/wiki/{NAME} +wpjp http://ja.wikipedia.org/wiki/{NAME} +wpmeta http://meta.wikipedia.org/wiki/{NAME} +doku http://www.dokuwiki.org/ +dokubug http://bugs.dokuwiki.org/index.php?do=details&task_id= +rfc http://www.cs.ccu.edu.tw/~chm91u/rfc2html.php?in= +man http://man.cx/ +amazon http://www.amazon.com/exec/obidos/ASIN/{URL}/splitbrain-20/ +amazon.de http://www.amazon.de/exec/obidos/ASIN/{URL}/splitbrain-21/ +amazon.uk http://www.amazon.co.uk/exec/obidos/ASIN/ +paypal https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business= +phpfn http://www.php.net/{NAME} +coral http://{HOST}.{PORT}.nyud.net:8090/{PATH}?{QUERY} +freecache http://freecache.org/{NAME} +sb http://www.splitbrain.org/go/ +skype skype:{NAME} +google.de http://www.google.de/search?q= +go http://www.google.com/search?q={URL}&btnI=lucky + +# To support VoIP/SIP links +callto callto://{NAME} + +# Standards from http://usemod.com/intermap.txt follow + +AbbeNormal http://www.ourpla.net/cgi-bin/pikie.cgi? +AcadWiki http://xarch.tu-graz.ac.at/autocad/wiki/ +Acronym http://www.acronymfinder.com/af-query.asp?String=exact&Acronym= +Advogato http://www.advogato.org/ +AIWiki http://www.ifi.unizh.ch/ailab/aiwiki/aiw.cgi? +ALife http://news.alife.org/wiki/index.php? +AndStuff http://andstuff.org/wiki.php? +Annotation http://bayle.stanford.edu/crit/nph-med.cgi/ +AnnotationWiki http://www.seedwiki.com/page.cfm?wikiid=368&doc= +AwarenessWiki http://taoriver.net/aware/ +BenefitsWiki http://www.benefitslink.com/cgi-bin/wiki.cgi? +BridgesWiki http://c2.com/w2/bridges/ +C2find http://c2.com/cgi/wiki?FindPage&value= +Cache http://www.google.com/search?q=cache: +CLiki http://ww.telent.net/cliki/ +CmWiki http://www.ourpla.net/cgi-bin/wiki.pl? +CreationMatters http://www.ourpla.net/cgi-bin/wiki.pl? +DejaNews http://www.deja.com/=dnc/getdoc.xp?AN= +DeWikiPedia http://www.wikipedia.de/wiki.cgi? +Dictionary http://www.dict.org/bin/Dict?Database=*&Form=Dict1&Strategy=*&Query= +DiveIntoOsx http://diveintoosx.org/ +DocBook http://docbook.org/wiki/moin.cgi/ +DolphinWiki http://www.object-arts.com/wiki/html/Dolphin/ +EfnetCeeWiki http://purl.net/wiki/c/ +EfnetCppWiki http://purl.net/wiki/cpp/ +EfnetPythonWiki http://purl.net/wiki/python/ +EfnetXmlWiki http://purl.net/wiki/xml/ +EljWiki http://elj.sourceforge.net/phpwiki/index.php/ +EmacsWiki http://www.emacswiki.org/cgi-bin/wiki.pl? +FinalEmpire http://final-empire.sourceforge.net/cgi-bin/wiki.pl? +Foldoc http://www.foldoc.org/foldoc/foldoc.cgi? +FoxWiki http://fox.wikis.com/wc.dll?Wiki~ +FreeBSDman http://www.FreeBSD.org/cgi/man.cgi?apropos=1&query= +Google http://www.google.com/search?q= +GoogleGroups http://groups.google.com/groups?q= +GreenCheese http://www.greencheese.org/ +HammondWiki http://www.dairiki.org/HammondWiki/index.php3? +Haribeau http://wiki.haribeau.de/cgi-bin/wiki.pl? +IAWiki http://www.IAwiki.net/ +IMDB http://us.imdb.com/Title? +JargonFile http://sunir.org/apps/meta.pl?wiki=JargonFile&redirect= +JiniWiki http://www.cdegroot.com/cgi-bin/jini? +JspWiki http://www.ecyrd.com/JSPWiki/Wiki.jsp?page= +KmWiki http://www.voght.com/cgi-bin/pywiki? +KnowHow http://www2.iro.umontreal.ca/~paquetse/cgi-bin/wiki.cgi? +LanifexWiki http://opt.lanifex.com/cgi-bin/wiki.pl? +LegoWiki http://www.object-arts.com/wiki/html/Lego-Robotics/ +LinuxWiki http://www.linuxwiki.de/ +LugKR http://lug-kr.sourceforge.net/cgi-bin/lugwiki.pl? +MathSongsWiki http://SeedWiki.com/page.cfm?wikiid=237&doc= +MbTest http://www.usemod.com/cgi-bin/mbtest.pl? +MeatBall http://www.usemod.com/cgi-bin/mb.pl? +MetaWiki http://sunir.org/apps/meta.pl? +MetaWikiPedia http://meta.wikipedia.com/wiki/ +MoinMoin http://purl.net/wiki/moin/ +MuWeb http://www.dunstable.com/scripts/MuWebWeb? +NetVillage http://www.netbros.com/? +OpenWiki http://openwiki.com/? +OrgPatterns http://www.bell-labs.com/cgi-user/OrgPatterns/OrgPatterns? +PangalacticOrg http://www.pangalactic.org/Wiki/ +PersonalTelco http://www.personaltelco.net/index.cgi/ +PhpWiki http://phpwiki.sourceforge.net/phpwiki/index.php? +Pikie http://pikie.darktech.org/cgi/pikie? +PPR http://c2.com/cgi/wiki? +PurlNet http://purl.oclc.org/NET/ +PythonInfo http://www.python.org/cgi-bin/moinmoin/ +PythonWiki http://www.pythonwiki.de/ +PyWiki http://www.voght.com/cgi-bin/pywiki? +SeaPig http://www.seapig.org/ +SeattleWireless http://seattlewireless.net/? +SenseisLibrary http://senseis.xmp.net/? +Shakti http://cgi.algonet.se/htbin/cgiwrap/pgd/ShaktiWiki/ +SourceForge http://sourceforge.net/{NAME} +Squeak http://minnow.cc.gatech.edu/squeak/ +StrikiWiki http://ch.twi.tudelft.nl/~mostert/striki/teststriki.pl? +SVGWiki http://www.protocol7.com/svg-wiki/default.asp? +Tavi http://tavi.sourceforge.net/index.php? +TmNet http://www.technomanifestos.net/? +TMwiki http://www.EasyTopicMaps.com/?page= +TWiki http://twiki.org/cgi-bin/view/{NAME} +TwistedWiki http://purl.net/wiki/twisted/ +Unreal http://wiki.beyondunreal.com/wiki/ +UseMod http://www.usemod.com/cgi-bin/wiki.pl? +VisualWorks http://wiki.cs.uiuc.edu/VisualWorks/ +WebDevWikiNL http://www.promo-it.nl/WebDevWiki/index.php?page= +WebSeitzWiki http://webseitz.fluxent.com/wiki/ +Why http://clublet.com/c/c/why? +Wiki http://c2.com/cgi/wiki? +WikiPedia http://www.wikipedia.com/wiki/ +WikiWorld http://WikiWorld.com/wiki/index.php/ +YpsiEyeball http://sknkwrks.dyndns.org:1957/writewiki/wiki.pl? +ZWiki http://www.zwiki.org/ + diff --git a/conf/license.php b/conf/license.php new file mode 100644 index 0000000..89728ab --- /dev/null +++ b/conf/license.php @@ -0,0 +1,36 @@ + 'CC0 1.0 Universal', + 'url' => 'http://creativecommons.org/publicdomain/zero/1.0/', +); +$license['publicdomain'] = array( + 'name' => 'Public Domain', + 'url' => 'http://creativecommons.org/licenses/publicdomain/', +); +$license['cc-by'] = array( + 'name' => 'CC Attribution 3.0 Unported', + 'url' => 'http://creativecommons.org/licenses/by/3.0/', +); +$license['cc-by-sa'] = array( + 'name' => 'CC Attribution-Share Alike 3.0 Unported', + 'url' => 'http://creativecommons.org/licenses/by-sa/3.0/', +); +$license['gnufdl'] = array( + 'name' => 'GNU Free Documentation License 1.3', + 'url' => 'http://www.gnu.org/licenses/fdl-1.3.html', +); +$license['cc-by-nc'] = array( + 'name' => 'CC Attribution-Noncommercial 3.0 Unported', + 'url' => 'http://creativecommons.org/licenses/by-nc/3.0/', +); +$license['cc-by-nc-sa'] = array( + 'name' => 'CC Attribution-Noncommercial-Share Alike 3.0 Unported', + 'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/', +); + diff --git a/conf/local.php.dist b/conf/local.php.dist new file mode 100644 index 0000000..0397954 --- /dev/null +++ b/conf/local.php.dist @@ -0,0 +1,16 @@ + array('Iptc.Headline', + 'img_title', + 'text'), + + 20 => array('', + 'img_date', + 'date', + array('Date.EarliestTime')), + + 30 => array('', + 'img_fname', + 'text', + array('File.Name')), + + 40 => array('Iptc.Caption', + 'img_caption', + 'textarea', + array('Exif.UserComment', + 'Exif.TIFFImageDescription', + 'Exif.TIFFUserComment')), + + 50 => array('Iptc.Byline', + 'img_artist', + 'text', + array('Exif.TIFFArtist', + 'Exif.Artist', + 'Iptc.Credit')), + + 60 => array('Iptc.CopyrightNotice', + 'img_copyr', + 'text', + array('Exif.TIFFCopyright', + 'Exif.Copyright')), + + 70 => array('', + 'img_format', + 'text', + array('File.Format')), + + 80 => array('', + 'img_fsize', + 'text', + array('File.NiceSize')), + + 90 => array('', + 'img_width', + 'text', + array('File.Width')), + + 100 => array('', + 'img_height', + 'text', + array('File.Height')), + + 110 => array('', + 'img_camera', + 'text', + array('Simple.Camera')), + + 120 => array('Iptc.Keywords', + 'img_keywords', + 'text', + array('Exif.Category')), +); diff --git a/conf/mime.conf b/conf/mime.conf new file mode 100644 index 0000000..deea3b5 --- /dev/null +++ b/conf/mime.conf @@ -0,0 +1,64 @@ +# Allowed uploadable file extensions and mimetypes are defined here. +# To extend this file it is recommended to create a mime.local.conf +# file. Mimetypes that should be downloadable and not be opened in the +# should be prefixed with a ! + +jpg image/jpeg +jpeg image/jpeg +gif image/gif +png image/png + +swf application/x-shockwave-flash +mp3 audio/mpeg +ogg audio/ogg +wav audio/wav + +tgz !application/octet-stream +tar !application/x-gtar +gz !application/octet-stream +bz2 !application/octet-stream +zip !application/zip +rar !application/rar +7z !application/x-7z-compressed + +pdf application/pdf +ps !application/postscript + +rpm !application/octet-stream +deb !application/octet-stream + +doc !application/msword +xls !application/msexcel +ppt !application/mspowerpoint +rtf !application/msword + +docx !application/vnd.openxmlformats-officedocument.wordprocessingml.document +xlsx !application/vnd.openxmlformats-officedocument.spreadsheetml.sheet +pptx !application/vnd.openxmlformats-officedocument.presentationml.presentation + +sxw !application/soffice +sxc !application/soffice +sxi !application/soffice +sxd !application/soffice + +odc !application/vnd.oasis.opendocument.chart +odf !application/vnd.oasis.opendocument.formula +odg !application/vnd.oasis.opendocument.graphics +odi !application/vnd.oasis.opendocument.image +odp !application/vnd.oasis.opendocument.presentation +ods !application/vnd.oasis.opendocument.spreadsheet +odt !application/vnd.oasis.opendocument.text + +# You should enable HTML and Text uploads only for restricted Wikis. +# Spammers are known to upload spam pages through unprotected Wikis. +# Note: Enabling HTML opens Cross Site Scripting vulnerabilities +# through JavaScript. Only enable this with trusted users. You +# need to disable the iexssprotect option additionally to +# adding the mime type here +html text/html +#htm text/html +#txt text/plain +#conf text/plain +#xml text/xml +#csv text/csv + diff --git a/conf/mysql.conf.php.example b/conf/mysql.conf.php.example new file mode 100644 index 0000000..94bc14e --- /dev/null +++ b/conf/mysql.conf.php.example @@ -0,0 +1,253 @@ + +# Don't modify the lines above +# +# Userfile +# +# Format: +# +# user:MD5password:Real Name:email:groups,comma,seperated + diff --git a/conf/wordblock.conf b/conf/wordblock.conf new file mode 100644 index 0000000..fe451f2 --- /dev/null +++ b/conf/wordblock.conf @@ -0,0 +1,32 @@ +# This blacklist is maintained by the DokuWiki community +# patches welcome +# +https?:\/\/(\S*?)(-side-effects|top|pharm|pill|discount|discount-|deal|price|order|now|best|cheap|cheap-|online|buy|buy-|sale|sell)(\S*?)(cialis|viagra|prazolam|xanax|zanax|soma|vicodin|zenical|xenical|meridia|paxil|prozac|claritin|allegra|lexapro|wellbutrin|zoloft|retin|valium|levitra|phentermine) +gay\s*sex +bi\s*sex +incest +zoosex +gang\s*bang +facials +ladyboy +fetish +\btits\b +\brape\b +bolea\.com +52crystal +baida\.org +web-directory\.awardspace\.us +korsan-team\.com +BUDA TAMAMDIR +wow-powerleveling-wow\.com +wow gold +wow-gold\.dinmo\.cn +downgrade-vista\.com +downgradetowindowsxp\.com +elegantugg\.com +classicedhardy\.com +research-service\.com +https?:\/\/(\S*?)(2-pay-secure|911essay|academia-research|anypapers|applicationessay|bestbuyessay|bestdissertation|bestessay|bestresume|besttermpaper|businessessay|college-paper|customessay|custom-made-paper|custom-writing|degree-?result|dissertationblog|dissertation-service|dissertations?expert|essaybank|essay-?blog|essaycapital|essaylogic|essaymill|essayontime|essaypaper|essays?land|essaytownsucks|essay-?writ|fastessays|freelancercareers|genuinecontent|genuineessay|genuinepaper|goessay|grandresume|killer-content|ma-dissertation|managementessay|masterpaper|mightystudent|needessay|researchedge|researchpaper-blog|resumecvservice|resumesexperts|resumesplanet|rushessay|samedayessay|superiorcontent|superiorpaper|superiorthesis|term-paper|termpaper-blog|term-paper-research|thesisblog|universalresearch|valwriting|vdwriters|wisetranslation|writersassembly|writers\.com\.ph|writers\.ph) +flatsinmumbai\.co\.in +https?:\/\/(\S*?)penny-?stock +mattressreview\.biz diff --git a/doku.php b/doku.php new file mode 100644 index 0000000..9cc0cf8 --- /dev/null +++ b/doku.php @@ -0,0 +1,93 @@ + + */ + +// update message version +$updateVersion = 36.2; + +// xdebug_start_profiling(); + +if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/'); + +if (isset($_SERVER['HTTP_X_DOKUWIKI_DO'])){ + $ACT = trim(strtolower($_SERVER['HTTP_X_DOKUWIKI_DO'])); +} elseif (!empty($_REQUEST['idx'])) { + $ACT = 'index'; +} elseif (isset($_REQUEST['do'])) { + $ACT = $_REQUEST['do']; +} else { + $ACT = 'show'; +} + +// load and initialize the core system +require_once(DOKU_INC.'inc/init.php'); + +//import variables +$_REQUEST['id'] = str_replace("\xC2\xAD",'',$_REQUEST['id']); //soft-hyphen +$QUERY = trim($_REQUEST['id']); +$ID = getID(); + +// deprecated 2011-01-14 +$NS = getNS($ID); + +$REV = $_REQUEST['rev']; +$IDX = $_REQUEST['idx']; +$DATE = $_REQUEST['date']; +$RANGE = $_REQUEST['range']; +$HIGH = $_REQUEST['s']; +if(empty($HIGH)) $HIGH = getGoogleQuery(); + +if (isset($_POST['wikitext'])) { + $TEXT = cleanText($_POST['wikitext']); +} +$PRE = cleanText(substr($_POST['prefix'], 0, -1)); +$SUF = cleanText($_POST['suffix']); +$SUM = $_REQUEST['summary']; + +//sanitize revision +$REV = preg_replace('/[^0-9]/','',$REV); + +//make infos about the selected page available +$INFO = pageinfo(); + +//export minimal infos to JS, plugins can add more +$JSINFO['id'] = $ID; +$JSINFO['namespace'] = (string) $INFO['namespace']; + + +// handle debugging +if($conf['allowdebug'] && $ACT == 'debug'){ + html_debug(); + exit; +} + +//send 404 for missing pages if configured or ID has special meaning to bots +if(!$INFO['exists'] && + ($conf['send404'] || preg_match('/^(robots\.txt|sitemap\.xml(\.gz)?|favicon\.ico|crossdomain\.xml)$/',$ID)) && + ($ACT == 'show' || (!is_array($ACT) && substr($ACT,0,7) == 'export_')) ){ + header('HTTP/1.0 404 Not Found'); +} + +//prepare breadcrumbs (initialize a static var) +if ($conf['breadcrumbs']) breadcrumbs(); + +// check upstream +checkUpdateMessages(); + +$tmp = array(); // No event data +trigger_event('DOKUWIKI_STARTED',$tmp); + +//close session +session_write_close(); + +//do the work +act_dispatch($ACT); + +$tmp = array(); // No event data +trigger_event('DOKUWIKI_DONE', $tmp); + +// xdebug_dump_function_profile(1); diff --git a/inc/.htaccess b/inc/.htaccess new file mode 100644 index 0000000..68ae43e --- /dev/null +++ b/inc/.htaccess @@ -0,0 +1,4 @@ +## no access to the inc directory +order allow,deny +deny from all +Satisfy All diff --git a/inc/DifferenceEngine.php b/inc/DifferenceEngine.php new file mode 100644 index 0000000..6e1d073 --- /dev/null +++ b/inc/DifferenceEngine.php @@ -0,0 +1,1163 @@ + + * @license You may copy this code freely under the conditions of the GPL. + */ +define('USE_ASSERTS', function_exists('assert')); + +class _DiffOp { + var $type; + var $orig; + var $closing; + + function reverse() { + trigger_error("pure virtual", E_USER_ERROR); + } + + function norig() { + return $this->orig ? count($this->orig) : 0; + } + + function nclosing() { + return $this->closing ? count($this->closing) : 0; + } +} + +class _DiffOp_Copy extends _DiffOp { + var $type = 'copy'; + + function __construct($orig, $closing = false) { + if (!is_array($closing)) + $closing = $orig; + $this->orig = $orig; + $this->closing = $closing; + } + + function reverse() { + return new _DiffOp_Copy($this->closing, $this->orig); + } +} + +class _DiffOp_Delete extends _DiffOp { + var $type = 'delete'; + + function __construct($lines) { + $this->orig = $lines; + $this->closing = false; + } + + function reverse() { + return new _DiffOp_Add($this->orig); + } +} + +class _DiffOp_Add extends _DiffOp { + var $type = 'add'; + + function __construct($lines) { + $this->closing = $lines; + $this->orig = false; + } + + function reverse() { + return new _DiffOp_Delete($this->closing); + } +} + +class _DiffOp_Change extends _DiffOp { + var $type = 'change'; + + function __construct($orig, $closing) { + $this->orig = $orig; + $this->closing = $closing; + } + + function reverse() { + return new _DiffOp_Change($this->closing, $this->orig); + } +} + + +/** + * Class used internally by Diff to actually compute the diffs. + * + * 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 are (and a bit of code) are from from analyze.c, from GNU + * diffutils-2.7, which can be found at: + * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz + * + * closingly, some ideas (subdivision by NCHUNKS > 2, and some optimizations) + * are my own. + * + * @author Geoffrey T. Dairiki + * @access private + */ +class _DiffEngine { + + function diff($from_lines, $to_lines) { + $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->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged); + $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged); + + // Compute the edit operations. + $edits = array(); + $xi = $yi = 0; + while ($xi < $n_from || $yi < $n_to) { + USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]); + USE_ASSERTS && 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 _DiffOp_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 _DiffOp_Change($delete, $add); + elseif ($delete) + $edits[] = new _DiffOp_Delete($delete); + elseif ($add) + $edits[] = new _DiffOp_Add($add); + } + return $edits; + } + + + /** + * Divide 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 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 ($junk, $y) = each($matches)) + if (empty($this->in_seq[$y])) { + $k = $this->_lcs_pos($y); + USE_ASSERTS && assert($k > 0); + $ymids[$k] = $ymids[$k-1]; + break; + } + while (list ($junk, $y) = each($matches)) { + if ($y > $this->seq[$k-1]) { + USE_ASSERTS && 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; + } + else if (empty($this->in_seq[$y])) { + $k = $this->_lcs_pos($y); + USE_ASSERTS && 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 _lcs_pos($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; + } + + USE_ASSERTS && assert($ypos != $this->seq[$end]); + + $this->in_seq[$this->seq[$end]] = false; + $this->seq[$end] = $ypos; + $this->in_seq[$ypos] = 1; + return $end; + } + + /** + * Find 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; + } + } + } + + /** + * Adjust 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 _shift_boundaries($lines, &$changed, $other_changed) { + $i = 0; + $j = 0; + + USE_ASSERTS && assert('count($lines) == count($changed)'); + $len = count($lines); + $other_len = count($other_changed); + + while (1) { + /* + * Scan forwards to find 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]) { + USE_ASSERTS && 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--; + USE_ASSERTS && assert('$j > 0'); + while ($other_changed[--$j]) + continue; + USE_ASSERTS && 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++; + + USE_ASSERTS && 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; + USE_ASSERTS && assert('$j > 0'); + while ($other_changed[--$j]) + continue; + USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); + } + } + } +} + +/** + * Class representing a 'diff' between two sequences of strings. + */ +class Diff { + + var $edits; + + /** + * Constructor. + * Computes diff between sequences of strings. + * + * @param $from_lines array An array of strings. + * (Typically these are lines from a file.) + * @param $to_lines array An array of strings. + */ + function __construct($from_lines, $to_lines) { + $eng = new _DiffEngine; + $this->edits = $eng->diff($from_lines, $to_lines); + //$this->_check($from_lines, $to_lines); + } + + /** + * Compute reversed Diff. + * + * SYNOPSIS: + * + * $diff = new Diff($lines1, $lines2); + * $rev = $diff->reverse(); + * @return object A Diff object representing the inverse of the + * original diff. + */ + function reverse() { + $rev = $this; + $rev->edits = array(); + foreach ($this->edits as $edit) { + $rev->edits[] = $edit->reverse(); + } + return $rev; + } + + /** + * Check for empty diff. + * + * @return bool True iff two sequences were identical. + */ + function isEmpty() { + foreach ($this->edits as $edit) { + if ($edit->type != 'copy') + return false; + } + return true; + } + + /** + * Compute the length of the Longest Common Subsequence (LCS). + * + * This is mostly for diagnostic purposed. + * + * @return int The length of the LCS. + */ + function lcs() { + $lcs = 0; + foreach ($this->edits as $edit) { + if ($edit->type == 'copy') + $lcs += count($edit->orig); + } + return $lcs; + } + + /** + * Get the original set of lines. + * + * This reconstructs the $from_lines parameter passed to the + * constructor. + * + * @return array The original sequence of strings. + */ + function orig() { + $lines = array(); + + foreach ($this->edits as $edit) { + if ($edit->orig) + array_splice($lines, count($lines), 0, $edit->orig); + } + return $lines; + } + + /** + * Get the closing set of lines. + * + * This reconstructs the $to_lines parameter passed to the + * constructor. + * + * @return array The sequence of strings. + */ + function closing() { + $lines = array(); + + foreach ($this->edits as $edit) { + if ($edit->closing) + array_splice($lines, count($lines), 0, $edit->closing); + } + return $lines; + } + + /** + * Check a Diff for validity. + * + * This is here only for debugging purposes. + */ + function _check($from_lines, $to_lines) { + if (serialize($from_lines) != serialize($this->orig())) + trigger_error("Reconstructed original doesn't match", E_USER_ERROR); + if (serialize($to_lines) != serialize($this->closing())) + trigger_error("Reconstructed closing doesn't match", E_USER_ERROR); + + $rev = $this->reverse(); + if (serialize($to_lines) != serialize($rev->orig())) + trigger_error("Reversed original doesn't match", E_USER_ERROR); + if (serialize($from_lines) != serialize($rev->closing())) + trigger_error("Reversed closing doesn't match", E_USER_ERROR); + + $prevtype = 'none'; + foreach ($this->edits as $edit) { + if ($prevtype == $edit->type) + trigger_error("Edit sequence is non-optimal", E_USER_ERROR); + $prevtype = $edit->type; + } + + $lcs = $this->lcs(); + trigger_error("Diff okay: LCS = $lcs", E_USER_NOTICE); + } +} + +/** + * FIXME: bad name. + */ +class MappedDiff extends Diff { + /** + * Constructor. + * + * Computes 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 $from_lines array An array of strings. + * (Typically these are lines from a file.) + * + * @param $to_lines array An array of strings. + * + * @param $mapped_from_lines array 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 $mapped_to_lines array This array should + * have the same number of elements as $to_lines. + */ + function __construct($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::__construct($mapped_from_lines, $mapped_to_lines); + + $xi = $yi = 0; + $ecnt = count($this->edits); + for ($i = 0; $i < $ecnt; $i++) { + $orig = &$this->edits[$i]->orig; + if (is_array($orig)) { + $orig = array_slice($from_lines, $xi, count($orig)); + $xi += count($orig); + } + + $closing = &$this->edits[$i]->closing; + if (is_array($closing)) { + $closing = array_slice($to_lines, $yi, count($closing)); + $yi += count($closing); + } + } + } +} + +/** + * A class to format Diffs + * + * This class formats the diff in classic diff format. + * It is intended that this class be customized via inheritance, + * to obtain fancier outputs. + */ +class DiffFormatter { + /** + * 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; + + /** + * Format a diff. + * + * @param $diff object A Diff object. + * @return string The formatted output. + */ + function format($diff) { + + $xi = $yi = 1; + $block = false; + $context = array(); + + $nlead = $this->leading_context_lines; + $ntrail = $this->trailing_context_lines; + + $this->_start_diff(); + + foreach ($diff->edits as $edit) { + if ($edit->type == 'copy') { + if (is_array($block)) { + if (count($edit->orig) <= $nlead + $ntrail) { + $block[] = $edit; + } + else{ + if ($ntrail) { + $context = array_slice($edit->orig, 0, $ntrail); + $block[] = new _DiffOp_Copy($context); + } + $this->_block($x0, $ntrail + $xi - $x0, $y0, $ntrail + $yi - $y0, $block); + $block = false; + } + } + $context = $edit->orig; + } + else { + if (! is_array($block)) { + $context = array_slice($context, count($context) - $nlead); + $x0 = $xi - count($context); + $y0 = $yi - count($context); + $block = array(); + if ($context) + $block[] = new _DiffOp_Copy($context); + } + $block[] = $edit; + } + + if ($edit->orig) + $xi += count($edit->orig); + if ($edit->closing) + $yi += count($edit->closing); + } + + if (is_array($block)) + $this->_block($x0, $xi - $x0, $y0, $yi - $y0, $block); + + return $this->_end_diff(); + } + + function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) { + $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen)); + foreach ($edits as $edit) { + if ($edit->type == 'copy') + $this->_context($edit->orig); + elseif ($edit->type == 'add') + $this->_added($edit->closing); + elseif ($edit->type == 'delete') + $this->_deleted($edit->orig); + elseif ($edit->type == 'change') + $this->_changed($edit->orig, $edit->closing); + else + trigger_error("Unknown edit type", E_USER_ERROR); + } + $this->_end_block(); + } + + function _start_diff() { + ob_start(); + } + + function _end_diff() { + $val = ob_get_contents(); + ob_end_clean(); + return $val; + } + + function _block_header($xbeg, $xlen, $ybeg, $ylen) { + if ($xlen > 1) + $xbeg .= "," . ($xbeg + $xlen - 1); + if ($ylen > 1) + $ybeg .= "," . ($ybeg + $ylen - 1); + + return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg; + } + + function _start_block($header) { + echo $header; + } + + function _end_block() { + } + + function _lines($lines, $prefix = ' ') { + foreach ($lines as $line) + echo "$prefix $line\n"; + } + + function _context($lines) { + $this->_lines($lines); + } + + function _added($lines) { + $this->_lines($lines, ">"); + } + function _deleted($lines) { + $this->_lines($lines, "<"); + } + + function _changed($orig, $closing) { + $this->_deleted($orig); + echo "---\n"; + $this->_added($closing); + } +} + + +/** + * Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3 + * + */ + +define('NBSP', "\xC2\xA0"); // utf-8 non-breaking space. + +class _HWLDF_WordAccumulator { + + function __construct() { + $this->_lines = array(); + $this->_line = ''; + $this->_group = ''; + $this->_tag = ''; + } + + function _flushGroup($new_tag) { + if ($this->_group !== '') { + if ($this->_tag == 'mark') + $this->_line .= ''.$this->_group.''; + elseif ($this->_tag == 'add') + $this->_line .= ''.$this->_group.''; + elseif ($this->_tag == 'del') + $this->_line .= ''.$this->_group.''; + else + $this->_line .= $this->_group; + } + $this->_group = ''; + $this->_tag = $new_tag; + } + + function _flushLine($new_tag) { + $this->_flushGroup($new_tag); + if ($this->_line != '') + $this->_lines[] = $this->_line; + $this->_line = ''; + } + + function addWords($words, $tag = '') { + if ($tag != $this->_tag) + $this->_flushGroup($tag); + + foreach ($words as $word) { + // new-line should only come as first char of word. + if ($word == '') + continue; + if ($word[0] == "\n") { + $this->_group .= NBSP; + $this->_flushLine($tag); + $word = substr($word, 1); + } + assert(!strstr($word, "\n")); + $this->_group .= $word; + } + } + + function getLines() { + $this->_flushLine('~done'); + return $this->_lines; + } +} + +class WordLevelDiff extends MappedDiff { + + function __construct($orig_lines, $closing_lines) { + list ($orig_words, $orig_stripped) = $this->_split($orig_lines); + list ($closing_words, $closing_stripped) = $this->_split($closing_lines); + + parent::__construct($orig_words, $closing_words, $orig_stripped, $closing_stripped); + } + + function _split($lines) { + if (!preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xsu', + implode("\n", $lines), $m)) { + return array(array(''), array('')); + } + return array($m[0], $m[1]); + } + + function orig() { + $orig = new _HWLDF_WordAccumulator; + + foreach ($this->edits as $edit) { + if ($edit->type == 'copy') + $orig->addWords($edit->orig); + elseif ($edit->orig) + $orig->addWords($edit->orig, 'mark'); + } + return $orig->getLines(); + } + + function closing() { + $closing = new _HWLDF_WordAccumulator; + + foreach ($this->edits as $edit) { + if ($edit->type == 'copy') + $closing->addWords($edit->closing); + elseif ($edit->closing) + $closing->addWords($edit->closing, 'mark'); + } + return $closing->getLines(); + } +} + +class InlineWordLevelDiff extends MappedDiff { + + function __construct($orig_lines, $closing_lines) { + list ($orig_words, $orig_stripped) = $this->_split($orig_lines); + list ($closing_words, $closing_stripped) = $this->_split($closing_lines); + + parent::__construct($orig_words, $closing_words, $orig_stripped, $closing_stripped); + } + + function _split($lines) { + if (!preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xsu', + implode("\n", $lines), $m)) { + return array(array(''), array('')); + } + return array($m[0], $m[1]); + } + + function inline() { + $orig = new _HWLDF_WordAccumulator; + foreach ($this->edits as $edit) { + if ($edit->type == 'copy') + $orig->addWords($edit->closing); + elseif ($edit->type == 'change'){ + $orig->addWords($edit->orig, 'del'); + $orig->addWords($edit->closing, 'add'); + } elseif ($edit->type == 'delete') + $orig->addWords($edit->orig, 'del'); + elseif ($edit->type == 'add') + $orig->addWords($edit->closing, 'add'); + elseif ($edit->orig) + $orig->addWords($edit->orig, 'del'); + } + return $orig->getLines(); + } +} + +/** + * "Unified" diff formatter. + * + * This class formats the diff in classic "unified diff" format. + */ +class UnifiedDiffFormatter extends DiffFormatter { + + function __construct($context_lines = 4) { + $this->leading_context_lines = $context_lines; + $this->trailing_context_lines = $context_lines; + } + + function _block_header($xbeg, $xlen, $ybeg, $ylen) { + if ($xlen != 1) + $xbeg .= "," . $xlen; + if ($ylen != 1) + $ybeg .= "," . $ylen; + return "@@ -$xbeg +$ybeg @@\n"; + } + + function _added($lines) { + $this->_lines($lines, "+"); + } + function _deleted($lines) { + $this->_lines($lines, "-"); + } + function _changed($orig, $final) { + $this->_deleted($orig); + $this->_added($final); + } +} + +/** + * Wikipedia Table style diff formatter. + * + */ +class TableDiffFormatter extends DiffFormatter { + + function __construct() { + $this->leading_context_lines = 2; + $this->trailing_context_lines = 2; + } + + function format($diff) { + // Preserve whitespaces by converting some to non-breaking spaces. + // Do not convert all of them to allow word-wrap. + $val = parent::format($diff); + $val = str_replace(' ','  ', $val); + $val = preg_replace('/ (?=<)|(?<=[ >]) /', ' ', $val); + return $val; + } + + function _pre($text){ + $text = htmlspecialchars($text); + return $text; + } + + function _block_header($xbeg, $xlen, $ybeg, $ylen) { + global $lang; + $l1 = $lang['line'].' '.$xbeg; + $l2 = $lang['line'].' '.$ybeg; + $r = ''.$l1.":\n". + ''.$l2.":\n". + "\n"; + return $r; + } + + function _start_block($header) { + print($header); + } + + function _end_block() { + } + + function _lines($lines, $prefix=' ', $color="white") { + } + + function addedLine($line) { + return '+' . $line.''; + } + + function deletedLine($line) { + return '-' . $line.''; + } + + function emptyLine() { + return ' '; + } + + function contextLine($line) { + return ' '.$line.''; + } + + function _added($lines) { + foreach ($lines as $line) { + print('' . $this->emptyLine() . $this->addedLine($line) . "\n"); + } + } + + function _deleted($lines) { + foreach ($lines as $line) { + print('' . $this->deletedLine($line) . $this->emptyLine() . "\n"); + } + } + + function _context($lines) { + foreach ($lines as $line) { + print('' . $this->contextLine($line) . $this->contextLine($line) . "\n"); + } + } + + function _changed($orig, $closing) { + $diff = new WordLevelDiff($orig, $closing); + $del = $diff->orig(); + $add = $diff->closing(); + + while ($line = array_shift($del)) { + $aline = array_shift($add); + print('' . $this->deletedLine($line) . $this->addedLine($aline) . "\n"); + } + $this->_added($add); # If any leftovers + } +} + +/** + * Inline style diff formatter. + * + */ +class InlineDiffFormatter extends DiffFormatter { + var $colspan = 4; + + function __construct() { + $this->leading_context_lines = 2; + $this->trailing_context_lines = 2; + } + + function format($diff) { + // Preserve whitespaces by converting some to non-breaking spaces. + // Do not convert all of them to allow word-wrap. + $val = parent::format($diff); + $val = str_replace(' ','  ', $val); + $val = preg_replace('/ (?=<)|(?<=[ >]) /', ' ', $val); + return $val; + } + + function _pre($text){ + $text = htmlspecialchars($text); + return $text; + } + + function _block_header($xbeg, $xlen, $ybeg, $ylen) { + global $lang; + if ($xlen != 1) + $xbeg .= "," . $xlen; + if ($ylen != 1) + $ybeg .= "," . $ylen; + $r = '@@ '.$lang['line']." -$xbeg +$ybeg @@"; + $r .= ' '.$lang['deleted'].''; + $r .= ' '.$lang['created'].''; + $r .= "\n"; + return $r; + } + + function _start_block($header) { + print($header."\n"); + } + + function _end_block() { + } + + function _lines($lines, $prefix=' ', $color="white") { + } + + function _added($lines) { + foreach ($lines as $line) { + print(''. $line . "\n"); + } + } + + function _deleted($lines) { + foreach ($lines as $line) { + print('' . $line . "\n"); + } + } + + function _context($lines) { + foreach ($lines as $line) { + print(''.$line."\n"); + } + } + + function _changed($orig, $closing) { + $diff = new InlineWordLevelDiff($orig, $closing); + $add = $diff->inline(); + + foreach ($add as $line) + print(''.$line."\n"); + } +} + + +//Setup VIM: ex: et ts=4 : diff --git a/inc/EmailAddressValidator.php b/inc/EmailAddressValidator.php new file mode 100644 index 0000000..bb4ef0c --- /dev/null +++ b/inc/EmailAddressValidator.php @@ -0,0 +1,191 @@ + + * @link http://code.google.com/p/php-email-address-validation/ + * @license http://www.opensource.org/licenses/bsd-license.php + * @version SVN r10 + Issue 15 fix + Issue 12 fix + */ +class EmailAddressValidator { + /** + * Set true to allow addresses like me@localhost + */ + public $allowLocalAddresses = false; + + /** + * Check email address validity + * @param strEmailAddress Email address to be checked + * @return True if email is valid, false if not + */ + public function check_email_address($strEmailAddress) { + + // If magic quotes is "on", email addresses with quote marks will + // fail validation because of added escape characters. Uncommenting + // the next three lines will allow for this issue. + //if (get_magic_quotes_gpc()) { + // $strEmailAddress = stripslashes($strEmailAddress); + //} + + // Control characters are not allowed + if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $strEmailAddress)) { + return false; + } + + // Check email length - min 3 (a@a), max 256 + if (!$this->check_text_length($strEmailAddress, 3, 256)) { + return false; + } + + // Split it into sections using last instance of "@" + $intAtSymbol = strrpos($strEmailAddress, '@'); + if ($intAtSymbol === false) { + // No "@" symbol in email. + return false; + } + $arrEmailAddress[0] = substr($strEmailAddress, 0, $intAtSymbol); + $arrEmailAddress[1] = substr($strEmailAddress, $intAtSymbol + 1); + + // Count the "@" symbols. Only one is allowed, except where + // contained in quote marks in the local part. Quickest way to + // check this is to remove anything in quotes. We also remove + // characters escaped with backslash, and the backslash + // character. + $arrTempAddress[0] = preg_replace('/\./' + ,'' + ,$arrEmailAddress[0]); + $arrTempAddress[0] = preg_replace('/"[^"]+"/' + ,'' + ,$arrTempAddress[0]); + $arrTempAddress[1] = $arrEmailAddress[1]; + $strTempAddress = $arrTempAddress[0] . $arrTempAddress[1]; + // Then check - should be no "@" symbols. + if (strrpos($strTempAddress, '@') !== false) { + // "@" symbol found + return false; + } + + // Check local portion + if (!$this->check_local_portion($arrEmailAddress[0])) { + return false; + } + + // Check domain portion + if (!$this->check_domain_portion($arrEmailAddress[1])) { + return false; + } + + // If we're still here, all checks above passed. Email is valid. + return true; + + } + + /** + * Checks email section before "@" symbol for validity + * @param strLocalPortion Text to be checked + * @return True if local portion is valid, false if not + */ + protected function check_local_portion($strLocalPortion) { + // Local portion can only be from 1 to 64 characters, inclusive. + // Please note that servers are encouraged to accept longer local + // parts than 64 characters. + if (!$this->check_text_length($strLocalPortion, 1, 64)) { + return false; + } + // Local portion must be: + // 1) a dot-atom (strings separated by periods) + // 2) a quoted string + // 3) an obsolete format string (combination of the above) + $arrLocalPortion = explode('.', $strLocalPortion); + for ($i = 0, $max = sizeof($arrLocalPortion); $i < $max; $i++) { + if (!preg_match('.^(' + . '([A-Za-z0-9!#$%&\'*+/=?^_`{|}~-]' + . '[A-Za-z0-9!#$%&\'*+/=?^_`{|}~-]{0,63})' + .'|' + . '("[^\\\"]{0,62}")' + .')$.' + ,$arrLocalPortion[$i])) { + return false; + } + } + return true; + } + + /** + * Checks email section after "@" symbol for validity + * @param strDomainPortion Text to be checked + * @return True if domain portion is valid, false if not + */ + protected function check_domain_portion($strDomainPortion) { + // Total domain can only be from 1 to 255 characters, inclusive + if (!$this->check_text_length($strDomainPortion, 1, 255)) { + return false; + } + + // some IPv4/v6 regexps borrowed from Feyd + // see: http://forums.devnetwork.net/viewtopic.php?f=38&t=53479 + $dec_octet = '(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|[0-9])'; + $hex_digit = '[A-Fa-f0-9]'; + $h16 = "{$hex_digit}{1,4}"; + $IPv4Address = "$dec_octet\\.$dec_octet\\.$dec_octet\\.$dec_octet"; + $ls32 = "(?:$h16:$h16|$IPv4Address)"; + $IPv6Address = + "(?:(?:{$IPv4Address})|(?:". + "(?:$h16:){6}$ls32" . + "|::(?:$h16:){5}$ls32" . + "|(?:$h16)?::(?:$h16:){4}$ls32" . + "|(?:(?:$h16:){0,1}$h16)?::(?:$h16:){3}$ls32" . + "|(?:(?:$h16:){0,2}$h16)?::(?:$h16:){2}$ls32" . + "|(?:(?:$h16:){0,3}$h16)?::(?:$h16:){1}$ls32" . + "|(?:(?:$h16:){0,4}$h16)?::$ls32" . + "|(?:(?:$h16:){0,5}$h16)?::$h16" . + "|(?:(?:$h16:){0,6}$h16)?::" . + ")(?:\\/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))?)"; + + // Check if domain is IP, possibly enclosed in square brackets. + if (preg_match("/^($IPv4Address|\[$IPv4Address\]|\[$IPv6Address\])$/", + $strDomainPortion)){ + return true; + } else { + $arrDomainPortion = explode('.', $strDomainPortion); + if (!$this->allowLocalAddresses && sizeof($arrDomainPortion) < 2) { + return false; // Not enough parts to domain + } + for ($i = 0, $max = sizeof($arrDomainPortion); $i < $max; $i++) { + // Each portion must be between 1 and 63 characters, inclusive + if (!$this->check_text_length($arrDomainPortion[$i], 1, 63)) { + return false; + } + if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|' + .'([A-Za-z0-9]+))$/', $arrDomainPortion[$i])) { + return false; + } + if ($i == $max - 1) { // TLD cannot be only numbers + if (strlen(preg_replace('/[0-9]/', '', $arrDomainPortion[$i])) <= 0) { + return false; + } + } + } + } + return true; + } + + /** + * Check given text length is between defined bounds + * @param strText Text to be checked + * @param intMinimum Minimum acceptable length + * @param intMaximum Maximum acceptable length + * @return True if string is within bounds (inclusive), false if not + */ + protected function check_text_length($strText, $intMinimum, $intMaximum) { + // Minimum and maximum are both inclusive + $intTextLength = strlen($strText); + if (($intTextLength < $intMinimum) || ($intTextLength > $intMaximum)) { + return false; + } else { + return true; + } + } + +} + diff --git a/inc/FeedParser.php b/inc/FeedParser.php new file mode 100644 index 0000000..e5f1fb6 --- /dev/null +++ b/inc/FeedParser.php @@ -0,0 +1,76 @@ + + */ + +if(!defined('DOKU_INC')) die('meh.'); + +/** + * We override some methods of the original SimplePie class here + */ +class FeedParser extends SimplePie { + + /** + * Constructor. Set some defaults + */ + function FeedParser(){ + $this->SimplePie(); + $this->enable_cache(false); + $this->set_file_class('FeedParser_File'); + } + + /** + * Backward compatibility for older plugins + */ + function feed_url($url){ + $this->set_feed_url($url); + } +} + +/** + * Fetch an URL using our own HTTPClient + * + * Replaces SimplePie's own class + */ +class FeedParser_File extends SimplePie_File { + var $http; + var $useragent; + var $success = true; + var $headers = array(); + var $body; + var $error; + + /** + * Inititializes the HTTPClient + * + * We ignore all given parameters - they are set in DokuHTTPClient + */ + function FeedParser_File($url, $timeout=10, $redirects=5, + $headers=null, $useragent=null, $force_fsockopen=false) { + $this->http = new DokuHTTPClient(); + $this->success = $this->http->sendRequest($url); + + $this->headers = $this->http->resp_headers; + $this->body = $this->http->resp_body; + $this->error = $this->http->error; + + $this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_FSOCKOPEN; + + return $this->success; + } + + function headers(){ + return $this->headers; + } + + function body(){ + return $this->body; + } + + function close(){ + return true; + } + +} diff --git a/inc/HTTPClient.php b/inc/HTTPClient.php new file mode 100644 index 0000000..6419503 --- /dev/null +++ b/inc/HTTPClient.php @@ -0,0 +1,686 @@ + + */ + + +define('HTTP_NL',"\r\n"); + + +/** + * Adds DokuWiki specific configs to the HTTP client + * + * @author Andreas Goetz + */ +class DokuHTTPClient extends HTTPClient { + + /** + * Constructor. + * + * @author Andreas Gohr + */ + function DokuHTTPClient(){ + global $conf; + + // call parent constructor + $this->HTTPClient(); + + // set some values from the config + $this->proxy_host = $conf['proxy']['host']; + $this->proxy_port = $conf['proxy']['port']; + $this->proxy_user = $conf['proxy']['user']; + $this->proxy_pass = conf_decodeString($conf['proxy']['pass']); + $this->proxy_ssl = $conf['proxy']['ssl']; + $this->proxy_except = $conf['proxy']['except']; + } + + + /** + * Wraps an event around the parent function + * + * @triggers HTTPCLIENT_REQUEST_SEND + * @author Andreas Gohr + */ + function sendRequest($url,$data='',$method='GET'){ + $httpdata = array('url' => $url, + 'data' => $data, + 'method' => $method); + $evt = new Doku_Event('HTTPCLIENT_REQUEST_SEND',$httpdata); + if($evt->advise_before()){ + $url = $httpdata['url']; + $data = $httpdata['data']; + $method = $httpdata['method']; + } + $evt->advise_after(); + unset($evt); + return parent::sendRequest($url,$data,$method); + } + +} + +/** + * This class implements a basic HTTP client + * + * It supports POST and GET, Proxy usage, basic authentication, + * handles cookies and referers. It is based upon the httpclient + * function from the VideoDB project. + * + * @link http://www.splitbrain.org/go/videodb + * @author Andreas Goetz + * @author Andreas Gohr + * @author Tobias Sarnowski + */ +class HTTPClient { + //set these if you like + var $agent; // User agent + var $http; // HTTP version defaults to 1.0 + var $timeout; // read timeout (seconds) + var $cookies; + var $referer; + var $max_redirect; + var $max_bodysize; + var $max_bodysize_abort = true; // if set, abort if the response body is bigger than max_bodysize + var $header_regexp; // if set this RE must match against the headers, else abort + var $headers; + var $debug; + var $start = 0; // for timings + var $keep_alive = true; // keep alive rocks + + // don't set these, read on error + var $error; + var $redirect_count; + + // read these after a successful request + var $status; + var $resp_body; + var $resp_headers; + + // set these to do basic authentication + var $user; + var $pass; + + // set these if you need to use a proxy + var $proxy_host; + var $proxy_port; + var $proxy_user; + var $proxy_pass; + var $proxy_ssl; //boolean set to true if your proxy needs SSL + var $proxy_except; // regexp of URLs to exclude from proxy + + // list of kept alive connections + static $connections = array(); + + // what we use as boundary on multipart/form-data posts + var $boundary = '---DokuWikiHTTPClient--4523452351'; + + /** + * Constructor. + * + * @author Andreas Gohr + */ + function HTTPClient(){ + $this->agent = 'Mozilla/4.0 (compatible; DokuWiki HTTP Client; '.PHP_OS.')'; + $this->timeout = 15; + $this->cookies = array(); + $this->referer = ''; + $this->max_redirect = 3; + $this->redirect_count = 0; + $this->status = 0; + $this->headers = array(); + $this->http = '1.0'; + $this->debug = false; + $this->max_bodysize = 0; + $this->header_regexp= ''; + if(extension_loaded('zlib')) $this->headers['Accept-encoding'] = 'gzip'; + $this->headers['Accept'] = 'text/xml,application/xml,application/xhtml+xml,'. + 'text/html,text/plain,image/png,image/jpeg,image/gif,*/*'; + $this->headers['Accept-Language'] = 'en-us'; + } + + + /** + * Simple function to do a GET request + * + * Returns the wanted page or false on an error; + * + * @param string $url The URL to fetch + * @param bool $sloppy304 Return body on 304 not modified + * @author Andreas Gohr + */ + function get($url,$sloppy304=false){ + if(!$this->sendRequest($url)) return false; + if($this->status == 304 && $sloppy304) return $this->resp_body; + if($this->status < 200 || $this->status > 206) return false; + return $this->resp_body; + } + + /** + * Simple function to do a GET request with given parameters + * + * Returns the wanted page or false on an error. + * + * This is a convenience wrapper around get(). The given parameters + * will be correctly encoded and added to the given base URL. + * + * @param string $url The URL to fetch + * @param array $data Associative array of parameters + * @param bool $sloppy304 Return body on 304 not modified + * @author Andreas Gohr + */ + function dget($url,$data,$sloppy304=false){ + if(strpos($url,'?')){ + $url .= '&'; + }else{ + $url .= '?'; + } + $url .= $this->_postEncode($data); + return $this->get($url,$sloppy304); + } + + /** + * Simple function to do a POST request + * + * Returns the resulting page or false on an error; + * + * @author Andreas Gohr + */ + function post($url,$data){ + if(!$this->sendRequest($url,$data,'POST')) return false; + if($this->status < 200 || $this->status > 206) return false; + return $this->resp_body; + } + + /** + * Send an HTTP request + * + * This method handles the whole HTTP communication. It respects set proxy settings, + * builds the request headers, follows redirects and parses the response. + * + * Post data should be passed as associative array. When passed as string it will be + * sent as is. You will need to setup your own Content-Type header then. + * + * @param string $url - the complete URL + * @param mixed $data - the post data either as array or raw data + * @param string $method - HTTP Method usually GET or POST. + * @return bool - true on success + * @author Andreas Goetz + * @author Andreas Gohr + */ + function sendRequest($url,$data='',$method='GET'){ + $this->start = $this->_time(); + $this->error = ''; + $this->status = 0; + + // don't accept gzip if truncated bodies might occur + if($this->max_bodysize && + !$this->max_bodysize_abort && + $this->headers['Accept-encoding'] == 'gzip'){ + unset($this->headers['Accept-encoding']); + } + + // parse URL into bits + $uri = parse_url($url); + $server = $uri['host']; + $path = $uri['path']; + if(empty($path)) $path = '/'; + if(!empty($uri['query'])) $path .= '?'.$uri['query']; + if(isset($uri['port']) && !empty($uri['port'])) $port = $uri['port']; + if(isset($uri['user'])) $this->user = $uri['user']; + if(isset($uri['pass'])) $this->pass = $uri['pass']; + + // proxy setup + if($this->proxy_host && (!$this->proxy_except || !preg_match('/'.$this->proxy_except.'/i',$url)) ){ + $request_url = $url; + $server = $this->proxy_host; + $port = $this->proxy_port; + if (empty($port)) $port = 8080; + }else{ + $request_url = $path; + $server = $server; + if (!isset($port)) $port = ($uri['scheme'] == 'https') ? 443 : 80; + } + + // add SSL stream prefix if needed - needs SSL support in PHP + if($port == 443 || $this->proxy_ssl) $server = 'ssl://'.$server; + + // prepare headers + $headers = $this->headers; + $headers['Host'] = $uri['host']; + if($uri['port']) $headers['Host'].= ':'.$uri['port']; + $headers['User-Agent'] = $this->agent; + $headers['Referer'] = $this->referer; + if ($this->keep_alive) { + $headers['Connection'] = 'Keep-Alive'; + } else { + $headers['Connection'] = 'Close'; + } + if($method == 'POST'){ + if(is_array($data)){ + if($headers['Content-Type'] == 'multipart/form-data'){ + $headers['Content-Type'] = 'multipart/form-data; boundary='.$this->boundary; + $data = $this->_postMultipartEncode($data); + }else{ + $headers['Content-Type'] = 'application/x-www-form-urlencoded'; + $data = $this->_postEncode($data); + } + } + $headers['Content-Length'] = strlen($data); + $rmethod = 'POST'; + }elseif($method == 'GET'){ + $data = ''; //no data allowed on GET requests + } + if($this->user) { + $headers['Authorization'] = 'Basic '.base64_encode($this->user.':'.$this->pass); + } + if($this->proxy_user) { + $headers['Proxy-Authorization'] = 'Basic '.base64_encode($this->proxy_user.':'.$this->proxy_pass); + } + + // stop time + $start = time(); + + // already connected? + $connectionId = $this->_uniqueConnectionId($server,$port); + $this->_debug('connection pool', $this->connections); + $socket = null; + if (isset($this->connections[$connectionId])) { + $this->_debug('reusing connection', $connectionId); + $socket = $this->connections[$connectionId]; + } + if (is_null($socket) || feof($socket)) { + $this->_debug('opening connection', $connectionId); + // open socket + $socket = @fsockopen($server,$port,$errno, $errstr, $this->timeout); + if (!$socket){ + $this->status = -100; + $this->error = "Could not connect to $server:$port\n$errstr ($errno)"; + return false; + } + + // keep alive? + if ($this->keep_alive) { + $this->connections[$connectionId] = $socket; + } else { + unset($this->connections[$connectionId]); + } + } + + //set blocking + stream_set_blocking($socket,1); + + // build request + $request = "$method $request_url HTTP/".$this->http.HTTP_NL; + $request .= $this->_buildHeaders($headers); + $request .= $this->_getCookies(); + $request .= HTTP_NL; + $request .= $data; + + $this->_debug('request',$request); + + // select parameters + $sel_r = null; + $sel_w = array($socket); + $sel_e = null; + + // send request + $towrite = strlen($request); + $written = 0; + while($written < $towrite){ + // check timeout + if(time()-$start > $this->timeout){ + $this->status = -100; + $this->error = sprintf('Timeout while sending request (%.3fs)',$this->_time() - $this->start); + unset($this->connections[$connectionId]); + return false; + } + + // wait for stream ready or timeout (1sec) + if(@stream_select($sel_r,$sel_w,$sel_e,1) === false){ + usleep(1000); + continue; + } + + // write to stream + $ret = fwrite($socket, substr($request,$written,4096)); + if($ret === false){ + $this->status = -100; + $this->error = 'Failed writing to socket'; + unset($this->connections[$connectionId]); + return false; + } + $written += $ret; + } + + // continue non-blocking + stream_set_blocking($socket,0); + + // read headers from socket + $r_headers = ''; + do{ + if(time()-$start > $this->timeout){ + $this->status = -100; + $this->error = sprintf('Timeout while reading headers (%.3fs)',$this->_time() - $this->start); + unset($this->connections[$connectionId]); + return false; + } + if(feof($socket)){ + $this->error = 'Premature End of File (socket)'; + unset($this->connections[$connectionId]); + return false; + } + usleep(1000); + $r_headers .= fgets($socket,1024); + }while(!preg_match('/\r?\n\r?\n$/',$r_headers)); + + $this->_debug('response headers',$r_headers); + + // check if expected body size exceeds allowance + if($this->max_bodysize && preg_match('/\r?\nContent-Length:\s*(\d+)\r?\n/i',$r_headers,$match)){ + if($match[1] > $this->max_bodysize){ + $this->error = 'Reported content length exceeds allowed response size'; + if ($this->max_bodysize_abort) + unset($this->connections[$connectionId]); + return false; + } + } + + // get Status + if (!preg_match('/^HTTP\/(\d\.\d)\s*(\d+).*?\n/', $r_headers, $m)) { + $this->error = 'Server returned bad answer'; + unset($this->connections[$connectionId]); + return false; + } + $this->status = $m[2]; + + // handle headers and cookies + $this->resp_headers = $this->_parseHeaders($r_headers); + if(isset($this->resp_headers['set-cookie'])){ + foreach ((array) $this->resp_headers['set-cookie'] as $cookie){ + list($cookie) = explode(';',$cookie,2); + list($key,$val) = explode('=',$cookie,2); + $key = trim($key); + if($val == 'deleted'){ + if(isset($this->cookies[$key])){ + unset($this->cookies[$key]); + } + }elseif($key){ + $this->cookies[$key] = $val; + } + } + } + + $this->_debug('Object headers',$this->resp_headers); + + // check server status code to follow redirect + if($this->status == 301 || $this->status == 302 ){ + // close the connection because we don't handle content retrieval here + // that's the easiest way to clean up the connection + fclose($socket); + unset($this->connections[$connectionId]); + + if (empty($this->resp_headers['location'])){ + $this->error = 'Redirect but no Location Header found'; + return false; + }elseif($this->redirect_count == $this->max_redirect){ + $this->error = 'Maximum number of redirects exceeded'; + return false; + }else{ + $this->redirect_count++; + $this->referer = $url; + // handle non-RFC-compliant relative redirects + if (!preg_match('/^http/i', $this->resp_headers['location'])){ + if($this->resp_headers['location'][0] != '/'){ + $this->resp_headers['location'] = $uri['scheme'].'://'.$uri['host'].':'.$uri['port']. + dirname($uri['path']).'/'.$this->resp_headers['location']; + }else{ + $this->resp_headers['location'] = $uri['scheme'].'://'.$uri['host'].':'.$uri['port']. + $this->resp_headers['location']; + } + } + // perform redirected request, always via GET (required by RFC) + return $this->sendRequest($this->resp_headers['location'],array(),'GET'); + } + } + + // check if headers are as expected + if($this->header_regexp && !preg_match($this->header_regexp,$r_headers)){ + $this->error = 'The received headers did not match the given regexp'; + unset($this->connections[$connectionId]); + return false; + } + + //read body (with chunked encoding if needed) + $r_body = ''; + if(preg_match('/transfer\-(en)?coding:\s*chunked\r\n/i',$r_headers)){ + do { + unset($chunk_size); + do { + if(feof($socket)){ + $this->error = 'Premature End of File (socket)'; + unset($this->connections[$connectionId]); + return false; + } + if(time()-$start > $this->timeout){ + $this->status = -100; + $this->error = sprintf('Timeout while reading chunk (%.3fs)',$this->_time() - $this->start); + unset($this->connections[$connectionId]); + return false; + } + $byte = fread($socket,1); + $chunk_size .= $byte; + } while (preg_match('/[a-zA-Z0-9]/',$byte)); // read chunksize including \r + + $byte = fread($socket,1); // readtrailing \n + $chunk_size = hexdec($chunk_size); + if ($chunk_size) { + $this_chunk = fread($socket,$chunk_size); + $r_body .= $this_chunk; + $byte = fread($socket,2); // read trailing \r\n + } + + if($this->max_bodysize && strlen($r_body) > $this->max_bodysize){ + $this->error = 'Allowed response size exceeded'; + if ($this->max_bodysize_abort){ + unset($this->connections[$connectionId]); + return false; + } else { + break; + } + } + } while ($chunk_size); + }else{ + // read entire socket + while (!feof($socket)) { + if(time()-$start > $this->timeout){ + $this->status = -100; + $this->error = sprintf('Timeout while reading response (%.3fs)',$this->_time() - $this->start); + unset($this->connections[$connectionId]); + return false; + } + $r_body .= fread($socket,4096); + $r_size = strlen($r_body); + if($this->max_bodysize && $r_size > $this->max_bodysize){ + $this->error = 'Allowed response size exceeded'; + if ($this->max_bodysize_abort) { + unset($this->connections[$connectionId]); + return false; + } else { + break; + } + } + if(isset($this->resp_headers['content-length']) && + !isset($this->resp_headers['transfer-encoding']) && + $this->resp_headers['content-length'] == $r_size){ + // we read the content-length, finish here + break; + } + } + } + + if (!$this->keep_alive || + (isset($this->resp_headers['connection']) && $this->resp_headers['connection'] == 'Close')) { + // close socket + $status = socket_get_status($socket); + fclose($socket); + unset($this->connections[$connectionId]); + } + + // decode gzip if needed + if(isset($this->resp_headers['content-encoding']) && + $this->resp_headers['content-encoding'] == 'gzip' && + strlen($r_body) > 10 && substr($r_body,0,3)=="\x1f\x8b\x08"){ + $this->resp_body = @gzinflate(substr($r_body, 10)); + if($this->resp_body === false){ + $this->error = 'Failed to decompress gzip encoded content'; + $this->resp_body = $r_body; + } + }else{ + $this->resp_body = $r_body; + } + + $this->_debug('response body',$this->resp_body); + $this->redirect_count = 0; + return true; + } + + /** + * print debug info + * + * @author Andreas Gohr + */ + function _debug($info,$var=null){ + if(!$this->debug) return; + print ''.$info.' '.($this->_time() - $this->start).'s
'; + if(!is_null($var)){ + ob_start(); + print_r($var); + $content = htmlspecialchars(ob_get_contents()); + ob_end_clean(); + print '
'.$content.'
'; + } + } + + /** + * Return current timestamp in microsecond resolution + */ + function _time(){ + list($usec, $sec) = explode(" ", microtime()); + return ((float)$usec + (float)$sec); + } + + /** + * convert given header string to Header array + * + * All Keys are lowercased. + * + * @author Andreas Gohr + */ + function _parseHeaders($string){ + $headers = array(); + if (!preg_match_all('/^\s*([\w-]+)\s*:\s*([\S \t]+)\s*$/m', $string, + $matches, PREG_SET_ORDER)) { + return $headers; + } + foreach($matches as $match){ + list(, $key, $val) = $match; + $key = strtolower($key); + if(isset($headers[$key])){ + if(is_array($headers[$key])){ + $headers[$key][] = $val; + }else{ + $headers[$key] = array($headers[$key],$val); + } + }else{ + $headers[$key] = $val; + } + } + return $headers; + } + + /** + * convert given header array to header string + * + * @author Andreas Gohr + */ + function _buildHeaders($headers){ + $string = ''; + foreach($headers as $key => $value){ + if(empty($value)) continue; + $string .= $key.': '.$value.HTTP_NL; + } + return $string; + } + + /** + * get cookies as http header string + * + * @author Andreas Goetz + */ + function _getCookies(){ + $headers = ''; + foreach ($this->cookies as $key => $val){ + $headers .= "$key=$val; "; + } + $headers = substr($headers, 0, -2); + if ($headers !== '') $headers = "Cookie: $headers".HTTP_NL; + return $headers; + } + + /** + * Encode data for posting + * + * @author Andreas Gohr + */ + function _postEncode($data){ + $url = ''; + foreach($data as $key => $val){ + if($url) $url .= '&'; + $url .= urlencode($key).'='.urlencode($val); + } + return $url; + } + + /** + * Encode data for posting using multipart encoding + * + * @fixme use of urlencode might be wrong here + * @author Andreas Gohr + */ + function _postMultipartEncode($data){ + $boundary = '--'.$this->boundary; + $out = ''; + foreach($data as $key => $val){ + $out .= $boundary.HTTP_NL; + if(!is_array($val)){ + $out .= 'Content-Disposition: form-data; name="'.urlencode($key).'"'.HTTP_NL; + $out .= HTTP_NL; // end of headers + $out .= $val; + $out .= HTTP_NL; + }else{ + $out .= 'Content-Disposition: form-data; name="'.urlencode($key).'"'; + if($val['filename']) $out .= '; filename="'.urlencode($val['filename']).'"'; + $out .= HTTP_NL; + if($val['mimetype']) $out .= 'Content-Type: '.$val['mimetype'].HTTP_NL; + $out .= HTTP_NL; // end of headers + $out .= $val['body']; + $out .= HTTP_NL; + } + } + $out .= "$boundary--".HTTP_NL; + return $out; + } + + /** + * Generates a unique identifier for a connection. + * + * @return string unique identifier + */ + function _uniqueConnectionId($server, $port) { + return "$server:$port"; + } +} + +//Setup VIM: ex: et ts=4 : diff --git a/inc/IXR_Library.php b/inc/IXR_Library.php new file mode 100644 index 0000000..c8255e6 --- /dev/null +++ b/inc/IXR_Library.php @@ -0,0 +1,816 @@ + + */ + + +class IXR_Value { + var $data; + var $type; + function IXR_Value ($data, $type = false) { + $this->data = $data; + if (!$type) { + $type = $this->calculateType(); + } + $this->type = $type; + if ($type == 'struct') { + /* Turn all the values in the array in to new IXR_Value objects */ + foreach ($this->data as $key => $value) { + $this->data[$key] = new IXR_Value($value); + } + } + if ($type == 'array') { + for ($i = 0, $j = count($this->data); $i < $j; $i++) { + $this->data[$i] = new IXR_Value($this->data[$i]); + } + } + } + function calculateType() { + if ($this->data === true || $this->data === false) { + return 'boolean'; + } + if (is_integer($this->data)) { + return 'int'; + } + if (is_double($this->data)) { + return 'double'; + } + // Deal with IXR object types base64 and date + if (is_object($this->data) && is_a($this->data, 'IXR_Date')) { + return 'date'; + } + if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) { + return 'base64'; + } + // If it is a normal PHP object convert it in to a struct + if (is_object($this->data)) { + + $this->data = get_object_vars($this->data); + return 'struct'; + } + if (!is_array($this->data)) { + return 'string'; + } + /* We have an array - is it an array or a struct ? */ + if ($this->isStruct($this->data)) { + return 'struct'; + } else { + return 'array'; + } + } + function getXml() { + /* Return XML for this value */ + switch ($this->type) { + case 'boolean': + return ''.(($this->data) ? '1' : '0').''; + break; + case 'int': + return ''.$this->data.''; + break; + case 'double': + return ''.$this->data.''; + break; + case 'string': + return ''.htmlspecialchars($this->data).''; + break; + case 'array': + $return = ''."\n"; + foreach ($this->data as $item) { + $return .= ' '.$item->getXml()."\n"; + } + $return .= ''; + return $return; + break; + case 'struct': + $return = ''."\n"; + foreach ($this->data as $name => $value) { + $return .= " $name"; + $return .= $value->getXml()."\n"; + } + $return .= ''; + return $return; + break; + case 'date': + case 'base64': + return $this->data->getXml(); + break; + } + return false; + } + function isStruct($array) { + /* Nasty function to check if an array is a struct or not */ + $expected = 0; + foreach ($array as $key => $value) { + if ((string)$key != (string)$expected) { + return true; + } + $expected++; + } + return false; + } +} + + +class IXR_Message { + var $message; + var $messageType; // methodCall / methodResponse / fault + var $faultCode; + var $faultString; + var $methodName; + var $params; + // Current variable stacks + var $_arraystructs = array(); // The stack used to keep track of the current array/struct + var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array + var $_currentStructName = array(); // A stack as well + var $_param; + var $_value; + var $_currentTag; + var $_currentTagContents; + var $_lastseen; + // The XML parser + var $_parser; + function IXR_Message ($message) { + $this->message = $message; + } + function parse() { + // first remove the XML declaration + $this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message); + // workaround for a bug in PHP/libxml2, see http://bugs.php.net/bug.php?id=45996 + $this->message = str_replace('<', '<', $this->message); + $this->message = str_replace('>', '>', $this->message); + $this->message = str_replace('&', '&', $this->message); + $this->message = str_replace(''', ''', $this->message); + $this->message = str_replace('"', '"', $this->message); + $this->message = str_replace("\x0b", ' ', $this->message); //vertical tab + if (trim($this->message) == '') { + return false; + } + $this->_parser = xml_parser_create(); + // Set XML parser to take the case of tags in to account + xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); + // Set XML parser callback functions + xml_set_object($this->_parser, $this); + xml_set_element_handler($this->_parser, 'tag_open', 'tag_close'); + xml_set_character_data_handler($this->_parser, 'cdata'); + if (!xml_parse($this->_parser, $this->message)) { + /* die(sprintf('XML error: %s at line %d', + xml_error_string(xml_get_error_code($this->_parser)), + xml_get_current_line_number($this->_parser))); */ + return false; + } + xml_parser_free($this->_parser); + // Grab the error messages, if any + if ($this->messageType == 'fault') { + $this->faultCode = $this->params[0]['faultCode']; + $this->faultString = $this->params[0]['faultString']; + } + return true; + } + function tag_open($parser, $tag, $attr) { + $this->currentTag = $tag; + $this->_currentTagContents = ''; + switch($tag) { + case 'methodCall': + case 'methodResponse': + case 'fault': + $this->messageType = $tag; + break; + /* Deal with stacks of arrays and structs */ + case 'data': // data is to all intents and puposes more interesting than array + $this->_arraystructstypes[] = 'array'; + $this->_arraystructs[] = array(); + break; + case 'struct': + $this->_arraystructstypes[] = 'struct'; + $this->_arraystructs[] = array(); + break; + } + $this->_lastseen = $tag; + } + function cdata($parser, $cdata) { + $this->_currentTagContents .= $cdata; + } + function tag_close($parser, $tag) { + $valueFlag = false; + switch($tag) { + case 'int': + case 'i4': + $value = (int)trim($this->_currentTagContents); + $this->_currentTagContents = ''; + $valueFlag = true; + break; + case 'double': + $value = (double)trim($this->_currentTagContents); + $this->_currentTagContents = ''; + $valueFlag = true; + break; + case 'string': + $value = (string)$this->_currentTagContents; + $this->_currentTagContents = ''; + $valueFlag = true; + break; + case 'dateTime.iso8601': + $value = new IXR_Date(trim($this->_currentTagContents)); + // $value = $iso->getTimestamp(); + $this->_currentTagContents = ''; + $valueFlag = true; + break; + case 'value': + // "If no type is indicated, the type is string." + if($this->_lastseen == 'value'){ + $value = (string)$this->_currentTagContents; + $this->_currentTagContents = ''; + $valueFlag = true; + } + break; + case 'boolean': + $value = (boolean)trim($this->_currentTagContents); + $this->_currentTagContents = ''; + $valueFlag = true; + break; + case 'base64': + $value = base64_decode($this->_currentTagContents); + $this->_currentTagContents = ''; + $valueFlag = true; + break; + /* Deal with stacks of arrays and structs */ + case 'data': + case 'struct': + $value = array_pop($this->_arraystructs); + array_pop($this->_arraystructstypes); + $valueFlag = true; + break; + case 'member': + array_pop($this->_currentStructName); + break; + case 'name': + $this->_currentStructName[] = trim($this->_currentTagContents); + $this->_currentTagContents = ''; + break; + case 'methodName': + $this->methodName = trim($this->_currentTagContents); + $this->_currentTagContents = ''; + break; + } + if ($valueFlag) { + /* + if (!is_array($value) && !is_object($value)) { + $value = trim($value); + } + */ + if (count($this->_arraystructs) > 0) { + // Add value to struct or array + if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') { + // Add to struct + $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value; + } else { + // Add to array + $this->_arraystructs[count($this->_arraystructs)-1][] = $value; + } + } else { + // Just add as a paramater + $this->params[] = $value; + } + } + $this->_lastseen = $tag; + } +} + + +class IXR_Server { + var $data; + var $callbacks = array(); + var $message; + var $capabilities; + function IXR_Server($callbacks = false, $data = false) { + $this->setCapabilities(); + if ($callbacks) { + $this->callbacks = $callbacks; + } + $this->setCallbacks(); + $this->serve($data); + } + function serve($data = false) { + if (!$data) { + global $HTTP_RAW_POST_DATA; + if (!$HTTP_RAW_POST_DATA) { + die('XML-RPC server accepts POST requests only.'); + } + $data = $HTTP_RAW_POST_DATA; + } + $this->message = new IXR_Message($data); + if (!$this->message->parse()) { + $this->error(-32700, 'parse error. not well formed'); + } + if ($this->message->messageType != 'methodCall') { + $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall'); + } + $result = $this->call($this->message->methodName, $this->message->params); + // Is the result an error? + if (is_a($result, 'IXR_Error')) { + $this->error($result); + } + // Encode the result + $r = new IXR_Value($result); + $resultxml = $r->getXml(); + // Create the XML + $xml = << + + + + $resultxml + + + + + +EOD; + // Send it + $this->output($xml); + } + function call($methodname, $args) { + if (!$this->hasMethod($methodname)) { + return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.'); + } + $method = $this->callbacks[$methodname]; + // Perform the callback and send the response + + # Removed for DokuWiki to have a more consistent interface + # if (count($args) == 1) { + # // If only one paramater just send that instead of the whole array + # $args = $args[0]; + # } + + # Adjusted for DokuWiki to use call_user_func_array + + // args need to be an array + $args = (array) $args; + + // Are we dealing with a function or a method? + if (substr($method, 0, 5) == 'this:') { + // It's a class method - check it exists + $method = substr($method, 5); + if (!method_exists($this, $method)) { + return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.'); + } + // Call the method + #$result = $this->$method($args); + $result = call_user_func_array(array(&$this,$method),$args); + } elseif (substr($method, 0, 7) == 'plugin:') { + list($pluginname, $callback) = explode(':', substr($method, 7), 2); + if(!plugin_isdisabled($pluginname)) { + $plugin = plugin_load('action', $pluginname); + return call_user_func_array(array($plugin, $callback), $args); + } else { + return new IXR_Error(-99999, 'server error'); + } + } else { + // It's a function - does it exist? + if (!function_exists($method)) { + return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.'); + } + // Call the function + #$result = $method($args); + $result = call_user_func_array($method,$args); + } + return $result; + } + + function error($error, $message = false) { + // Accepts either an error object or an error code and message + if ($message && !is_object($error)) { + $error = new IXR_Error($error, $message); + } + $this->output($error->getXml()); + } + function output($xml) { + header('Content-Type: text/xml; charset=utf-8'); + echo '', "\n", $xml; + exit; + } + function hasMethod($method) { + return in_array($method, array_keys($this->callbacks)); + } + function setCapabilities() { + // Initialises capabilities array + $this->capabilities = array( + 'xmlrpc' => array( + 'specUrl' => 'http://www.xmlrpc.com/spec', + 'specVersion' => 1 + ), + 'faults_interop' => array( + 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php', + 'specVersion' => 20010516 + ), + 'system.multicall' => array( + 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', + 'specVersion' => 1 + ), + ); + } + function getCapabilities() { + return $this->capabilities; + } + function setCallbacks() { + $this->callbacks['system.getCapabilities'] = 'this:getCapabilities'; + $this->callbacks['system.listMethods'] = 'this:listMethods'; + $this->callbacks['system.multicall'] = 'this:multiCall'; + } + function listMethods() { + // Returns a list of methods - uses array_reverse to ensure user defined + // methods are listed before server defined methods + return array_reverse(array_keys($this->callbacks)); + } + function multiCall($methodcalls) { + // See http://www.xmlrpc.com/discuss/msgReader$1208 + $return = array(); + foreach ($methodcalls as $call) { + $method = $call['methodName']; + $params = $call['params']; + if ($method == 'system.multicall') { + $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden'); + } else { + $result = $this->call($method, $params); + } + if (is_a($result, 'IXR_Error')) { + $return[] = array( + 'faultCode' => $result->code, + 'faultString' => $result->message + ); + } else { + $return[] = array($result); + } + } + return $return; + } +} + +class IXR_Request { + var $method; + var $args; + var $xml; + function IXR_Request($method, $args) { + $this->method = $method; + $this->args = $args; + $this->xml = << + +{$this->method} + + +EOD; + foreach ($this->args as $arg) { + $this->xml .= ''; + $v = new IXR_Value($arg); + $this->xml .= $v->getXml(); + $this->xml .= "\n"; + } + $this->xml .= ''; + } + function getLength() { + return strlen($this->xml); + } + function getXml() { + return $this->xml; + } +} + +/** + * Changed for DokuWiki to use DokuHTTPClient + * + * This should be compatible to the original class, but uses DokuWiki's + * HTTP client library which will respect proxy settings + * + * Because the XMLRPC client is not used in DokuWiki currently this is completely + * untested + */ +class IXR_Client extends DokuHTTPClient { + var $posturl = ''; + var $message = false; + var $xmlerror = false; + + function IXR_Client($server, $path = false, $port = 80) { + $this->DokuHTTPClient(); + if (!$path) { + // Assume we have been given a URL instead + $this->posturl = $server; + }else{ + $this->posturl = 'http://'.$server.':'.$port.$path; + } + } + + function query() { + $args = func_get_args(); + $method = array_shift($args); + $request = new IXR_Request($method, $args); + $xml = $request->getXml(); + + $this->headers['Content-Type'] = 'text/xml'; + if(!$this->sendRequest($this->posturl,$xml,'POST')){ + $this->xmlerror = new IXR_Error(-32300, 'transport error - '.$this->error); + return false; + } + + // Check HTTP Response code + if($this->status < 200 || $this->status > 206){ + $this->xmlerror = new IXR_Error(-32300, 'transport error - HTTP status '.$this->status); + return false; + } + + // Now parse what we've got back + $this->message = new IXR_Message($this->resp_body); + if (!$this->message->parse()) { + // XML error + $this->xmlerror = new IXR_Error(-32700, 'parse error. not well formed'); + return false; + } + // Is the message a fault? + if ($this->message->messageType == 'fault') { + $this->xmlerror = new IXR_Error($this->message->faultCode, $this->message->faultString); + return false; + } + // Message must be OK + return true; + } + function getResponse() { + // methodResponses can only have one param - return that + return $this->message->params[0]; + } + function isError() { + return (is_object($this->xmlerror)); + } + function getErrorCode() { + return $this->xmlerror->code; + } + function getErrorMessage() { + return $this->xmlerror->message; + } +} + + +class IXR_Error { + var $code; + var $message; + function IXR_Error($code, $message) { + $this->code = $code; + $this->message = $message; + } + function getXml() { + $xml = << + + + + + faultCode + {$this->code} + + + faultString + {$this->message} + + + + + + +EOD; + return $xml; + } +} + + +class IXR_Date { + var $year; + var $month; + var $day; + var $hour; + var $minute; + var $second; + function IXR_Date($time) { + // $time can be a PHP timestamp or an ISO one + if (is_numeric($time)) { + $this->parseTimestamp($time); + } else { + $this->parseIso($time); + } + } + function parseTimestamp($timestamp) { + $this->year = gmdate('Y', $timestamp); + $this->month = gmdate('m', $timestamp); + $this->day = gmdate('d', $timestamp); + $this->hour = gmdate('H', $timestamp); + $this->minute = gmdate('i', $timestamp); + $this->second = gmdate('s', $timestamp); + } + function parseIso($iso) { + if(preg_match('/^(\d\d\d\d)-?(\d\d)-?(\d\d)([T ](\d\d):(\d\d)(:(\d\d))?)?/',$iso,$match)){ + $this->year = (int) $match[1]; + $this->month = (int) $match[2]; + $this->day = (int) $match[3]; + $this->hour = (int) $match[5]; + $this->minute = (int) $match[6]; + $this->second = (int) $match[8]; + } + } + function getIso() { + return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second; + } + function getXml() { + return ''.$this->getIso().''; + } + function getTimestamp() { + return gmmktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year); + } +} + + +class IXR_Base64 { + var $data; + function IXR_Base64($data) { + $this->data = $data; + } + function getXml() { + return ''.base64_encode($this->data).''; + } +} + + +class IXR_IntrospectionServer extends IXR_Server { + var $signatures; + var $help; + function IXR_IntrospectionServer() { + $this->setCallbacks(); + $this->setCapabilities(); + $this->capabilities['introspection'] = array( + 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html', + 'specVersion' => 1 + ); + $this->addCallback( + 'system.methodSignature', + 'this:methodSignature', + array('array', 'string'), + 'Returns an array describing the return type and required parameters of a method' + ); + $this->addCallback( + 'system.getCapabilities', + 'this:getCapabilities', + array('struct'), + 'Returns a struct describing the XML-RPC specifications supported by this server' + ); + $this->addCallback( + 'system.listMethods', + 'this:listMethods', + array('array'), + 'Returns an array of available methods on this server' + ); + $this->addCallback( + 'system.methodHelp', + 'this:methodHelp', + array('string', 'string'), + 'Returns a documentation string for the specified method' + ); + } + function addCallback($method, $callback, $args, $help) { + $this->callbacks[$method] = $callback; + $this->signatures[$method] = $args; + $this->help[$method] = $help; + } + function call($methodname, $args) { + // Make sure it's in an array + if ($args && !is_array($args)) { + $args = array($args); + } + // Over-rides default call method, adds signature check + if (!$this->hasMethod($methodname)) { + return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.'); + } + $method = $this->callbacks[$methodname]; + $signature = $this->signatures[$methodname]; + $returnType = array_shift($signature); + // Check the number of arguments. Check only, if the minimum count of parameters is specified. More parameters are possible. + // This is a hack to allow optional parameters... + if (count($args) < count($signature)) { + // print 'Num of args: '.count($args).' Num in signature: '.count($signature); + return new IXR_Error(-32602, 'server error. wrong number of method parameters'); + } + // Check the argument types + $ok = true; + $argsbackup = $args; + for ($i = 0, $j = count($args); $i < $j; $i++) { + $arg = array_shift($args); + $type = array_shift($signature); + switch ($type) { + case 'int': + case 'i4': + if (is_array($arg) || !is_int($arg)) { + $ok = false; + } + break; + case 'base64': + case 'string': + if (!is_string($arg)) { + $ok = false; + } + break; + case 'boolean': + if ($arg !== false && $arg !== true) { + $ok = false; + } + break; + case 'float': + case 'double': + if (!is_float($arg)) { + $ok = false; + } + break; + case 'date': + case 'dateTime.iso8601': + if (!is_a($arg, 'IXR_Date')) { + $ok = false; + } + break; + } + if (!$ok) { + return new IXR_Error(-32602, 'server error. invalid method parameters'); + } + } + // It passed the test - run the "real" method call + return parent::call($methodname, $argsbackup); + } + function methodSignature($method) { + if (!$this->hasMethod($method)) { + return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.'); + } + // We should be returning an array of types + $types = $this->signatures[$method]; + $return = array(); + foreach ($types as $type) { + switch ($type) { + case 'string': + $return[] = 'string'; + break; + case 'int': + case 'i4': + $return[] = 42; + break; + case 'double': + $return[] = 3.1415; + break; + case 'dateTime.iso8601': + $return[] = new IXR_Date(time()); + break; + case 'boolean': + $return[] = true; + break; + case 'base64': + $return[] = new IXR_Base64('base64'); + break; + case 'array': + $return[] = array('array'); + break; + case 'struct': + $return[] = array('struct' => 'struct'); + break; + } + } + return $return; + } + function methodHelp($method) { + return $this->help[$method]; + } +} + + +class IXR_ClientMulticall extends IXR_Client { + var $calls = array(); + function IXR_ClientMulticall($server, $path = false, $port = 80) { + parent::IXR_Client($server, $path, $port); + //$this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)'; + } + function addCall() { + $args = func_get_args(); + $methodName = array_shift($args); + $struct = array( + 'methodName' => $methodName, + 'params' => $args + ); + $this->calls[] = $struct; + } + function query() { + // Prepare multicall, then call the parent::query() method + return parent::query('system.multicall', $this->calls); + } +} + diff --git a/inc/JSON.php b/inc/JSON.php new file mode 100644 index 0000000..2dea440 --- /dev/null +++ b/inc/JSON.php @@ -0,0 +1,661 @@ + + * @author Matt Knapp + * @author Brett Stimmerman + * @copyright 2005 Michal Migurski + * @license http://www.freebsd.org/copyright/freebsd-license.html + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + +// for DokuWiki +if(!defined('DOKU_INC')) die('meh.'); + +/** + * Marker constant for JSON::decode(), used to flag stack state + */ +define('JSON_SLICE', 1); + +/** + * Marker constant for JSON::decode(), used to flag stack state + */ +define('JSON_IN_STR', 2); + +/** + * Marker constant for JSON::decode(), used to flag stack state + */ +define('JSON_IN_ARR', 4); + +/** + * Marker constant for JSON::decode(), used to flag stack state + */ +define('JSON_IN_OBJ', 8); + +/** + * Marker constant for JSON::decode(), used to flag stack state + */ +define('JSON_IN_CMT', 16); + +/** + * Behavior switch for JSON::decode() + */ +define('JSON_LOOSE_TYPE', 10); + +/** + * Behavior switch for JSON::decode() + */ +define('JSON_STRICT_TYPE', 11); + +/** + * Converts to and from JSON format. + * + * @category + * @package + * @author Michal Migurski + * @author Matt Knapp + * @author Brett Stimmerman + * @copyright 2005 Michal Migurski + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version + * @link + * @see + * @since + * @deprecated + */ +class JSON { + + /** + * Disables the use of PHP5's native json_decode() + * + * You shouldn't change this usually because the native function is much + * faster. However, this non-native will also parse slightly broken JSON + * which might be handy when talking to a non-conform endpoint + */ + public $skipnative = false; + + /** + * constructs a new JSON instance + * + * @param int $use object behavior: when encoding or decoding, + * be loose or strict about object/array usage + * + * possible values: + * JSON_STRICT_TYPE - strict typing, default + * "{...}" syntax creates objects in decode. + * JSON_LOOSE_TYPE - loose typing + * "{...}" syntax creates associative arrays in decode. + */ + function JSON($use=JSON_STRICT_TYPE) { + $this->use = $use; + } + + /** + * encodes an arbitrary variable into JSON format + * If available the native PHP JSON implementation is used. + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return string JSON string representation of input var + * @access public + */ + function encode($var) { + if (function_exists('json_encode')) return json_encode($var); + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return sprintf('%d', $var); + + case 'double': + case 'float': + return sprintf('%f', $var); + + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch ($ord_var_c) { + case 0x08: + $ascii .= '\b'; + break; + case 0x09: + $ascii .= '\t'; + break; + case 0x0A: + $ascii .= '\n'; + break; + case 0x0C: + $ascii .= '\f'; + break; + case 0x0D: + $ascii .= '\r'; + break; + + case 0x22: + case 0x2F: + case 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c+1})); + $c+=1; + //$utf16 = mb_convert_encoding($char, 'UTF-16', 'UTF-8'); + $utf16 = utf8_to_utf16be($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c+1}), + ord($var{$c+2})); + $c+=2; + //$utf16 = mb_convert_encoding($char, 'UTF-16', 'UTF-8'); + $utf16 = utf8_to_utf16be($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c+1}), + ord($var{$c+2}), + ord($var{$c+3})); + $c+=3; + //$utf16 = mb_convert_encoding($char, 'UTF-16', 'UTF-8'); + $utf16 = utf8_to_utf16be($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c+1}), + ord($var{$c+2}), + ord($var{$c+3}), + ord($var{$c+4})); + $c+=4; + //$utf16 = mb_convert_encoding($char, 'UTF-16', 'UTF-8'); + $utf16 = utf8_to_utf16be($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c+1}), + ord($var{$c+2}), + ord($var{$c+3}), + ord($var{$c+4}), + ord($var{$c+5})); + $c+=5; + //$utf16 = mb_convert_encoding($char, 'UTF-16', 'UTF-8'); + $utf16 = utf8_to_utf16be($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + } + } + + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, count($var) - 1))) { + return sprintf('{%s}', join(',', array_map(array($this, 'name_value'), + array_keys($var), + array_values($var)))); + } + + // treat it like a regular array + return sprintf('[%s]', join(',', array_map(array($this, 'encode'), $var))); + + case 'object': + $vars = get_object_vars($var); + return sprintf('{%s}', join(',', array_map(array($this, 'name_value'), + array_keys($vars), + array_values($vars)))); + + default: + return ''; + } + } + + /** + * encodes an arbitrary variable into JSON format, alias for encode() + */ + function enc($var) { + return $this->encode($var); + } + + /** function name_value + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) { + return (sprintf("%s:%s", $this->encode(strval($name)), $this->encode($value))); + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', + + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', + + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' + + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * If available the native PHP JSON implementation is used. + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) { + if (!$this->skipnative && function_exists('json_decode')){ + return json_decode($str,($this->use == JSON_LOOSE_TYPE)); + } + + $str = $this->reduce_string($str); + + switch (strtolower($str)) { + case 'true': + return true; + + case 'false': + return false; + + case 'null': + return null; + + default: + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').+("|\')$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = substr($str, 0, 1); + $chrs = substr($str, 1, -1); + $utf8 = ''; + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = substr($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + + switch ($substr_chrs_c_2) { + case '\b': + $utf8 .= chr(0x08); + $c+=1; + break; + case '\t': + $utf8 .= chr(0x09); + $c+=1; + break; + case '\n': + $utf8 .= chr(0x0A); + $c+=1; + break; + case '\f': + $utf8 .= chr(0x0C); + $c+=1; + break; + case '\r': + $utf8 .= chr(0x0D); + $c+=1; + break; + + case '\\"': + case '\\\'': + case '\\\\': + case '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + default: + if (preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6))) { + // single, escaped unicode character + $utf16 = chr(hexdec(substr($chrs, ($c+2), 2))) + . chr(hexdec(substr($chrs, ($c+4), 2))); + //$utf8 .= mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + $utf8 .= utf16be_to_utf8($utf16); + $c+=5; + + } elseif(($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F)) { + $utf8 .= $chrs{$c}; + + } elseif(($ord_chrs_c & 0xE0) == 0xC0) { + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 2); + $c += 1; + + } elseif(($ord_chrs_c & 0xF0) == 0xE0) { + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 3); + $c += 2; + + } elseif(($ord_chrs_c & 0xF8) == 0xF0) { + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 4); + $c += 3; + + } elseif(($ord_chrs_c & 0xFC) == 0xF8) { + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 5); + $c += 4; + + } elseif(($ord_chrs_c & 0xFE) == 0xFC) { + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 6); + $c += 5; + + } + break; + + } + + } + + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use == JSON_LOOSE_TYPE) { + $stk = array(JSON_IN_OBJ); + $obj = array(); + } else { + $stk = array(JSON_IN_OBJ); + $obj = new stdClass(); + } + } + + array_push($stk, array('what' => JSON_SLICE, + 'where' => 0, + 'delim' => false)); + + $chrs = substr($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == JSON_IN_ARR) { + return $arr; + + } else { + return $obj; + + } + } + + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = substr($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = substr($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode($parts[2]); + + if ($this->use == JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode($parts[2]); + + if ($this->use == JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } + + } + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && + in_array($top['what'], array(JSON_SLICE, JSON_IN_ARR, JSON_IN_OBJ))) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == JSON_IN_STR) && + (($chrs{$c - 1} != "\\") || + ($chrs{$c - 1} == "\\" && $chrs{$c - 2} == "\\"))) { + // found a quote, we're in a string, and it's not escaped + array_pop($stk); + //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(JSON_SLICE, JSON_IN_ARR, JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(JSON_SLICE, JSON_IN_ARR, JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(JSON_SLICE, JSON_IN_ARR, JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } + + } + + if (reset($stk) == JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == JSON_IN_OBJ) { + return $obj; + + } + + } + } + } + + /** + * decodes a JSON string into appropriate variable; alias for decode() + */ + function dec($var) { + return $this->decode($var); + } +} + diff --git a/inc/JpegMeta.php b/inc/JpegMeta.php new file mode 100644 index 0000000..5c043fb --- /dev/null +++ b/inc/JpegMeta.php @@ -0,0 +1,3008 @@ + + * @link http://github.com/sd/jpeg-php + * @author Sebastian Delmont + * @author Andreas Gohr + * @author Hakan Sandell + * @todo Add support for Maker Notes, Extend for GIF and PNG metadata + */ + +// Original copyright notice: +// +// Copyright (c) 2003 Sebastian Delmont +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. 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. +// 3. Neither the name of the author nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER 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 + +class JpegMeta { + var $_fileName; + var $_fp = null; + var $_type = 'unknown'; + + var $_markers; + var $_info; + + + /** + * Constructor + * + * @author Sebastian Delmont + */ + function JpegMeta($fileName) { + + $this->_fileName = $fileName; + + $this->_fp = null; + $this->_type = 'unknown'; + + unset($this->_info); + unset($this->_markers); + } + + /** + * Returns all gathered info as multidim array + * + * @author Sebastian Delmont + */ + function & getRawInfo() { + $this->_parseAll(); + + if ($this->_markers == null) { + return false; + } + + return $this->_info; + } + + /** + * Returns basic image info + * + * @author Sebastian Delmont + */ + function & getBasicInfo() { + $this->_parseAll(); + + $info = array(); + + if ($this->_markers == null) { + return false; + } + + $info['Name'] = $this->_info['file']['Name']; + if (isset($this->_info['file']['Url'])) { + $info['Url'] = $this->_info['file']['Url']; + $info['NiceSize'] = "???KB"; + } else { + $info['Size'] = $this->_info['file']['Size']; + $info['NiceSize'] = $this->_info['file']['NiceSize']; + } + + if (@isset($this->_info['sof']['Format'])) { + $info['Format'] = $this->_info['sof']['Format'] . " JPEG"; + } else { + $info['Format'] = $this->_info['sof']['Format'] . " JPEG"; + } + + if (@isset($this->_info['sof']['ColorChannels'])) { + $info['ColorMode'] = ($this->_info['sof']['ColorChannels'] > 1) ? "Color" : "B&W"; + } + + $info['Width'] = $this->getWidth(); + $info['Height'] = $this->getHeight(); + $info['DimStr'] = $this->getDimStr(); + + $dates = $this->getDates(); + + $info['DateTime'] = $dates['EarliestTime']; + $info['DateTimeStr'] = $dates['EarliestTimeStr']; + + $info['HasThumbnail'] = $this->hasThumbnail(); + + return $info; + } + + + /** + * Convinience function to access nearly all available Data + * through one function + * + * @author Andreas Gohr + */ + function getField($fields) { + if(!is_array($fields)) $fields = array($fields); + $info = false; + foreach($fields as $field){ + if(strtolower(substr($field,0,5)) == 'iptc.'){ + $info = $this->getIPTCField(substr($field,5)); + }elseif(strtolower(substr($field,0,5)) == 'exif.'){ + $info = $this->getExifField(substr($field,5)); + }elseif(strtolower(substr($field,0,4)) == 'xmp.'){ + $info = $this->getXmpField(substr($field,4)); + }elseif(strtolower(substr($field,0,5)) == 'file.'){ + $info = $this->getFileField(substr($field,5)); + }elseif(strtolower(substr($field,0,5)) == 'date.'){ + $info = $this->getDateField(substr($field,5)); + }elseif(strtolower($field) == 'simple.camera'){ + $info = $this->getCamera(); + }elseif(strtolower($field) == 'simple.raw'){ + return $this->getRawInfo(); + }elseif(strtolower($field) == 'simple.title'){ + $info = $this->getTitle(); + }elseif(strtolower($field) == 'simple.shutterspeed'){ + $info = $this->getShutterSpeed(); + }else{ + $info = $this->getExifField($field); + } + if($info != false) break; + } + + if($info === false) $info = $alt; + if(is_array($info)){ + if(isset($info['val'])){ + $info = $info['val']; + }else{ + $info = join(', ',$info); + } + } + return trim($info); + } + + /** + * Convinience function to set nearly all available Data + * through one function + * + * @author Andreas Gohr + */ + function setField($field, $value) { + if(strtolower(substr($field,0,5)) == 'iptc.'){ + return $this->setIPTCField(substr($field,5),$value); + }elseif(strtolower(substr($field,0,5)) == 'exif.'){ + return $this->setExifField(substr($field,5),$value); + }else{ + return $this->setExifField($field,$value); + } + } + + /** + * Convinience function to delete nearly all available Data + * through one function + * + * @author Andreas Gohr + */ + function deleteField($field) { + if(strtolower(substr($field,0,5)) == 'iptc.'){ + return $this->deleteIPTCField(substr($field,5)); + }elseif(strtolower(substr($field,0,5)) == 'exif.'){ + return $this->deleteExifField(substr($field,5)); + }else{ + return $this->deleteExifField($field); + } + } + + /** + * Return a date field + * + * @author Andreas Gohr + */ + function getDateField($field) { + if (!isset($this->_info['dates'])) { + $this->_info['dates'] = $this->getDates(); + } + + if (isset($this->_info['dates'][$field])) { + return $this->_info['dates'][$field]; + } + + return false; + } + + /** + * Return a file info field + * + * @author Andreas Gohr + */ + function getFileField($field) { + if (!isset($this->_info['file'])) { + $this->_parseFileInfo(); + } + + if (isset($this->_info['file'][$field])) { + return $this->_info['file'][$field]; + } + + return false; + } + + /** + * Return the camera info (Maker and Model) + * + * @author Andreas Gohr + * @todo handle makernotes + */ + function getCamera(){ + $make = $this->getField(array('Exif.Make','Exif.TIFFMake')); + $model = $this->getField(array('Exif.Model','Exif.TIFFModel')); + $cam = trim("$make $model"); + if(empty($cam)) return false; + return $cam; + } + + /** + * Return shutter speed as a ratio + * + * @author Joe Lapp + */ + function getShutterSpeed() { + if (!isset($this->_info['exif'])) { + $this->_parseMarkerExif(); + } + if(!isset($this->_info['exif']['ExposureTime'])){ + return ''; + } + + $field = $this->_info['exif']['ExposureTime']; + if($field['den'] == 1) return $field['num']; + return $field['num'].'/'.$field['den']; + } + + /** + * Return an EXIF field + * + * @author Sebastian Delmont + */ + function getExifField($field) { + if (!isset($this->_info['exif'])) { + $this->_parseMarkerExif(); + } + + if ($this->_markers == null) { + return false; + } + + if (isset($this->_info['exif'][$field])) { + return $this->_info['exif'][$field]; + } + + return false; + } + + /** + * Return an XMP field + * + * @author Hakan Sandell + */ + function getXmpField($field) { + if (!isset($this->_info['xmp'])) { + $this->_parseMarkerXmp(); + } + + if ($this->_markers == null) { + return false; + } + + if (isset($this->_info['xmp'][$field])) { + return $this->_info['xmp'][$field]; + } + + return false; + } + + /** + * Return an Adobe Field + * + * @author Sebastian Delmont + */ + function getAdobeField($field) { + if (!isset($this->_info['adobe'])) { + $this->_parseMarkerAdobe(); + } + + if ($this->_markers == null) { + return false; + } + + if (isset($this->_info['adobe'][$field])) { + return $this->_info['adobe'][$field]; + } + + return false; + } + + /** + * Return an IPTC field + * + * @author Sebastian Delmont + */ + function getIPTCField($field) { + if (!isset($this->_info['iptc'])) { + $this->_parseMarkerAdobe(); + } + + if ($this->_markers == null) { + return false; + } + + if (isset($this->_info['iptc'][$field])) { + return $this->_info['iptc'][$field]; + } + + return false; + } + + /** + * Set an EXIF field + * + * @author Sebastian Delmont + * @author Joe Lapp + */ + function setExifField($field, $value) { + if (!isset($this->_info['exif'])) { + $this->_parseMarkerExif(); + } + + if ($this->_markers == null) { + return false; + } + + if ($this->_info['exif'] == false) { + $this->_info['exif'] = array(); + } + + // make sure datetimes are in correct format + if(strlen($field) >= 8 && strtolower(substr($field, 0, 8)) == 'datetime') { + if(strlen($value) < 8 || $value{4} != ':' || $value{7} != ':') { + $value = date('Y:m:d H:i:s', strtotime($value)); + } + } + + $this->_info['exif'][$field] = $value; + + return true; + } + + /** + * Set an Adobe Field + * + * @author Sebastian Delmont + */ + function setAdobeField($field, $value) { + if (!isset($this->_info['adobe'])) { + $this->_parseMarkerAdobe(); + } + + if ($this->_markers == null) { + return false; + } + + if ($this->_info['adobe'] == false) { + $this->_info['adobe'] = array(); + } + + $this->_info['adobe'][$field] = $value; + + return true; + } + + /** + * Calculates the multiplier needed to resize the image to the given + * dimensions + * + * @author Andreas Gohr + */ + function getResizeRatio($maxwidth,$maxheight=0){ + if(!$maxheight) $maxheight = $maxwidth; + + $w = $this->getField('File.Width'); + $h = $this->getField('File.Height'); + + $ratio = 1; + if($w >= $h){ + if($w >= $maxwidth){ + $ratio = $maxwidth/$w; + }elseif($h > $maxheight){ + $ratio = $maxheight/$h; + } + }else{ + if($h >= $maxheight){ + $ratio = $maxheight/$h; + }elseif($w > $maxwidth){ + $ratio = $maxwidth/$w; + } + } + return $ratio; + } + + + /** + * Set an IPTC field + * + * @author Sebastian Delmont + */ + function setIPTCField($field, $value) { + if (!isset($this->_info['iptc'])) { + $this->_parseMarkerAdobe(); + } + + if ($this->_markers == null) { + return false; + } + + if ($this->_info['iptc'] == false) { + $this->_info['iptc'] = array(); + } + + $this->_info['iptc'][$field] = $value; + + return true; + } + + /** + * Delete an EXIF field + * + * @author Sebastian Delmont + */ + function deleteExifField($field) { + if (!isset($this->_info['exif'])) { + $this->_parseMarkerAdobe(); + } + + if ($this->_markers == null) { + return false; + } + + if ($this->_info['exif'] != false) { + unset($this->_info['exif'][$field]); + } + + return true; + } + + /** + * Delete an Adobe field + * + * @author Sebastian Delmont + */ + function deleteAdobeField($field) { + if (!isset($this->_info['adobe'])) { + $this->_parseMarkerAdobe(); + } + + if ($this->_markers == null) { + return false; + } + + if ($this->_info['adobe'] != false) { + unset($this->_info['adobe'][$field]); + } + + return true; + } + + /** + * Delete an IPTC field + * + * @author Sebastian Delmont + */ + function deleteIPTCField($field) { + if (!isset($this->_info['iptc'])) { + $this->_parseMarkerAdobe(); + } + + if ($this->_markers == null) { + return false; + } + + if ($this->_info['iptc'] != false) { + unset($this->_info['iptc'][$field]); + } + + return true; + } + + /** + * Get the image's title, tries various fields + * + * @param int $max maximum number chars (keeps words) + * @author Andreas Gohr + */ + function getTitle($max=80){ + $cap = ''; + + // try various fields + $cap = $this->getField(array('Iptc.Headline', + 'Iptc.Caption', + 'Xmp.dc:title', + 'Exif.UserComment', + 'Exif.TIFFUserComment', + 'Exif.TIFFImageDescription', + 'File.Name')); + if (empty($cap)) return false; + + if(!$max) return $cap; + // Shorten to 80 chars (keeping words) + $new = preg_replace('/\n.+$/','',wordwrap($cap, $max)); + if($new != $cap) $new .= '...'; + + return $new; + } + + /** + * Gather various date fields + * + * @author Sebastian Delmont + */ + function getDates() { + $this->_parseAll(); + if ($this->_markers == null) { + if (@isset($this->_info['file']['UnixTime'])) { + $dates['FileModified'] = $this->_info['file']['UnixTime']; + $dates['Time'] = $this->_info['file']['UnixTime']; + $dates['TimeSource'] = 'FileModified'; + $dates['TimeStr'] = date("Y-m-d H:i:s", $this->_info['file']['UnixTime']); + $dates['EarliestTime'] = $this->_info['file']['UnixTime']; + $dates['EarliestTimeSource'] = 'FileModified'; + $dates['EarliestTimeStr'] = date("Y-m-d H:i:s", $this->_info['file']['UnixTime']); + $dates['LatestTime'] = $this->_info['file']['UnixTime']; + $dates['LatestTimeSource'] = 'FileModified'; + $dates['LatestTimeStr'] = date("Y-m-d H:i:s", $this->_info['file']['UnixTime']); + return $dates; + } + return false; + } + + $dates = array(); + + $latestTime = 0; + $latestTimeSource = ""; + $earliestTime = time(); + $earliestTimeSource = ""; + + if (@isset($this->_info['exif']['DateTime'])) { + $dates['ExifDateTime'] = $this->_info['exif']['DateTime']; + + $aux = $this->_info['exif']['DateTime']; + $aux{4} = "-"; + $aux{7} = "-"; + $t = strtotime($aux); + + if ($t && $t > $latestTime) { + $latestTime = $t; + $latestTimeSource = "ExifDateTime"; + } + + if ($t && $t < $earliestTime) { + $earliestTime = $t; + $earliestTimeSource = "ExifDateTime"; + } + } + + if (@isset($this->_info['exif']['DateTimeOriginal'])) { + $dates['ExifDateTimeOriginal'] = $this->_info['exif']['DateTime']; + + $aux = $this->_info['exif']['DateTimeOriginal']; + $aux{4} = "-"; + $aux{7} = "-"; + $t = strtotime($aux); + + if ($t && $t > $latestTime) { + $latestTime = $t; + $latestTimeSource = "ExifDateTimeOriginal"; + } + + if ($t && $t < $earliestTime) { + $earliestTime = $t; + $earliestTimeSource = "ExifDateTimeOriginal"; + } + } + + if (@isset($this->_info['exif']['DateTimeDigitized'])) { + $dates['ExifDateTimeDigitized'] = $this->_info['exif']['DateTime']; + + $aux = $this->_info['exif']['DateTimeDigitized']; + $aux{4} = "-"; + $aux{7} = "-"; + $t = strtotime($aux); + + if ($t && $t > $latestTime) { + $latestTime = $t; + $latestTimeSource = "ExifDateTimeDigitized"; + } + + if ($t && $t < $earliestTime) { + $earliestTime = $t; + $earliestTimeSource = "ExifDateTimeDigitized"; + } + } + + if (@isset($this->_info['iptc']['DateCreated'])) { + $dates['IPTCDateCreated'] = $this->_info['iptc']['DateCreated']; + + $aux = $this->_info['iptc']['DateCreated']; + $aux = substr($aux, 0, 4) . "-" . substr($aux, 4, 2) . "-" . substr($aux, 6, 2); + $t = strtotime($aux); + + if ($t && $t > $latestTime) { + $latestTime = $t; + $latestTimeSource = "IPTCDateCreated"; + } + + if ($t && $t < $earliestTime) { + $earliestTime = $t; + $earliestTimeSource = "IPTCDateCreated"; + } + } + + if (@isset($this->_info['file']['UnixTime'])) { + $dates['FileModified'] = $this->_info['file']['UnixTime']; + + $t = $this->_info['file']['UnixTime']; + + if ($t && $t > $latestTime) { + $latestTime = $t; + $latestTimeSource = "FileModified"; + } + + if ($t && $t < $earliestTime) { + $earliestTime = $t; + $earliestTimeSource = "FileModified"; + } + } + + $dates['Time'] = $earliestTime; + $dates['TimeSource'] = $earliestTimeSource; + $dates['TimeStr'] = date("Y-m-d H:i:s", $earliestTime); + $dates['EarliestTime'] = $earliestTime; + $dates['EarliestTimeSource'] = $earliestTimeSource; + $dates['EarliestTimeStr'] = date("Y-m-d H:i:s", $earliestTime); + $dates['LatestTime'] = $latestTime; + $dates['LatestTimeSource'] = $latestTimeSource; + $dates['LatestTimeStr'] = date("Y-m-d H:i:s", $latestTime); + + return $dates; + } + + /** + * Get the image width, tries various fields + * + * @author Sebastian Delmont + */ + function getWidth() { + if (!isset($this->_info['sof'])) { + $this->_parseMarkerSOF(); + } + + if ($this->_markers == null) { + return false; + } + + if (isset($this->_info['sof']['ImageWidth'])) { + return $this->_info['sof']['ImageWidth']; + } + + if (!isset($this->_info['exif'])) { + $this->_parseMarkerExif(); + } + + if (isset($this->_info['exif']['PixelXDimension'])) { + return $this->_info['exif']['PixelXDimension']; + } + + return false; + } + + /** + * Get the image height, tries various fields + * + * @author Sebastian Delmont + */ + function getHeight() { + if (!isset($this->_info['sof'])) { + $this->_parseMarkerSOF(); + } + + if ($this->_markers == null) { + return false; + } + + if (isset($this->_info['sof']['ImageHeight'])) { + return $this->_info['sof']['ImageHeight']; + } + + if (!isset($this->_info['exif'])) { + $this->_parseMarkerExif(); + } + + if (isset($this->_info['exif']['PixelYDimension'])) { + return $this->_info['exif']['PixelYDimension']; + } + + return false; + } + + /** + * Get an dimension string for use in img tag + * + * @author Sebastian Delmont + */ + function getDimStr() { + if ($this->_markers == null) { + return false; + } + + $w = $this->getWidth(); + $h = $this->getHeight(); + + return "width='" . $w . "' height='" . $h . "'"; + } + + /** + * Checks for an embedded thumbnail + * + * @author Sebastian Delmont + */ + function hasThumbnail($which = 'any') { + if (($which == 'any') || ($which == 'exif')) { + if (!isset($this->_info['exif'])) { + $this->_parseMarkerExif(); + } + + if ($this->_markers == null) { + return false; + } + + if (isset($this->_info['exif']) && is_array($this->_info['exif'])) { + if (isset($this->_info['exif']['JFIFThumbnail'])) { + return 'exif'; + } + } + } + + if ($which == 'adobe') { + if (!isset($this->_info['adobe'])) { + $this->_parseMarkerAdobe(); + } + + if ($this->_markers == null) { + return false; + } + + if (isset($this->_info['adobe']) && is_array($this->_info['adobe'])) { + if (isset($this->_info['adobe']['ThumbnailData'])) { + return 'exif'; + } + } + } + + return false; + } + + /** + * Send embedded thumbnail to browser + * + * @author Sebastian Delmont + */ + function sendThumbnail($which = 'any') { + $data = null; + + if (($which == 'any') || ($which == 'exif')) { + if (!isset($this->_info['exif'])) { + $this->_parseMarkerExif(); + } + + if ($this->_markers == null) { + return false; + } + + if (isset($this->_info['exif']) && is_array($this->_info['exif'])) { + if (isset($this->_info['exif']['JFIFThumbnail'])) { + $data =& $this->_info['exif']['JFIFThumbnail']; + } + } + } + + if (($which == 'adobe') || ($data == null)){ + if (!isset($this->_info['adobe'])) { + $this->_parseMarkerAdobe(); + } + + if ($this->_markers == null) { + return false; + } + + if (isset($this->_info['adobe']) && is_array($this->_info['adobe'])) { + if (isset($this->_info['adobe']['ThumbnailData'])) { + $data =& $this->_info['adobe']['ThumbnailData']; + } + } + } + + if ($data != null) { + header("Content-type: image/jpeg"); + echo $data; + return true; + } + + return false; + } + + /** + * Save changed Metadata + * + * @author Sebastian Delmont + * @author Andreas Gohr + */ + function save($fileName = "") { + if ($fileName == "") { + $tmpName = tempnam(dirname($this->_fileName),'_metatemp_'); + $this->_writeJPEG($tmpName); + if (@file_exists($tmpName)) { + return io_rename($tmpName, $this->_fileName); + } + } else { + return $this->_writeJPEG($fileName); + } + return false; + } + + /*************************************************************/ + /* PRIVATE FUNCTIONS (Internal Use Only!) */ + /*************************************************************/ + + /*************************************************************/ + function _dispose() { + $this->_fileName = $fileName; + + $this->_fp = null; + $this->_type = 'unknown'; + + unset($this->_markers); + unset($this->_info); + } + + /*************************************************************/ + function _readJPEG() { + unset($this->_markers); + //unset($this->_info); + $this->_markers = array(); + //$this->_info = array(); + + $this->_fp = @fopen($this->_fileName, 'rb'); + if ($this->_fp) { + if (file_exists($this->_fileName)) { + $this->_type = 'file'; + } + else { + $this->_type = 'url'; + } + } else { + $this->_fp = null; + return false; // ERROR: Can't open file + } + + // Check for the JPEG signature + $c1 = ord(fgetc($this->_fp)); + $c2 = ord(fgetc($this->_fp)); + + if ($c1 != 0xFF || $c2 != 0xD8) { // (0xFF + SOI) + $this->_markers = null; + return false; // ERROR: File is not a JPEG + } + + $count = 0; + + $done = false; + $ok = true; + + while (!$done) { + $capture = false; + + // First, skip any non 0xFF bytes + $discarded = 0; + $c = ord(fgetc($this->_fp)); + while (!feof($this->_fp) && ($c != 0xFF)) { + $discarded++; + $c = ord(fgetc($this->_fp)); + } + // Then skip all 0xFF until the marker byte + do { + $marker = ord(fgetc($this->_fp)); + } while (!feof($this->_fp) && ($marker == 0xFF)); + + if (feof($this->_fp)) { + return false; // ERROR: Unexpected EOF + } + if ($discarded != 0) { + return false; // ERROR: Extraneous data + } + + $length = ord(fgetc($this->_fp)) * 256 + ord(fgetc($this->_fp)); + if (feof($this->_fp)) { + return false; // ERROR: Unexpected EOF + } + if ($length < 2) { + return false; // ERROR: Extraneous data + } + $length = $length - 2; // The length we got counts itself + + switch ($marker) { + case 0xC0: // SOF0 + case 0xC1: // SOF1 + case 0xC2: // SOF2 + case 0xC9: // SOF9 + case 0xE0: // APP0: JFIF data + case 0xE1: // APP1: EXIF or XMP data + case 0xED: // APP13: IPTC / Photoshop data + $capture = true; + break; + case 0xDA: // SOS: Start of scan... the image itself and the last block on the file + $capture = false; + $length = -1; // This field has no length... it includes all data until EOF + $done = true; + break; + default: + $capture = true;//false; + break; + } + + $this->_markers[$count] = array(); + $this->_markers[$count]['marker'] = $marker; + $this->_markers[$count]['length'] = $length; + + if ($capture) { + if ($length) + $this->_markers[$count]['data'] =& fread($this->_fp, $length); + else + $this->_markers[$count]['data'] = ""; + } + elseif (!$done) { + $result = @fseek($this->_fp, $length, SEEK_CUR); + // fseek doesn't seem to like HTTP 'files', but fgetc has no problem + if (!($result === 0)) { + for ($i = 0; $i < $length; $i++) { + fgetc($this->_fp); + } + } + } + $count++; + } + + if ($this->_fp) { + fclose($this->_fp); + $this->_fp = null; + } + + return $ok; + } + + /*************************************************************/ + function _parseAll() { + if (!isset($this->_info['file'])) { + $this->_parseFileInfo(); + } + if (!isset($this->_markers)) { + $this->_readJPEG(); + } + + if ($this->_markers == null) { + return false; + } + + if (!isset($this->_info['jfif'])) { + $this->_parseMarkerJFIF(); + } + if (!isset($this->_info['jpeg'])) { + $this->_parseMarkerSOF(); + } + if (!isset($this->_info['exif'])) { + $this->_parseMarkerExif(); + } + if (!isset($this->_info['xmp'])) { + $this->_parseMarkerXmp(); + } + if (!isset($this->_info['adobe'])) { + $this->_parseMarkerAdobe(); + } + } + + /*************************************************************/ + function _writeJPEG($outputName) { + $this->_parseAll(); + + $wroteEXIF = false; + $wroteAdobe = false; + + $this->_fp = @fopen($this->_fileName, 'r'); + if ($this->_fp) { + if (file_exists($this->_fileName)) { + $this->_type = 'file'; + } + else { + $this->_type = 'url'; + } + } else { + $this->_fp = null; + return false; // ERROR: Can't open file + } + + $this->_fpout = fopen($outputName, 'wb'); + if (!$this->_fpout) { + $this->_fpout = null; + fclose($this->_fp); + $this->_fp = null; + return false; // ERROR: Can't open output file + } + + // Check for the JPEG signature + $c1 = ord(fgetc($this->_fp)); + $c2 = ord(fgetc($this->_fp)); + + if ($c1 != 0xFF || $c2 != 0xD8) { // (0xFF + SOI) + return false; // ERROR: File is not a JPEG + } + + fputs($this->_fpout, chr(0xFF), 1); + fputs($this->_fpout, chr(0xD8), 1); // (0xFF + SOI) + + $count = 0; + + $done = false; + $ok = true; + + while (!$done) { + // First, skip any non 0xFF bytes + $discarded = 0; + $c = ord(fgetc($this->_fp)); + while (!feof($this->_fp) && ($c != 0xFF)) { + $discarded++; + $c = ord(fgetc($this->_fp)); + } + // Then skip all 0xFF until the marker byte + do { + $marker = ord(fgetc($this->_fp)); + } while (!feof($this->_fp) && ($marker == 0xFF)); + + if (feof($this->_fp)) { + $ok = false; + break; // ERROR: Unexpected EOF + } + if ($discarded != 0) { + $ok = false; + break; // ERROR: Extraneous data + } + + $length = ord(fgetc($this->_fp)) * 256 + ord(fgetc($this->_fp)); + if (feof($this->_fp)) { + $ok = false; + break; // ERROR: Unexpected EOF + } + if ($length < 2) { + $ok = false; + break; // ERROR: Extraneous data + } + $length = $length - 2; // The length we got counts itself + + unset($data); + if ($marker == 0xE1) { // APP1: EXIF data + $data =& $this->_createMarkerEXIF(); + $wroteEXIF = true; + } + elseif ($marker == 0xED) { // APP13: IPTC / Photoshop data + $data =& $this->_createMarkerAdobe(); + $wroteAdobe = true; + } + elseif ($marker == 0xDA) { // SOS: Start of scan... the image itself and the last block on the file + $done = true; + } + + if (!$wroteEXIF && (($marker < 0xE0) || ($marker > 0xEF))) { + if (isset($this->_info['exif']) && is_array($this->_info['exif'])) { + $exif =& $this->_createMarkerEXIF(); + $this->_writeJPEGMarker(0xE1, strlen($exif), $exif, 0); + unset($exif); + } + $wroteEXIF = true; + } + + if (!$wroteAdobe && (($marker < 0xE0) || ($marker > 0xEF))) { + if ((isset($this->_info['adobe']) && is_array($this->_info['adobe'])) + || (isset($this->_info['iptc']) && is_array($this->_info['iptc']))) { + $adobe =& $this->_createMarkerAdobe(); + $this->_writeJPEGMarker(0xED, strlen($adobe), $adobe, 0); + unset($adobe); + } + $wroteAdobe = true; + } + + $origLength = $length; + if (isset($data)) { + $length = strlen($data); + } + + if ($marker != -1) { + $this->_writeJPEGMarker($marker, $length, $data, $origLength); + } + } + + if ($this->_fp) { + fclose($this->_fp); + $this->_fp = null; + } + + if ($this->_fpout) { + fclose($this->_fpout); + $this->_fpout = null; + } + + return $ok; + } + + /*************************************************************/ + function _writeJPEGMarker($marker, $length, &$data, $origLength) { + if ($length <= 0) { + return false; + } + + fputs($this->_fpout, chr(0xFF), 1); + fputs($this->_fpout, chr($marker), 1); + fputs($this->_fpout, chr((($length + 2) & 0x0000FF00) >> 8), 1); + fputs($this->_fpout, chr((($length + 2) & 0x000000FF) >> 0), 1); + + if (isset($data)) { + // Copy the generated data + fputs($this->_fpout, $data, $length); + + if ($origLength > 0) { // Skip the original data + $result = @fseek($this->_fp, $origLength, SEEK_CUR); + // fseek doesn't seem to like HTTP 'files', but fgetc has no problem + if ($result != 0) { + for ($i = 0; $i < $origLength; $i++) { + fgetc($this->_fp); + } + } + } + } else { + if ($marker == 0xDA) { // Copy until EOF + while (!feof($this->_fp)) { + $data = fread($this->_fp, 1024 * 16); + fputs($this->_fpout, $data, strlen($data)); + } + } else { // Copy only $length bytes + $data = @fread($this->_fp, $length); + fputs($this->_fpout, $data, $length); + } + } + + return true; + } + + /** + * Gets basic info from the file - should work with non-JPEGs + * + * @author Sebastian Delmont + * @author Andreas Gohr + */ + function _parseFileInfo() { + if (file_exists($this->_fileName) && is_file($this->_fileName)) { + $this->_info['file'] = array(); + $this->_info['file']['Name'] = basename($this->_fileName); + $this->_info['file']['Path'] = fullpath($this->_fileName); + $this->_info['file']['Size'] = filesize($this->_fileName); + if ($this->_info['file']['Size'] < 1024) { + $this->_info['file']['NiceSize'] = $this->_info['file']['Size'] . 'B'; + } elseif ($this->_info['file']['Size'] < (1024 * 1024)) { + $this->_info['file']['NiceSize'] = round($this->_info['file']['Size'] / 1024) . 'KB'; + } elseif ($this->_info['file']['Size'] < (1024 * 1024 * 1024)) { + $this->_info['file']['NiceSize'] = round($this->_info['file']['Size'] / (1024*1024)) . 'MB'; + } else { + $this->_info['file']['NiceSize'] = $this->_info['file']['Size'] . 'B'; + } + $this->_info['file']['UnixTime'] = filemtime($this->_fileName); + + // get image size directly from file + $size = getimagesize($this->_fileName); + $this->_info['file']['Width'] = $size[0]; + $this->_info['file']['Height'] = $size[1]; + // set mime types and formats + // http://www.php.net/manual/en/function.getimagesize.php + // http://www.php.net/manual/en/function.image-type-to-mime-type.php + switch ($size[2]){ + case 1: + $this->_info['file']['Mime'] = 'image/gif'; + $this->_info['file']['Format'] = 'GIF'; + break; + case 2: + $this->_info['file']['Mime'] = 'image/jpeg'; + $this->_info['file']['Format'] = 'JPEG'; + break; + case 3: + $this->_info['file']['Mime'] = 'image/png'; + $this->_info['file']['Format'] = 'PNG'; + break; + case 4: + $this->_info['file']['Mime'] = 'application/x-shockwave-flash'; + $this->_info['file']['Format'] = 'SWF'; + break; + case 5: + $this->_info['file']['Mime'] = 'image/psd'; + $this->_info['file']['Format'] = 'PSD'; + break; + case 6: + $this->_info['file']['Mime'] = 'image/bmp'; + $this->_info['file']['Format'] = 'BMP'; + break; + case 7: + $this->_info['file']['Mime'] = 'image/tiff'; + $this->_info['file']['Format'] = 'TIFF (Intel)'; + break; + case 8: + $this->_info['file']['Mime'] = 'image/tiff'; + $this->_info['file']['Format'] = 'TIFF (Motorola)'; + break; + case 9: + $this->_info['file']['Mime'] = 'application/octet-stream'; + $this->_info['file']['Format'] = 'JPC'; + break; + case 10: + $this->_info['file']['Mime'] = 'image/jp2'; + $this->_info['file']['Format'] = 'JP2'; + break; + case 11: + $this->_info['file']['Mime'] = 'application/octet-stream'; + $this->_info['file']['Format'] = 'JPX'; + break; + case 12: + $this->_info['file']['Mime'] = 'application/octet-stream'; + $this->_info['file']['Format'] = 'JB2'; + break; + case 13: + $this->_info['file']['Mime'] = 'application/x-shockwave-flash'; + $this->_info['file']['Format'] = 'SWC'; + break; + case 14: + $this->_info['file']['Mime'] = 'image/iff'; + $this->_info['file']['Format'] = 'IFF'; + break; + case 15: + $this->_info['file']['Mime'] = 'image/vnd.wap.wbmp'; + $this->_info['file']['Format'] = 'WBMP'; + break; + case 16: + $this->_info['file']['Mime'] = 'image/xbm'; + $this->_info['file']['Format'] = 'XBM'; + break; + default: + $this->_info['file']['Mime'] = 'image/unknown'; + } + } else { + $this->_info['file'] = array(); + $this->_info['file']['Name'] = basename($this->_fileName); + $this->_info['file']['Url'] = $this->_fileName; + } + + return true; + } + + /*************************************************************/ + function _parseMarkerJFIF() { + if (!isset($this->_markers)) { + $this->_readJPEG(); + } + + if ($this->_markers == null) { + return false; + } + + $data = null; + $count = count($this->_markers); + for ($i = 0; $i < $count; $i++) { + if ($this->_markers[$i]['marker'] == 0xE0) { + $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 4); + if ($signature == 'JFIF') { + $data =& $this->_markers[$i]['data']; + break; + } + } + } + + if ($data == null) { + $this->_info['jfif'] = false; + return false; + } + + $pos = 0; + $this->_info['jfif'] = array(); + + $vmaj = $this->_getByte($data, 5); + $vmin = $this->_getByte($data, 6); + + $this->_info['jfif']['Version'] = sprintf('%d.%02d', $vmaj, $vmin); + + $units = $this->_getByte($data, 7); + switch ($units) { + case 0: + $this->_info['jfif']['Units'] = 'pixels'; + break; + case 1: + $this->_info['jfif']['Units'] = 'dpi'; + break; + case 2: + $this->_info['jfif']['Units'] = 'dpcm'; + break; + default: + $this->_info['jfif']['Units'] = 'unknown'; + break; + } + + $xdens = $this->_getShort($data, 8); + $ydens = $this->_getShort($data, 10); + + $this->_info['jfif']['XDensity'] = $xdens; + $this->_info['jfif']['YDensity'] = $ydens; + + $thumbx = $this->_getByte($data, 12); + $thumby = $this->_getByte($data, 13); + + $this->_info['jfif']['ThumbnailWidth'] = $thumbx; + $this->_info['jfif']['ThumbnailHeight'] = $thumby; + + return true; + } + + /*************************************************************/ + function _parseMarkerSOF() { + if (!isset($this->_markers)) { + $this->_readJPEG(); + } + + if ($this->_markers == null) { + return false; + } + + $data = null; + $count = count($this->_markers); + for ($i = 0; $i < $count; $i++) { + switch ($this->_markers[$i]['marker']) { + case 0xC0: // SOF0 + case 0xC1: // SOF1 + case 0xC2: // SOF2 + case 0xC9: // SOF9 + $data =& $this->_markers[$i]['data']; + $marker = $this->_markers[$i]['marker']; + break; + } + } + + if ($data == null) { + $this->_info['sof'] = false; + return false; + } + + $pos = 0; + $this->_info['sof'] = array(); + + switch ($marker) { + case 0xC0: // SOF0 + $format = 'Baseline'; + break; + case 0xC1: // SOF1 + $format = 'Progessive'; + break; + case 0xC2: // SOF2 + $format = 'Non-baseline'; + break; + case 0xC9: // SOF9 + $format = 'Arithmetic'; + break; + default: + return false; + break; + } + + $this->_info['sof']['Format'] = $format; + $this->_info['sof']['SamplePrecision'] = $this->_getByte($data, $pos + 0); + $this->_info['sof']['ImageHeight'] = $this->_getShort($data, $pos + 1); + $this->_info['sof']['ImageWidth'] = $this->_getShort($data, $pos + 3); + $this->_info['sof']['ColorChannels'] = $this->_getByte($data, $pos + 5); + + return true; + } + + /** + * Parses the XMP data + * + * @author Hakan Sandell + */ + function _parseMarkerXmp() { + if (!isset($this->_markers)) { + $this->_readJPEG(); + } + + if ($this->_markers == null) { + return false; + } + + $data = null; + $count = count($this->_markers); + for ($i = 0; $i < $count; $i++) { + if ($this->_markers[$i]['marker'] == 0xE1) { + $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 29); + if ($signature == "http://ns.adobe.com/xap/1.0/\0") { + $data =& substr($this->_markers[$i]['data'], 29); + break; + } + } + } + + if ($data == null) { + $this->_info['xmp'] = false; + return false; + } + + $parser = xml_parser_create(); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); + xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); + $result = xml_parse_into_struct($parser, $data, $values, $tags); + xml_parser_free($parser); + + if ($result == 0) { + $this->_info['xmp'] = false; + return false; + } + + $this->_info['xmp'] = array(); + $count = count($values); + for ($i = 0; $i < $count; $i++) { + if ($values[$i]['tag'] == 'rdf:Description' && $values[$i]['type'] == 'open') { + + while ((++$i < $count) && ($values[$i]['tag'] != 'rdf:Description')) { + $this->_parseXmpNode($values, $i, $this->_info['xmp'][$values[$i]['tag']], $count); + } + } + } + return true; + } + + /** + * Parses XMP nodes by recursion + * + * @author Hakan Sandell + */ + function _parseXmpNode($values, &$i, &$meta, $count) { + if ($values[$i]['type'] == 'close') return; + + if ($values[$i]['type'] == 'complete') { + // Simple Type property + $meta = $values[$i]['value']; + return; + } + + $i++; + if ($i >= $count) return; + + if ($values[$i]['tag'] == 'rdf:Bag' || $values[$i]['tag'] == 'rdf:Seq') { + // Array property + $meta = array(); + while ($values[++$i]['tag'] == 'rdf:li') { + $this->_parseXmpNode($values, $i, $meta[], $count); + } + $i++; // skip closing Bag/Seq tag + + } elseif ($values[$i]['tag'] == 'rdf:Alt') { + // Language Alternative property, only the first (default) value is used + if ($values[$i]['type'] == 'open') { + $i++; + $this->_parseXmpNode($values, $i, $meta, $count); + while ((++$i < $count) && ($values[$i]['tag'] != 'rdf:Alt')); + $i++; // skip closing Alt tag + } + + } else { + // Structure property + $meta = array(); + $startTag = $values[$i-1]['tag']; + do { + $this->_parseXmpNode($values, $i, $meta[$values[$i]['tag']], $count); + } while ((++$i < $count) && ($values[$i]['tag'] != $startTag)); + } + } + + /*************************************************************/ + function _parseMarkerExif() { + if (!isset($this->_markers)) { + $this->_readJPEG(); + } + + if ($this->_markers == null) { + return false; + } + + $data = null; + $count = count($this->_markers); + for ($i = 0; $i < $count; $i++) { + if ($this->_markers[$i]['marker'] == 0xE1) { + $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 6); + if ($signature == "Exif\0\0") { + $data =& $this->_markers[$i]['data']; + break; + } + } + } + + if ($data == null) { + $this->_info['exif'] = false; + return false; + } + $pos = 6; + $this->_info['exif'] = array(); + + // We don't increment $pos after this because Exif uses offsets relative to this point + + $byteAlign = $this->_getShort($data, $pos + 0); + + if ($byteAlign == 0x4949) { // "II" + $isBigEndian = false; + } elseif ($byteAlign == 0x4D4D) { // "MM" + $isBigEndian = true; + } else { + return false; // Unexpected data + } + + $alignCheck = $this->_getShort($data, $pos + 2, $isBigEndian); + if ($alignCheck != 0x002A) // That's the expected value + return false; // Unexpected data + + if ($isBigEndian) { + $this->_info['exif']['ByteAlign'] = "Big Endian"; + } else { + $this->_info['exif']['ByteAlign'] = "Little Endian"; + } + + $offsetIFD0 = $this->_getLong($data, $pos + 4, $isBigEndian); + if ($offsetIFD0 < 8) + return false; // Unexpected data + + $offsetIFD1 = $this->_readIFD($data, $pos, $offsetIFD0, $isBigEndian, 'ifd0'); + if ($offsetIFD1 != 0) + $this->_readIFD($data, $pos, $offsetIFD1, $isBigEndian, 'ifd1'); + + return true; + } + + /*************************************************************/ + function _readIFD($data, $base, $offset, $isBigEndian, $mode) { + $EXIFTags = $this->_exifTagNames($mode); + + $numEntries = $this->_getShort($data, $base + $offset, $isBigEndian); + $offset += 2; + + $exifTIFFOffset = 0; + $exifTIFFLength = 0; + $exifThumbnailOffset = 0; + $exifThumbnailLength = 0; + + for ($i = 0; $i < $numEntries; $i++) { + $tag = $this->_getShort($data, $base + $offset, $isBigEndian); + $offset += 2; + $type = $this->_getShort($data, $base + $offset, $isBigEndian); + $offset += 2; + $count = $this->_getLong($data, $base + $offset, $isBigEndian); + $offset += 4; + + if (($type < 1) || ($type > 12)) + return false; // Unexpected Type + + $typeLengths = array( -1, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 ); + + $dataLength = $typeLengths[$type] * $count; + if ($dataLength > 4) { + $dataOffset = $this->_getLong($data, $base + $offset, $isBigEndian); + $rawValue = $this->_getFixedString($data, $base + $dataOffset, $dataLength); + } else { + $rawValue = $this->_getFixedString($data, $base + $offset, $dataLength); + } + $offset += 4; + + switch ($type) { + case 1: // UBYTE + if ($count == 1) { + $value = $this->_getByte($rawValue, 0); + } else { + $value = array(); + for ($j = 0; $j < $count; $j++) + $value[$j] = $this->_getByte($rawValue, $j); + } + break; + case 2: // ASCII + $value = $rawValue; + break; + case 3: // USHORT + if ($count == 1) { + $value = $this->_getShort($rawValue, 0, $isBigEndian); + } else { + $value = array(); + for ($j = 0; $j < $count; $j++) + $value[$j] = $this->_getShort($rawValue, $j * 2, $isBigEndian); + } + break; + case 4: // ULONG + if ($count == 1) { + $value = $this->_getLong($rawValue, 0, $isBigEndian); + } else { + $value = array(); + for ($j = 0; $j < $count; $j++) + $value[$j] = $this->_getLong($rawValue, $j * 4, $isBigEndian); + } + break; + case 5: // URATIONAL + if ($count == 1) { + $a = $this->_getLong($rawValue, 0, $isBigEndian); + $b = $this->_getLong($rawValue, 4, $isBigEndian); + $value = array(); + $value['val'] = 0; + $value['num'] = $a; + $value['den'] = $b; + if (($a != 0) && ($b != 0)) { + $value['val'] = $a / $b; + } + } else { + $value = array(); + for ($j = 0; $j < $count; $j++) { + $a = $this->_getLong($rawValue, $j * 8, $isBigEndian); + $b = $this->_getLong($rawValue, ($j * 8) + 4, $isBigEndian); + $value = array(); + $value[$j]['val'] = 0; + $value[$j]['num'] = $a; + $value[$j]['den'] = $b; + if (($a != 0) && ($b != 0)) + $value[$j]['val'] = $a / $b; + } + } + break; + case 6: // SBYTE + if ($count == 1) { + $value = $this->_getByte($rawValue, 0); + } else { + $value = array(); + for ($j = 0; $j < $count; $j++) + $value[$j] = $this->_getByte($rawValue, $j); + } + break; + case 7: // UNDEFINED + $value = $rawValue; + break; + case 8: // SSHORT + if ($count == 1) { + $value = $this->_getShort($rawValue, 0, $isBigEndian); + } else { + $value = array(); + for ($j = 0; $j < $count; $j++) + $value[$j] = $this->_getShort($rawValue, $j * 2, $isBigEndian); + } + break; + case 9: // SLONG + if ($count == 1) { + $value = $this->_getLong($rawValue, 0, $isBigEndian); + } else { + $value = array(); + for ($j = 0; $j < $count; $j++) + $value[$j] = $this->_getLong($rawValue, $j * 4, $isBigEndian); + } + break; + case 10: // SRATIONAL + if ($count == 1) { + $a = $this->_getLong($rawValue, 0, $isBigEndian); + $b = $this->_getLong($rawValue, 4, $isBigEndian); + $value = array(); + $value['val'] = 0; + $value['num'] = $a; + $value['den'] = $b; + if (($a != 0) && ($b != 0)) + $value['val'] = $a / $b; + } else { + $value = array(); + for ($j = 0; $j < $count; $j++) { + $a = $this->_getLong($rawValue, $j * 8, $isBigEndian); + $b = $this->_getLong($rawValue, ($j * 8) + 4, $isBigEndian); + $value = array(); + $value[$j]['val'] = 0; + $value[$j]['num'] = $a; + $value[$j]['den'] = $b; + if (($a != 0) && ($b != 0)) + $value[$j]['val'] = $a / $b; + } + } + break; + case 11: // FLOAT + $value = $rawValue; + break; + + case 12: // DFLOAT + $value = $rawValue; + break; + default: + return false; // Unexpected Type + } + + $tagName = ''; + if (($mode == 'ifd0') && ($tag == 0x8769)) { // ExifIFDOffset + $this->_readIFD($data, $base, $value, $isBigEndian, 'exif'); + } elseif (($mode == 'ifd0') && ($tag == 0x8825)) { // GPSIFDOffset + $this->_readIFD($data, $base, $value, $isBigEndian, 'gps'); + } elseif (($mode == 'ifd1') && ($tag == 0x0111)) { // TIFFStripOffsets + $exifTIFFOffset = $value; + } elseif (($mode == 'ifd1') && ($tag == 0x0117)) { // TIFFStripByteCounts + $exifTIFFLength = $value; + } elseif (($mode == 'ifd1') && ($tag == 0x0201)) { // TIFFJFIFOffset + $exifThumbnailOffset = $value; + } elseif (($mode == 'ifd1') && ($tag == 0x0202)) { // TIFFJFIFLength + $exifThumbnailLength = $value; + } elseif (($mode == 'exif') && ($tag == 0xA005)) { // InteropIFDOffset + $this->_readIFD($data, $base, $value, $isBigEndian, 'interop'); + } + // elseif (($mode == 'exif') && ($tag == 0x927C)) { // MakerNote + // } + else { + if (isset($EXIFTags[$tag])) { + $tagName = $EXIFTags[$tag]; + if (isset($this->_info['exif'][$tagName])) { + if (!is_array($this->_info['exif'][$tagName])) { + $aux = array(); + $aux[0] = $this->_info['exif'][$tagName]; + $this->_info['exif'][$tagName] = $aux; + } + + $this->_info['exif'][$tagName][count($this->_info['exif'][$tagName])] = $value; + } else { + $this->_info['exif'][$tagName] = $value; + } + } + /* + else { + echo sprintf("

Unknown tag %02x (t: %d l: %d) %s in %s

", $tag, $type, $count, $mode, $this->_fileName); + // Unknown Tags will be ignored!!! + // That's because the tag might be a pointer (like the Exif tag) + // and saving it without saving the data it points to might + // create an invalid file. + } + */ + } + } + + if (($exifThumbnailOffset > 0) && ($exifThumbnailLength > 0)) { + $this->_info['exif']['JFIFThumbnail'] = $this->_getFixedString($data, $base + $exifThumbnailOffset, $exifThumbnailLength); + } + + if (($exifTIFFOffset > 0) && ($exifTIFFLength > 0)) { + $this->_info['exif']['TIFFStrips'] = $this->_getFixedString($data, $base + $exifTIFFOffset, $exifTIFFLength); + } + + $nextOffset = $this->_getLong($data, $base + $offset, $isBigEndian); + return $nextOffset; + } + + /*************************************************************/ + function & _createMarkerExif() { + $data = null; + $count = count($this->_markers); + for ($i = 0; $i < $count; $i++) { + if ($this->_markers[$i]['marker'] == 0xE1) { + $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 6); + if ($signature == "Exif\0\0") { + $data =& $this->_markers[$i]['data']; + break; + } + } + } + + if (!isset($this->_info['exif'])) { + return false; + } + + $data = "Exif\0\0"; + $pos = 6; + $offsetBase = 6; + + if (isset($this->_info['exif']['ByteAlign']) && ($this->_info['exif']['ByteAlign'] == "Big Endian")) { + $isBigEndian = true; + $aux = "MM"; + $pos = $this->_putString($data, $pos, $aux); + } else { + $isBigEndian = false; + $aux = "II"; + $pos = $this->_putString($data, $pos, $aux); + } + $pos = $this->_putShort($data, $pos, 0x002A, $isBigEndian); + $pos = $this->_putLong($data, $pos, 0x00000008, $isBigEndian); // IFD0 Offset is always 8 + + $ifd0 =& $this->_getIFDEntries($isBigEndian, 'ifd0'); + $ifd1 =& $this->_getIFDEntries($isBigEndian, 'ifd1'); + + $pos = $this->_writeIFD($data, $pos, $offsetBase, $ifd0, $isBigEndian, true); + $pos = $this->_writeIFD($data, $pos, $offsetBase, $ifd1, $isBigEndian, false); + + return $data; + } + + /*************************************************************/ + function _writeIFD(&$data, $pos, $offsetBase, &$entries, $isBigEndian, $hasNext) { + $tiffData = null; + $tiffDataOffsetPos = -1; + + $entryCount = count($entries); + + $dataPos = $pos + 2 + ($entryCount * 12) + 4; + $pos = $this->_putShort($data, $pos, $entryCount, $isBigEndian); + + for ($i = 0; $i < $entryCount; $i++) { + $tag = $entries[$i]['tag']; + $type = $entries[$i]['type']; + + if ($type == -99) { // SubIFD + $pos = $this->_putShort($data, $pos, $tag, $isBigEndian); + $pos = $this->_putShort($data, $pos, 0x04, $isBigEndian); // LONG + $pos = $this->_putLong($data, $pos, 0x01, $isBigEndian); // Count = 1 + $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian); + + $dataPos = $this->_writeIFD($data, $dataPos, $offsetBase, $entries[$i]['value'], $isBigEndian, false); + } elseif ($type == -98) { // TIFF Data + $pos = $this->_putShort($data, $pos, $tag, $isBigEndian); + $pos = $this->_putShort($data, $pos, 0x04, $isBigEndian); // LONG + $pos = $this->_putLong($data, $pos, 0x01, $isBigEndian); // Count = 1 + $tiffDataOffsetPos = $pos; + $pos = $this->_putLong($data, $pos, 0x00, $isBigEndian); // For Now + $tiffData =& $entries[$i]['value'] ; + } else { // Regular Entry + $pos = $this->_putShort($data, $pos, $tag, $isBigEndian); + $pos = $this->_putShort($data, $pos, $type, $isBigEndian); + $pos = $this->_putLong($data, $pos, $entries[$i]['count'], $isBigEndian); + if (strlen($entries[$i]['value']) > 4) { + $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian); + $dataPos = $this->_putString($data, $dataPos, $entries[$i]['value']); + } else { + $val = str_pad($entries[$i]['value'], 4, "\0"); + $pos = $this->_putString($data, $pos, $val); + } + } + } + + if ($tiffData != null) { + $this->_putLong($data, $tiffDataOffsetPos, $dataPos - $offsetBase, $isBigEndian); + $dataPos = $this->_putString($data, $dataPos, $tiffData); + } + + if ($hasNext) { + $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian); + } else { + $pos = $this->_putLong($data, $pos, 0, $isBigEndian); + } + + return $dataPos; + } + + /*************************************************************/ + function & _getIFDEntries($isBigEndian, $mode) { + $EXIFNames = $this->_exifTagNames($mode); + $EXIFTags = $this->_exifNameTags($mode); + $EXIFTypeInfo = $this->_exifTagTypes($mode); + + $ifdEntries = array(); + $entryCount = 0; + + reset($EXIFNames); + while (list($tag, $name) = each($EXIFNames)) { + $type = $EXIFTypeInfo[$tag][0]; + $count = $EXIFTypeInfo[$tag][1]; + $value = null; + + if (($mode == 'ifd0') && ($tag == 0x8769)) { // ExifIFDOffset + if (isset($this->_info['exif']['EXIFVersion'])) { + $value =& $this->_getIFDEntries($isBigEndian, "exif"); + $type = -99; + } + else { + $value = null; + } + } elseif (($mode == 'ifd0') && ($tag == 0x8825)) { // GPSIFDOffset + if (isset($this->_info['exif']['GPSVersionID'])) { + $value =& $this->_getIFDEntries($isBigEndian, "gps"); + $type = -99; + } else { + $value = null; + } + } elseif (($mode == 'ifd1') && ($tag == 0x0111)) { // TIFFStripOffsets + if (isset($this->_info['exif']['TIFFStrips'])) { + $value =& $this->_info['exif']['TIFFStrips']; + $type = -98; + } else { + $value = null; + } + } elseif (($mode == 'ifd1') && ($tag == 0x0117)) { // TIFFStripByteCounts + if (isset($this->_info['exif']['TIFFStrips'])) { + $value = strlen($this->_info['exif']['TIFFStrips']); + } else { + $value = null; + } + } elseif (($mode == 'ifd1') && ($tag == 0x0201)) { // TIFFJFIFOffset + if (isset($this->_info['exif']['JFIFThumbnail'])) { + $value =& $this->_info['exif']['JFIFThumbnail']; + $type = -98; + } else { + $value = null; + } + } elseif (($mode == 'ifd1') && ($tag == 0x0202)) { // TIFFJFIFLength + if (isset($this->_info['exif']['JFIFThumbnail'])) { + $value = strlen($this->_info['exif']['JFIFThumbnail']); + } else { + $value = null; + } + } elseif (($mode == 'exif') && ($tag == 0xA005)) { // InteropIFDOffset + if (isset($this->_info['exif']['InteroperabilityIndex'])) { + $value =& $this->_getIFDEntries($isBigEndian, "interop"); + $type = -99; + } else { + $value = null; + } + } elseif (isset($this->_info['exif'][$name])) { + $origValue =& $this->_info['exif'][$name]; + + // This makes it easier to process variable size elements + if (!is_array($origValue) || isset($origValue['val'])) { + unset($origValue); // Break the reference + $origValue = array($this->_info['exif'][$name]); + } + $origCount = count($origValue); + + if ($origCount == 0 ) { + $type = -1; // To ignore this field + } + + $value = " "; + + switch ($type) { + case 1: // UBYTE + if ($count == 0) { + $count = $origCount; + } + + $j = 0; + while (($j < $count) && ($j < $origCount)) { + + $this->_putByte($value, $j, $origValue[$j]); + $j++; + } + + while ($j < $count) { + $this->_putByte($value, $j, 0); + $j++; + } + break; + case 2: // ASCII + $v = strval($origValue[0]); + if (($count != 0) && (strlen($v) > $count)) { + $v = substr($v, 0, $count); + } + elseif (($count > 0) && (strlen($v) < $count)) { + $v = str_pad($v, $count, "\0"); + } + + $count = strlen($v); + + $this->_putString($value, 0, $v); + break; + case 3: // USHORT + if ($count == 0) { + $count = $origCount; + } + + $j = 0; + while (($j < $count) && ($j < $origCount)) { + $this->_putShort($value, $j * 2, $origValue[$j], $isBigEndian); + $j++; + } + + while ($j < $count) { + $this->_putShort($value, $j * 2, 0, $isBigEndian); + $j++; + } + break; + case 4: // ULONG + if ($count == 0) { + $count = $origCount; + } + + $j = 0; + while (($j < $count) && ($j < $origCount)) { + $this->_putLong($value, $j * 4, $origValue[$j], $isBigEndian); + $j++; + } + + while ($j < $count) { + $this->_putLong($value, $j * 4, 0, $isBigEndian); + $j++; + } + break; + case 5: // URATIONAL + if ($count == 0) { + $count = $origCount; + } + + $j = 0; + while (($j < $count) && ($j < $origCount)) { + $v = $origValue[$j]; + if (is_array($v)) { + $a = $v['num']; + $b = $v['den']; + } + else { + $a = 0; + $b = 0; + // TODO: Allow other types and convert them + } + $this->_putLong($value, $j * 8, $a, $isBigEndian); + $this->_putLong($value, ($j * 8) + 4, $b, $isBigEndian); + $j++; + } + + while ($j < $count) { + $this->_putLong($value, $j * 8, 0, $isBigEndian); + $this->_putLong($value, ($j * 8) + 4, 0, $isBigEndian); + $j++; + } + break; + case 6: // SBYTE + if ($count == 0) { + $count = $origCount; + } + + $j = 0; + while (($j < $count) && ($j < $origCount)) { + $this->_putByte($value, $j, $origValue[$j]); + $j++; + } + + while ($j < $count) { + $this->_putByte($value, $j, 0); + $j++; + } + break; + case 7: // UNDEFINED + $v = strval($origValue[0]); + if (($count != 0) && (strlen($v) > $count)) { + $v = substr($v, 0, $count); + } + elseif (($count > 0) && (strlen($v) < $count)) { + $v = str_pad($v, $count, "\0"); + } + + $count = strlen($v); + + $this->_putString($value, 0, $v); + break; + case 8: // SSHORT + if ($count == 0) { + $count = $origCount; + } + + $j = 0; + while (($j < $count) && ($j < $origCount)) { + $this->_putShort($value, $j * 2, $origValue[$j], $isBigEndian); + $j++; + } + + while ($j < $count) { + $this->_putShort($value, $j * 2, 0, $isBigEndian); + $j++; + } + break; + case 9: // SLONG + if ($count == 0) { + $count = $origCount; + } + + $j = 0; + while (($j < $count) && ($j < $origCount)) { + $this->_putLong($value, $j * 4, $origValue[$j], $isBigEndian); + $j++; + } + + while ($j < $count) { + $this->_putLong($value, $j * 4, 0, $isBigEndian); + $j++; + } + break; + case 10: // SRATIONAL + if ($count == 0) { + $count = $origCount; + } + + $j = 0; + while (($j < $count) && ($j < $origCount)) { + $v = $origValue[$j]; + if (is_array($v)) { + $a = $v['num']; + $b = $v['den']; + } + else { + $a = 0; + $b = 0; + // TODO: Allow other types and convert them + } + + $this->_putLong($value, $j * 8, $a, $isBigEndian); + $this->_putLong($value, ($j * 8) + 4, $b, $isBigEndian); + $j++; + } + + while ($j < $count) { + $this->_putLong($value, $j * 8, 0, $isBigEndian); + $this->_putLong($value, ($j * 8) + 4, 0, $isBigEndian); + $j++; + } + break; + case 11: // FLOAT + if ($count == 0) { + $count = $origCount; + } + + $j = 0; + while (($j < $count) && ($j < $origCount)) { + $v = strval($origValue[$j]); + if (strlen($v) > 4) { + $v = substr($v, 0, 4); + } + elseif (strlen($v) < 4) { + $v = str_pad($v, 4, "\0"); + } + $this->_putString($value, $j * 4, $v); + $j++; + } + + while ($j < $count) { + $this->_putString($value, $j * 4, "\0\0\0\0"); + $j++; + } + break; + case 12: // DFLOAT + if ($count == 0) { + $count = $origCount; + } + + $j = 0; + while (($j < $count) && ($j < $origCount)) { + $v = strval($origValue[$j]); + if (strlen($v) > 8) { + $v = substr($v, 0, 8); + } + elseif (strlen($v) < 8) { + $v = str_pad($v, 8, "\0"); + } + $this->_putString($value, $j * 8, $v); + $j++; + } + + while ($j < $count) { + $this->_putString($value, $j * 8, "\0\0\0\0\0\0\0\0"); + $j++; + } + break; + default: + $value = null; + break; + } + } + + if ($value != null) { + $ifdEntries[$entryCount] = array(); + $ifdEntries[$entryCount]['tag'] = $tag; + $ifdEntries[$entryCount]['type'] = $type; + $ifdEntries[$entryCount]['count'] = $count; + $ifdEntries[$entryCount]['value'] = $value; + + $entryCount++; + } + } + + return $ifdEntries; + } + + /*************************************************************/ + function _parseMarkerAdobe() { + if (!isset($this->_markers)) { + $this->_readJPEG(); + } + + if ($this->_markers == null) { + return false; + } + + $data = null; + $count = count($this->_markers); + for ($i = 0; $i < $count; $i++) { + if ($this->_markers[$i]['marker'] == 0xED) { + $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 14); + if ($signature == "Photoshop 3.0\0") { + $data =& $this->_markers[$i]['data']; + break; + } + } + } + + if ($data == null) { + $this->_info['adobe'] = false; + $this->_info['iptc'] = false; + return false; + } + $pos = 14; + $this->_info['adobe'] = array(); + $this->_info['adobe']['raw'] = array(); + $this->_info['iptc'] = array(); + + $datasize = strlen($data); + + while ($pos < $datasize) { + $signature = $this->_getFixedString($data, $pos, 4); + if ($signature != '8BIM') + return false; + $pos += 4; + + $type = $this->_getShort($data, $pos); + $pos += 2; + + $strlen = $this->_getByte($data, $pos); + $pos += 1; + $header = ''; + for ($i = 0; $i < $strlen; $i++) { + $header .= $data{$pos + $i}; + } + $pos += $strlen + 1 - ($strlen % 2); // The string is padded to even length, counting the length byte itself + + $length = $this->_getLong($data, $pos); + $pos += 4; + + $basePos = $pos; + + switch ($type) { + case 0x0404: // Caption (IPTC Data) + $pos = $this->_readIPTC($data, $pos); + if ($pos == false) + return false; + break; + case 0x040A: // CopyrightFlag + $this->_info['adobe']['CopyrightFlag'] = $this->_getByte($data, $pos); + $pos += $length; + break; + case 0x040B: // ImageURL + $this->_info['adobe']['ImageURL'] = $this->_getFixedString($data, $pos, $length); + $pos += $length; + break; + case 0x040C: // Thumbnail + $aux = $this->_getLong($data, $pos); + $pos += 4; + if ($aux == 1) { + $this->_info['adobe']['ThumbnailWidth'] = $this->_getLong($data, $pos); + $pos += 4; + $this->_info['adobe']['ThumbnailHeight'] = $this->_getLong($data, $pos); + $pos += 4; + + $pos += 16; // Skip some data + + $this->_info['adobe']['ThumbnailData'] = $this->_getFixedString($data, $pos, $length - 28); + $pos += $length - 28; + } + break; + default: + break; + } + + // We save all blocks, even those we recognized + $label = sprintf('8BIM_0x%04x', $type); + $this->_info['adobe']['raw'][$label] = array(); + $this->_info['adobe']['raw'][$label]['type'] = $type; + $this->_info['adobe']['raw'][$label]['header'] = $header; + $this->_info['adobe']['raw'][$label]['data'] =& $this->_getFixedString($data, $basePos, $length); + + $pos = $basePos + $length + ($length % 2); // Even padding + } + + } + + /*************************************************************/ + function _readIPTC(&$data, $pos = 0) { + $totalLength = strlen($data); + + $IPTCTags =& $this->_iptcTagNames(); + + while ($pos < ($totalLength - 5)) { + $signature = $this->_getShort($data, $pos); + if ($signature != 0x1C02) + return $pos; + $pos += 2; + + $type = $this->_getByte($data, $pos); + $pos += 1; + $length = $this->_getShort($data, $pos); + $pos += 2; + + $basePos = $pos; + $label = ''; + + if (isset($IPTCTags[$type])) { + $label = $IPTCTags[$type]; + } else { + $label = sprintf('IPTC_0x%02x', $type); + } + + if ($label != '') { + if (isset($this->_info['iptc'][$label])) { + if (!is_array($this->_info['iptc'][$label])) { + $aux = array(); + $aux[0] = $this->_info['iptc'][$label]; + $this->_info['iptc'][$label] = $aux; + } + $this->_info['iptc'][$label][ count($this->_info['iptc'][$label]) ] = $this->_getFixedString($data, $pos, $length); + } else { + $this->_info['iptc'][$label] = $this->_getFixedString($data, $pos, $length); + } + } + + $pos = $basePos + $length; // No padding + } + return $pos; + } + + /*************************************************************/ + function & _createMarkerAdobe() { + if (isset($this->_info['iptc'])) { + if (!isset($this->_info['adobe'])) { + $this->_info['adobe'] = array(); + } + if (!isset($this->_info['adobe']['raw'])) { + $this->_info['adobe']['raw'] = array(); + } + if (!isset($this->_info['adobe']['raw']['8BIM_0x0404'])) { + $this->_info['adobe']['raw']['8BIM_0x0404'] = array(); + } + $this->_info['adobe']['raw']['8BIM_0x0404']['type'] = 0x0404; + $this->_info['adobe']['raw']['8BIM_0x0404']['header'] = "Caption"; + $this->_info['adobe']['raw']['8BIM_0x0404']['data'] =& $this->_writeIPTC(); + } + + if (isset($this->_info['adobe']['raw']) && (count($this->_info['adobe']['raw']) > 0)) { + $data = "Photoshop 3.0\0"; + $pos = 14; + + reset($this->_info['adobe']['raw']); + while (list($key) = each($this->_info['adobe']['raw'])) { + $pos = $this->_write8BIM( + $data, + $pos, + $this->_info['adobe']['raw'][$key]['type'], + $this->_info['adobe']['raw'][$key]['header'], + $this->_info['adobe']['raw'][$key]['data'] ); + } + } + + return $data; + } + + /*************************************************************/ + function _write8BIM(&$data, $pos, $type, $header, &$value) { + $signature = "8BIM"; + + $pos = $this->_putString($data, $pos, $signature); + $pos = $this->_putShort($data, $pos, $type); + + $len = strlen($header); + + $pos = $this->_putByte($data, $pos, $len); + $pos = $this->_putString($data, $pos, $header); + if (($len % 2) == 0) { // Even padding, including the length byte + $pos = $this->_putByte($data, $pos, 0); + } + + $len = strlen($value); + $pos = $this->_putLong($data, $pos, $len); + $pos = $this->_putString($data, $pos, $value); + if (($len % 2) != 0) { // Even padding + $pos = $this->_putByte($data, $pos, 0); + } + return $pos; + } + + /*************************************************************/ + function & _writeIPTC() { + $data = " "; + $pos = 0; + + $IPTCNames =& $this->_iptcNameTags(); + + reset($this->_info['iptc']); + + while (list($label) = each($this->_info['iptc'])) { + $value =& $this->_info['iptc'][$label]; + $type = -1; + + if (isset($IPTCNames[$label])) { + $type = $IPTCNames[$label]; + } + elseif (substr($label, 0, 7) == "IPTC_0x") { + $type = hexdec(substr($label, 7, 2)); + } + + if ($type != -1) { + if (is_array($value)) { + $vcnt = count($value); + for ($i = 0; $i < $vcnt; $i++) { + $pos = $this->_writeIPTCEntry($data, $pos, $type, $value[$i]); + } + } + else { + $pos = $this->_writeIPTCEntry($data, $pos, $type, $value); + } + } + } + + return $data; + } + + /*************************************************************/ + function _writeIPTCEntry(&$data, $pos, $type, &$value) { + $pos = $this->_putShort($data, $pos, 0x1C02); + $pos = $this->_putByte($data, $pos, $type); + $pos = $this->_putShort($data, $pos, strlen($value)); + $pos = $this->_putString($data, $pos, $value); + + return $pos; + } + + /*************************************************************/ + function _exifTagNames($mode) { + $tags = array(); + + if ($mode == 'ifd0') { + $tags[0x010E] = 'ImageDescription'; + $tags[0x010F] = 'Make'; + $tags[0x0110] = 'Model'; + $tags[0x0112] = 'Orientation'; + $tags[0x011A] = 'XResolution'; + $tags[0x011B] = 'YResolution'; + $tags[0x0128] = 'ResolutionUnit'; + $tags[0x0131] = 'Software'; + $tags[0x0132] = 'DateTime'; + $tags[0x013B] = 'Artist'; + $tags[0x013E] = 'WhitePoint'; + $tags[0x013F] = 'PrimaryChromaticities'; + $tags[0x0211] = 'YCbCrCoefficients'; + $tags[0x0212] = 'YCbCrSubSampling'; + $tags[0x0213] = 'YCbCrPositioning'; + $tags[0x0214] = 'ReferenceBlackWhite'; + $tags[0x8298] = 'Copyright'; + $tags[0x8769] = 'ExifIFDOffset'; + $tags[0x8825] = 'GPSIFDOffset'; + } + if ($mode == 'ifd1') { + $tags[0x00FE] = 'TIFFNewSubfileType'; + $tags[0x00FF] = 'TIFFSubfileType'; + $tags[0x0100] = 'TIFFImageWidth'; + $tags[0x0101] = 'TIFFImageHeight'; + $tags[0x0102] = 'TIFFBitsPerSample'; + $tags[0x0103] = 'TIFFCompression'; + $tags[0x0106] = 'TIFFPhotometricInterpretation'; + $tags[0x0107] = 'TIFFThreshholding'; + $tags[0x0108] = 'TIFFCellWidth'; + $tags[0x0109] = 'TIFFCellLength'; + $tags[0x010A] = 'TIFFFillOrder'; + $tags[0x010E] = 'TIFFImageDescription'; + $tags[0x010F] = 'TIFFMake'; + $tags[0x0110] = 'TIFFModel'; + $tags[0x0111] = 'TIFFStripOffsets'; + $tags[0x0112] = 'TIFFOrientation'; + $tags[0x0115] = 'TIFFSamplesPerPixel'; + $tags[0x0116] = 'TIFFRowsPerStrip'; + $tags[0x0117] = 'TIFFStripByteCounts'; + $tags[0x0118] = 'TIFFMinSampleValue'; + $tags[0x0119] = 'TIFFMaxSampleValue'; + $tags[0x011A] = 'TIFFXResolution'; + $tags[0x011B] = 'TIFFYResolution'; + $tags[0x011C] = 'TIFFPlanarConfiguration'; + $tags[0x0122] = 'TIFFGrayResponseUnit'; + $tags[0x0123] = 'TIFFGrayResponseCurve'; + $tags[0x0128] = 'TIFFResolutionUnit'; + $tags[0x0131] = 'TIFFSoftware'; + $tags[0x0132] = 'TIFFDateTime'; + $tags[0x013B] = 'TIFFArtist'; + $tags[0x013C] = 'TIFFHostComputer'; + $tags[0x0140] = 'TIFFColorMap'; + $tags[0x0152] = 'TIFFExtraSamples'; + $tags[0x0201] = 'TIFFJFIFOffset'; + $tags[0x0202] = 'TIFFJFIFLength'; + $tags[0x0211] = 'TIFFYCbCrCoefficients'; + $tags[0x0212] = 'TIFFYCbCrSubSampling'; + $tags[0x0213] = 'TIFFYCbCrPositioning'; + $tags[0x0214] = 'TIFFReferenceBlackWhite'; + $tags[0x8298] = 'TIFFCopyright'; + $tags[0x9286] = 'TIFFUserComment'; + } elseif ($mode == 'exif') { + $tags[0x829A] = 'ExposureTime'; + $tags[0x829D] = 'FNumber'; + $tags[0x8822] = 'ExposureProgram'; + $tags[0x8824] = 'SpectralSensitivity'; + $tags[0x8827] = 'ISOSpeedRatings'; + $tags[0x8828] = 'OECF'; + $tags[0x9000] = 'EXIFVersion'; + $tags[0x9003] = 'DatetimeOriginal'; + $tags[0x9004] = 'DatetimeDigitized'; + $tags[0x9101] = 'ComponentsConfiguration'; + $tags[0x9102] = 'CompressedBitsPerPixel'; + $tags[0x9201] = 'ShutterSpeedValue'; + $tags[0x9202] = 'ApertureValue'; + $tags[0x9203] = 'BrightnessValue'; + $tags[0x9204] = 'ExposureBiasValue'; + $tags[0x9205] = 'MaxApertureValue'; + $tags[0x9206] = 'SubjectDistance'; + $tags[0x9207] = 'MeteringMode'; + $tags[0x9208] = 'LightSource'; + $tags[0x9209] = 'Flash'; + $tags[0x920A] = 'FocalLength'; + $tags[0x927C] = 'MakerNote'; + $tags[0x9286] = 'UserComment'; + $tags[0x9290] = 'SubSecTime'; + $tags[0x9291] = 'SubSecTimeOriginal'; + $tags[0x9292] = 'SubSecTimeDigitized'; + $tags[0xA000] = 'FlashPixVersion'; + $tags[0xA001] = 'ColorSpace'; + $tags[0xA002] = 'PixelXDimension'; + $tags[0xA003] = 'PixelYDimension'; + $tags[0xA004] = 'RelatedSoundFile'; + $tags[0xA005] = 'InteropIFDOffset'; + $tags[0xA20B] = 'FlashEnergy'; + $tags[0xA20C] = 'SpatialFrequencyResponse'; + $tags[0xA20E] = 'FocalPlaneXResolution'; + $tags[0xA20F] = 'FocalPlaneYResolution'; + $tags[0xA210] = 'FocalPlaneResolutionUnit'; + $tags[0xA214] = 'SubjectLocation'; + $tags[0xA215] = 'ExposureIndex'; + $tags[0xA217] = 'SensingMethod'; + $tags[0xA300] = 'FileSource'; + $tags[0xA301] = 'SceneType'; + $tags[0xA302] = 'CFAPattern'; + } elseif ($mode == 'interop') { + $tags[0x0001] = 'InteroperabilityIndex'; + $tags[0x0002] = 'InteroperabilityVersion'; + $tags[0x1000] = 'RelatedImageFileFormat'; + $tags[0x1001] = 'RelatedImageWidth'; + $tags[0x1002] = 'RelatedImageLength'; + } elseif ($mode == 'gps') { + $tags[0x0000] = 'GPSVersionID'; + $tags[0x0001] = 'GPSLatitudeRef'; + $tags[0x0002] = 'GPSLatitude'; + $tags[0x0003] = 'GPSLongitudeRef'; + $tags[0x0004] = 'GPSLongitude'; + $tags[0x0005] = 'GPSAltitudeRef'; + $tags[0x0006] = 'GPSAltitude'; + $tags[0x0007] = 'GPSTimeStamp'; + $tags[0x0008] = 'GPSSatellites'; + $tags[0x0009] = 'GPSStatus'; + $tags[0x000A] = 'GPSMeasureMode'; + $tags[0x000B] = 'GPSDOP'; + $tags[0x000C] = 'GPSSpeedRef'; + $tags[0x000D] = 'GPSSpeed'; + $tags[0x000E] = 'GPSTrackRef'; + $tags[0x000F] = 'GPSTrack'; + $tags[0x0010] = 'GPSImgDirectionRef'; + $tags[0x0011] = 'GPSImgDirection'; + $tags[0x0012] = 'GPSMapDatum'; + $tags[0x0013] = 'GPSDestLatitudeRef'; + $tags[0x0014] = 'GPSDestLatitude'; + $tags[0x0015] = 'GPSDestLongitudeRef'; + $tags[0x0016] = 'GPSDestLongitude'; + $tags[0x0017] = 'GPSDestBearingRef'; + $tags[0x0018] = 'GPSDestBearing'; + $tags[0x0019] = 'GPSDestDistanceRef'; + $tags[0x001A] = 'GPSDestDistance'; + } + + return $tags; + } + + /*************************************************************/ + function _exifTagTypes($mode) { + $tags = array(); + + if ($mode == 'ifd0') { + $tags[0x010E] = array(2, 0); // ImageDescription -> ASCII, Any + $tags[0x010F] = array(2, 0); // Make -> ASCII, Any + $tags[0x0110] = array(2, 0); // Model -> ASCII, Any + $tags[0x0112] = array(3, 1); // Orientation -> SHORT, 1 + $tags[0x011A] = array(5, 1); // XResolution -> RATIONAL, 1 + $tags[0x011B] = array(5, 1); // YResolution -> RATIONAL, 1 + $tags[0x0128] = array(3, 1); // ResolutionUnit -> SHORT + $tags[0x0131] = array(2, 0); // Software -> ASCII, Any + $tags[0x0132] = array(2, 20); // DateTime -> ASCII, 20 + $tags[0x013B] = array(2, 0); // Artist -> ASCII, Any + $tags[0x013E] = array(5, 2); // WhitePoint -> RATIONAL, 2 + $tags[0x013F] = array(5, 6); // PrimaryChromaticities -> RATIONAL, 6 + $tags[0x0211] = array(5, 3); // YCbCrCoefficients -> RATIONAL, 3 + $tags[0x0212] = array(3, 2); // YCbCrSubSampling -> SHORT, 2 + $tags[0x0213] = array(3, 1); // YCbCrPositioning -> SHORT, 1 + $tags[0x0214] = array(5, 6); // ReferenceBlackWhite -> RATIONAL, 6 + $tags[0x8298] = array(2, 0); // Copyright -> ASCII, Any + $tags[0x8769] = array(4, 1); // ExifIFDOffset -> LONG, 1 + $tags[0x8825] = array(4, 1); // GPSIFDOffset -> LONG, 1 + } + if ($mode == 'ifd1') { + $tags[0x00FE] = array(4, 1); // TIFFNewSubfileType -> LONG, 1 + $tags[0x00FF] = array(3, 1); // TIFFSubfileType -> SHORT, 1 + $tags[0x0100] = array(4, 1); // TIFFImageWidth -> LONG (or SHORT), 1 + $tags[0x0101] = array(4, 1); // TIFFImageHeight -> LONG (or SHORT), 1 + $tags[0x0102] = array(3, 3); // TIFFBitsPerSample -> SHORT, 3 + $tags[0x0103] = array(3, 1); // TIFFCompression -> SHORT, 1 + $tags[0x0106] = array(3, 1); // TIFFPhotometricInterpretation -> SHORT, 1 + $tags[0x0107] = array(3, 1); // TIFFThreshholding -> SHORT, 1 + $tags[0x0108] = array(3, 1); // TIFFCellWidth -> SHORT, 1 + $tags[0x0109] = array(3, 1); // TIFFCellLength -> SHORT, 1 + $tags[0x010A] = array(3, 1); // TIFFFillOrder -> SHORT, 1 + $tags[0x010E] = array(2, 0); // TIFFImageDescription -> ASCII, Any + $tags[0x010F] = array(2, 0); // TIFFMake -> ASCII, Any + $tags[0x0110] = array(2, 0); // TIFFModel -> ASCII, Any + $tags[0x0111] = array(4, 0); // TIFFStripOffsets -> LONG (or SHORT), Any (one per strip) + $tags[0x0112] = array(3, 1); // TIFFOrientation -> SHORT, 1 + $tags[0x0115] = array(3, 1); // TIFFSamplesPerPixel -> SHORT, 1 + $tags[0x0116] = array(4, 1); // TIFFRowsPerStrip -> LONG (or SHORT), 1 + $tags[0x0117] = array(4, 0); // TIFFStripByteCounts -> LONG (or SHORT), Any (one per strip) + $tags[0x0118] = array(3, 0); // TIFFMinSampleValue -> SHORT, Any (SamplesPerPixel) + $tags[0x0119] = array(3, 0); // TIFFMaxSampleValue -> SHORT, Any (SamplesPerPixel) + $tags[0x011A] = array(5, 1); // TIFFXResolution -> RATIONAL, 1 + $tags[0x011B] = array(5, 1); // TIFFYResolution -> RATIONAL, 1 + $tags[0x011C] = array(3, 1); // TIFFPlanarConfiguration -> SHORT, 1 + $tags[0x0122] = array(3, 1); // TIFFGrayResponseUnit -> SHORT, 1 + $tags[0x0123] = array(3, 0); // TIFFGrayResponseCurve -> SHORT, Any (2^BitsPerSample) + $tags[0x0128] = array(3, 1); // TIFFResolutionUnit -> SHORT, 1 + $tags[0x0131] = array(2, 0); // TIFFSoftware -> ASCII, Any + $tags[0x0132] = array(2, 20); // TIFFDateTime -> ASCII, 20 + $tags[0x013B] = array(2, 0); // TIFFArtist -> ASCII, Any + $tags[0x013C] = array(2, 0); // TIFFHostComputer -> ASCII, Any + $tags[0x0140] = array(3, 0); // TIFFColorMap -> SHORT, Any (3 * 2^BitsPerSample) + $tags[0x0152] = array(3, 0); // TIFFExtraSamples -> SHORT, Any (SamplesPerPixel - 3) + $tags[0x0201] = array(4, 1); // TIFFJFIFOffset -> LONG, 1 + $tags[0x0202] = array(4, 1); // TIFFJFIFLength -> LONG, 1 + $tags[0x0211] = array(5, 3); // TIFFYCbCrCoefficients -> RATIONAL, 3 + $tags[0x0212] = array(3, 2); // TIFFYCbCrSubSampling -> SHORT, 2 + $tags[0x0213] = array(3, 1); // TIFFYCbCrPositioning -> SHORT, 1 + $tags[0x0214] = array(5, 6); // TIFFReferenceBlackWhite -> RATIONAL, 6 + $tags[0x8298] = array(2, 0); // TIFFCopyright -> ASCII, Any + $tags[0x9286] = array(2, 0); // TIFFUserComment -> ASCII, Any + } elseif ($mode == 'exif') { + $tags[0x829A] = array(5, 1); // ExposureTime -> RATIONAL, 1 + $tags[0x829D] = array(5, 1); // FNumber -> RATIONAL, 1 + $tags[0x8822] = array(3, 1); // ExposureProgram -> SHORT, 1 + $tags[0x8824] = array(2, 0); // SpectralSensitivity -> ASCII, Any + $tags[0x8827] = array(3, 0); // ISOSpeedRatings -> SHORT, Any + $tags[0x8828] = array(7, 0); // OECF -> UNDEFINED, Any + $tags[0x9000] = array(7, 4); // EXIFVersion -> UNDEFINED, 4 + $tags[0x9003] = array(2, 20); // DatetimeOriginal -> ASCII, 20 + $tags[0x9004] = array(2, 20); // DatetimeDigitized -> ASCII, 20 + $tags[0x9101] = array(7, 4); // ComponentsConfiguration -> UNDEFINED, 4 + $tags[0x9102] = array(5, 1); // CompressedBitsPerPixel -> RATIONAL, 1 + $tags[0x9201] = array(10, 1); // ShutterSpeedValue -> SRATIONAL, 1 + $tags[0x9202] = array(5, 1); // ApertureValue -> RATIONAL, 1 + $tags[0x9203] = array(10, 1); // BrightnessValue -> SRATIONAL, 1 + $tags[0x9204] = array(10, 1); // ExposureBiasValue -> SRATIONAL, 1 + $tags[0x9205] = array(5, 1); // MaxApertureValue -> RATIONAL, 1 + $tags[0x9206] = array(5, 1); // SubjectDistance -> RATIONAL, 1 + $tags[0x9207] = array(3, 1); // MeteringMode -> SHORT, 1 + $tags[0x9208] = array(3, 1); // LightSource -> SHORT, 1 + $tags[0x9209] = array(3, 1); // Flash -> SHORT, 1 + $tags[0x920A] = array(5, 1); // FocalLength -> RATIONAL, 1 + $tags[0x927C] = array(7, 0); // MakerNote -> UNDEFINED, Any + $tags[0x9286] = array(7, 0); // UserComment -> UNDEFINED, Any + $tags[0x9290] = array(2, 0); // SubSecTime -> ASCII, Any + $tags[0x9291] = array(2, 0); // SubSecTimeOriginal -> ASCII, Any + $tags[0x9292] = array(2, 0); // SubSecTimeDigitized -> ASCII, Any + $tags[0xA000] = array(7, 4); // FlashPixVersion -> UNDEFINED, 4 + $tags[0xA001] = array(3, 1); // ColorSpace -> SHORT, 1 + $tags[0xA002] = array(4, 1); // PixelXDimension -> LONG (or SHORT), 1 + $tags[0xA003] = array(4, 1); // PixelYDimension -> LONG (or SHORT), 1 + $tags[0xA004] = array(2, 13); // RelatedSoundFile -> ASCII, 13 + $tags[0xA005] = array(4, 1); // InteropIFDOffset -> LONG, 1 + $tags[0xA20B] = array(5, 1); // FlashEnergy -> RATIONAL, 1 + $tags[0xA20C] = array(7, 0); // SpatialFrequencyResponse -> UNDEFINED, Any + $tags[0xA20E] = array(5, 1); // FocalPlaneXResolution -> RATIONAL, 1 + $tags[0xA20F] = array(5, 1); // FocalPlaneYResolution -> RATIONAL, 1 + $tags[0xA210] = array(3, 1); // FocalPlaneResolutionUnit -> SHORT, 1 + $tags[0xA214] = array(3, 2); // SubjectLocation -> SHORT, 2 + $tags[0xA215] = array(5, 1); // ExposureIndex -> RATIONAL, 1 + $tags[0xA217] = array(3, 1); // SensingMethod -> SHORT, 1 + $tags[0xA300] = array(7, 1); // FileSource -> UNDEFINED, 1 + $tags[0xA301] = array(7, 1); // SceneType -> UNDEFINED, 1 + $tags[0xA302] = array(7, 0); // CFAPattern -> UNDEFINED, Any + } elseif ($mode == 'interop') { + $tags[0x0001] = array(2, 0); // InteroperabilityIndex -> ASCII, Any + $tags[0x0002] = array(7, 4); // InteroperabilityVersion -> UNKNOWN, 4 + $tags[0x1000] = array(2, 0); // RelatedImageFileFormat -> ASCII, Any + $tags[0x1001] = array(4, 1); // RelatedImageWidth -> LONG (or SHORT), 1 + $tags[0x1002] = array(4, 1); // RelatedImageLength -> LONG (or SHORT), 1 + } elseif ($mode == 'gps') { + $tags[0x0000] = array(1, 4); // GPSVersionID -> BYTE, 4 + $tags[0x0001] = array(2, 2); // GPSLatitudeRef -> ASCII, 2 + $tags[0x0002] = array(5, 3); // GPSLatitude -> RATIONAL, 3 + $tags[0x0003] = array(2, 2); // GPSLongitudeRef -> ASCII, 2 + $tags[0x0004] = array(5, 3); // GPSLongitude -> RATIONAL, 3 + $tags[0x0005] = array(2, 2); // GPSAltitudeRef -> ASCII, 2 + $tags[0x0006] = array(5, 1); // GPSAltitude -> RATIONAL, 1 + $tags[0x0007] = array(5, 3); // GPSTimeStamp -> RATIONAL, 3 + $tags[0x0008] = array(2, 0); // GPSSatellites -> ASCII, Any + $tags[0x0009] = array(2, 2); // GPSStatus -> ASCII, 2 + $tags[0x000A] = array(2, 2); // GPSMeasureMode -> ASCII, 2 + $tags[0x000B] = array(5, 1); // GPSDOP -> RATIONAL, 1 + $tags[0x000C] = array(2, 2); // GPSSpeedRef -> ASCII, 2 + $tags[0x000D] = array(5, 1); // GPSSpeed -> RATIONAL, 1 + $tags[0x000E] = array(2, 2); // GPSTrackRef -> ASCII, 2 + $tags[0x000F] = array(5, 1); // GPSTrack -> RATIONAL, 1 + $tags[0x0010] = array(2, 2); // GPSImgDirectionRef -> ASCII, 2 + $tags[0x0011] = array(5, 1); // GPSImgDirection -> RATIONAL, 1 + $tags[0x0012] = array(2, 0); // GPSMapDatum -> ASCII, Any + $tags[0x0013] = array(2, 2); // GPSDestLatitudeRef -> ASCII, 2 + $tags[0x0014] = array(5, 3); // GPSDestLatitude -> RATIONAL, 3 + $tags[0x0015] = array(2, 2); // GPSDestLongitudeRef -> ASCII, 2 + $tags[0x0016] = array(5, 3); // GPSDestLongitude -> RATIONAL, 3 + $tags[0x0017] = array(2, 2); // GPSDestBearingRef -> ASCII, 2 + $tags[0x0018] = array(5, 1); // GPSDestBearing -> RATIONAL, 1 + $tags[0x0019] = array(2, 2); // GPSDestDistanceRef -> ASCII, 2 + $tags[0x001A] = array(5, 1); // GPSDestDistance -> RATIONAL, 1 + } + + return $tags; + } + + /*************************************************************/ + function _exifNameTags($mode) { + $tags = $this->_exifTagNames($mode); + return $this->_names2Tags($tags); + } + + /*************************************************************/ + function _iptcTagNames() { + $tags = array(); + $tags[0x14] = 'SuplementalCategories'; + $tags[0x19] = 'Keywords'; + $tags[0x78] = 'Caption'; + $tags[0x7A] = 'CaptionWriter'; + $tags[0x69] = 'Headline'; + $tags[0x28] = 'SpecialInstructions'; + $tags[0x0F] = 'Category'; + $tags[0x50] = 'Byline'; + $tags[0x55] = 'BylineTitle'; + $tags[0x6E] = 'Credit'; + $tags[0x73] = 'Source'; + $tags[0x74] = 'CopyrightNotice'; + $tags[0x05] = 'ObjectName'; + $tags[0x5A] = 'City'; + $tags[0x5C] = 'Sublocation'; + $tags[0x5F] = 'ProvinceState'; + $tags[0x65] = 'CountryName'; + $tags[0x67] = 'OriginalTransmissionReference'; + $tags[0x37] = 'DateCreated'; + $tags[0x0A] = 'CopyrightFlag'; + + return $tags; + } + + /*************************************************************/ + function & _iptcNameTags() { + $tags = $this->_iptcTagNames(); + return $this->_names2Tags($tags); + } + + /*************************************************************/ + function _names2Tags($tags2Names) { + $names2Tags = array(); + reset($tags2Names); + while (list($tag, $name) = each($tags2Names)) { + $names2Tags[$name] = $tag; + } + + return $names2Tags; + } + + /*************************************************************/ + function _getByte(&$data, $pos) { + return ord($data{$pos}); + } + + /*************************************************************/ + function _putByte(&$data, $pos, $val) { + $val = intval($val); + + $data{$pos} = chr($val); + + return $pos + 1; + } + + /*************************************************************/ + function _getShort(&$data, $pos, $bigEndian = true) { + if ($bigEndian) { + return (ord($data{$pos}) << 8) + + ord($data{$pos + 1}); + } else { + return ord($data{$pos}) + + (ord($data{$pos + 1}) << 8); + } + } + + /*************************************************************/ + function _putShort(&$data, $pos = 0, $val = 0, $bigEndian = true) { + $val = intval($val); + + if ($bigEndian) { + $data{$pos + 0} = chr(($val & 0x0000FF00) >> 8); + $data{$pos + 1} = chr(($val & 0x000000FF) >> 0); + } else { + $data{$pos + 0} = chr(($val & 0x00FF) >> 0); + $data{$pos + 1} = chr(($val & 0xFF00) >> 8); + } + + return $pos + 2; + } + + /*************************************************************/ + function _getLong(&$data, $pos, $bigEndian = true) { + if ($bigEndian) { + return (ord($data{$pos}) << 24) + + (ord($data{$pos + 1}) << 16) + + (ord($data{$pos + 2}) << 8) + + ord($data{$pos + 3}); + } else { + return ord($data{$pos}) + + (ord($data{$pos + 1}) << 8) + + (ord($data{$pos + 2}) << 16) + + (ord($data{$pos + 3}) << 24); + } + } + + /*************************************************************/ + function _putLong(&$data, $pos, $val, $bigEndian = true) { + $val = intval($val); + + if ($bigEndian) { + $data{$pos + 0} = chr(($val & 0xFF000000) >> 24); + $data{$pos + 1} = chr(($val & 0x00FF0000) >> 16); + $data{$pos + 2} = chr(($val & 0x0000FF00) >> 8); + $data{$pos + 3} = chr(($val & 0x000000FF) >> 0); + } else { + $data{$pos + 0} = chr(($val & 0x000000FF) >> 0); + $data{$pos + 1} = chr(($val & 0x0000FF00) >> 8); + $data{$pos + 2} = chr(($val & 0x00FF0000) >> 16); + $data{$pos + 3} = chr(($val & 0xFF000000) >> 24); + } + + return $pos + 4; + } + + /*************************************************************/ + function & _getNullString(&$data, $pos) { + $str = ''; + $max = strlen($data); + + while ($pos < $max) { + if (ord($data{$pos}) == 0) { + return $str; + } else { + $str .= $data{$pos}; + } + $pos++; + } + + return $str; + } + + /*************************************************************/ + function & _getFixedString(&$data, $pos, $length = -1) { + if ($length == -1) { + $length = strlen($data) - $pos; + } + + return substr($data, $pos, $length); + } + + /*************************************************************/ + function _putString(&$data, $pos, &$str) { + $len = strlen($str); + for ($i = 0; $i < $len; $i++) { + $data{$pos + $i} = $str{$i}; + } + + return $pos + $len; + } + + /*************************************************************/ + function _hexDump(&$data, $start = 0, $length = -1) { + if (($length == -1) || (($length + $start) > strlen($data))) { + $end = strlen($data); + } else { + $end = $start + $length; + } + + $ascii = ''; + $count = 0; + + echo "\n"; + + while ($start < $end) { + if (($count % 16) == 0) { + echo sprintf('%04d', $count) . ': '; + } + + $c = ord($data{$start}); + $count++; + $start++; + + $aux = dechex($c); + if (strlen($aux) == 1) + echo '0'; + echo $aux . ' '; + + if ($c == 60) + $ascii .= '<'; + elseif ($c == 62) + $ascii .= '>'; + elseif ($c == 32) + $ascii .= ' '; + elseif ($c > 32) + $ascii .= chr($c); + else + $ascii .= '.'; + + if (($count % 4) == 0) { + echo ' - '; + } + + if (($count % 16) == 0) { + echo ': ' . $ascii . "
\n"; + $ascii = ''; + } + } + + if ($ascii != '') { + while (($count % 16) != 0) { + echo '-- '; + $count++; + if (($count % 4) == 0) { + echo ' - '; + } + } + echo ': ' . $ascii . "
\n"; + } + + echo "
\n"; + } + + /*****************************************************************/ +} + +/* vim: set expandtab tabstop=4 shiftwidth=4: */ diff --git a/inc/PassHash.class.php b/inc/PassHash.class.php new file mode 100644 index 0000000..31493c0 --- /dev/null +++ b/inc/PassHash.class.php @@ -0,0 +1,382 @@ + + * @license LGPL2 + */ +class PassHash { + /** + * Verifies a cleartext password against a crypted hash + * + * The method and salt used for the crypted hash is determined automatically, + * then the clear text password is crypted using the same method. If both hashs + * match true is is returned else false + * + * @author Andreas Gohr + * @return bool + */ + function verify_hash($clear,$hash){ + $method=''; + $salt=''; + $magic=''; + + //determine the used method and salt + $len = strlen($hash); + if(preg_match('/^\$1\$([^\$]{0,8})\$/',$hash,$m)){ + $method = 'smd5'; + $salt = $m[1]; + $magic = '1'; + }elseif(preg_match('/^\$apr1\$([^\$]{0,8})\$/',$hash,$m)){ + $method = 'apr1'; + $salt = $m[1]; + $magic = 'apr1'; + }elseif(preg_match('/^\$P\$(.{31})$/',$hash,$m)){ + $method = 'pmd5'; + $salt = $m[1]; + $magic = 'P'; + }elseif(preg_match('/^\$H\$(.{31})$/',$hash,$m)){ + $method = 'pmd5'; + $salt = $m[1]; + $magic = 'H'; + }elseif(preg_match('/^sha1\$(.{5})\$/',$hash,$m)){ + $method = 'djangosha1'; + $salt = $m[1]; + }elseif(preg_match('/^md5\$(.{5})\$/',$hash,$m)){ + $method = 'djangomd5'; + $salt = $m[1]; + }elseif(substr($hash,0,6) == '{SSHA}'){ + $method = 'ssha'; + $salt = substr(base64_decode(substr($hash, 6)),20); + }elseif($len == 32){ + $method = 'md5'; + }elseif($len == 40){ + $method = 'sha1'; + }elseif($len == 16){ + $method = 'mysql'; + }elseif($len == 41 && $hash[0] == '*'){ + $method = 'my411'; + }elseif($len == 34){ + $method = 'kmd5'; + $salt = $hash; + }else{ + $method = 'crypt'; + $salt = substr($hash,0,2); + } + + //crypt and compare + $call = 'hash_'.$method; + if($this->$call($clear,$salt,$magic) === $hash){ + return true; + } + return false; + } + + /** + * Create a random salt + * + * @param int $len - The length of the salt + */ + public function gen_salt($len=32){ + $salt = ''; + $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + for($i=0;$i<$len;$i++) $salt .= $chars[mt_rand(0,61)]; + return $salt; + } + + /** + * Initialize the passed variable with a salt if needed. + * + * If $salt is not null, the value is kept, but the lenght restriction is + * applied. + * + * @param stringref $salt - The salt, pass null if you want one generated + * @param int $len - The length of the salt + */ + public function init_salt(&$salt,$len=32){ + if(is_null($salt)) $salt = $this->gen_salt($len); + if(strlen($salt) > $len) $salt = substr($salt,0,$len); + } + + // Password hashing methods follow below + + /** + * Password hashing method 'smd5' + * + * Uses salted MD5 hashs. Salt is 8 bytes long. + * + * The same mechanism is used by Apache's 'apr1' method. This will + * fallback to a implementation in pure PHP if MD5 support is not + * available in crypt() + * + * @author Andreas Gohr + * @author + * @link http://de.php.net/manual/en/function.crypt.php#73619 + * @param string $clear - the clear text to hash + * @param string $salt - the salt to use, null for random + * @param string $magic - the hash identifier (apr1 or 1) + * @returns string - hashed password + */ + public function hash_smd5($clear, $salt=null){ + $this->init_salt($salt,8); + + if(defined('CRYPT_MD5') && CRYPT_MD5){ + return crypt($clear,'$1$'.$salt.'$'); + }else{ + // Fall back to PHP-only implementation + return $this->hash_apr1($clear, $salt, '1'); + } + } + + /** + * Password hashing method 'apr1' + * + * Uses salted MD5 hashs. Salt is 8 bytes long. + * + * This is basically the same as smd1 above, but as used by Apache. + * + * @author + * @link http://de.php.net/manual/en/function.crypt.php#73619 + * @param string $clear - the clear text to hash + * @param string $salt - the salt to use, null for random + * @param string $magic - the hash identifier (apr1 or 1) + * @returns string - hashed password + */ + public function hash_apr1($clear, $salt=null, $magic='apr1'){ + $this->init_salt($salt,8); + + $len = strlen($clear); + $text = $clear.'$'.$magic.'$'.$salt; + $bin = pack("H32", md5($clear.$salt.$clear)); + for($i = $len; $i > 0; $i -= 16) { + $text .= substr($bin, 0, min(16, $i)); + } + for($i = $len; $i > 0; $i >>= 1) { + $text .= ($i & 1) ? chr(0) : $clear{0}; + } + $bin = pack("H32", md5($text)); + for($i = 0; $i < 1000; $i++) { + $new = ($i & 1) ? $clear : $bin; + if ($i % 3) $new .= $salt; + if ($i % 7) $new .= $clear; + $new .= ($i & 1) ? $bin : $clear; + $bin = pack("H32", md5($new)); + } + $tmp = ''; + for ($i = 0; $i < 5; $i++) { + $k = $i + 6; + $j = $i + 12; + if ($j == 16) $j = 5; + $tmp = $bin[$i].$bin[$k].$bin[$j].$tmp; + } + $tmp = chr(0).chr(0).$bin[11].$tmp; + $tmp = strtr(strrev(substr(base64_encode($tmp), 2)), + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); + return '$'.$magic.'$'.$salt.'$'.$tmp; + } + + /** + * Password hashing method 'md5' + * + * Uses MD5 hashs. + * + * @param string $clear - the clear text to hash + * @returns string - hashed password + */ + public function hash_md5($clear){ + return md5($clear); + } + + /** + * Password hashing method 'sha1' + * + * Uses SHA1 hashs. + * + * @param string $clear - the clear text to hash + * @returns string - hashed password + */ + public function hash_sha1($clear){ + return sha1($clear); + } + + /** + * Password hashing method 'ssha' as used by LDAP + * + * Uses salted SHA1 hashs. Salt is 4 bytes long. + * + * @param string $clear - the clear text to hash + * @param string $salt - the salt to use, null for random + * @returns string - hashed password + */ + public function hash_ssha($clear, $salt=null){ + $this->init_salt($salt,4); + return '{SSHA}'.base64_encode(pack("H*", sha1($clear.$salt)).$salt); + } + + /** + * Password hashing method 'crypt' + * + * Uses salted crypt hashs. Salt is 2 bytes long. + * + * @param string $clear - the clear text to hash + * @param string $salt - the salt to use, null for random + * @returns string - hashed password + */ + public function hash_crypt($clear, $salt=null){ + $this->init_salt($salt,2); + return crypt($clear,$salt); + } + + /** + * Password hashing method 'mysql' + * + * This method was used by old MySQL systems + * + * @link http://www.php.net/mysql + * @author + * @param string $clear - the clear text to hash + * @returns string - hashed password + */ + public function hash_mysql($clear){ + $nr=0x50305735; + $nr2=0x12345671; + $add=7; + $charArr = preg_split("//", $clear); + foreach ($charArr as $char) { + if (($char == '') || ($char == ' ') || ($char == '\t')) continue; + $charVal = ord($char); + $nr ^= ((($nr & 63) + $add) * $charVal) + ($nr << 8); + $nr2 += ($nr2 << 8) ^ $nr; + $add += $charVal; + } + return sprintf("%08x%08x", ($nr & 0x7fffffff), ($nr2 & 0x7fffffff)); + } + + /** + * Password hashing method 'my411' + * + * Uses SHA1 hashs. This method is used by MySQL 4.11 and above + * + * @param string $clear - the clear text to hash + * @returns string - hashed password + */ + public function hash_my411($clear){ + return '*'.sha1(pack("H*", sha1($clear))); + } + + /** + * Password hashing method 'kmd5' + * + * Uses salted MD5 hashs. + * + * Salt is 2 bytes long, but stored at position 16, so you need to pass at + * least 18 bytes. You can pass the crypted hash as salt. + * + * @param string $clear - the clear text to hash + * @param string $salt - the salt to use, null for random + * @returns string - hashed password + */ + public function hash_kmd5($clear, $salt=null){ + $this->init_salt($salt); + + $key = substr($salt, 16, 2); + $hash1 = strtolower(md5($key . md5($clear))); + $hash2 = substr($hash1, 0, 16) . $key . substr($hash1, 16); + return $hash2; + } + + /** + * Password hashing method 'pmd5' + * + * Uses salted MD5 hashs. Salt is 1+8 bytes long, 1st byte is the + * iteration count when given, for null salts $compute is used. + * + * @param string $clear - the clear text to hash + * @param string $salt - the salt to use, null for random + * @param string $magic - the hash identifier (P or H) + * @param int $compute - the iteration count for new passwords + * @returns string - hashed password + */ + public function hash_pmd5($clear, $salt=null, $magic='P',$compute=8){ + $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + if(is_null($salt)){ + $this->init_salt($salt); + $salt = $itoa64[$compute].$salt; // prefix iteration count + } + $iterc = $salt[0]; // pos 0 of salt is iteration count + $iter = strpos($itoa64,$iterc); + $iter = 1 << $iter; + $salt = substr($salt,1,8); + + // iterate + $hash = md5($salt . $clear, true); + do { + $hash = md5($hash . $clear, true); + } while (--$iter); + + // encode + $output = ''; + $count = 16; + $i = 0; + do { + $value = ord($hash[$i++]); + $output .= $itoa64[$value & 0x3f]; + if ($i < $count) + $value |= ord($hash[$i]) << 8; + $output .= $itoa64[($value >> 6) & 0x3f]; + if ($i++ >= $count) + break; + if ($i < $count) + $value |= ord($hash[$i]) << 16; + $output .= $itoa64[($value >> 12) & 0x3f]; + if ($i++ >= $count) + break; + $output .= $itoa64[($value >> 18) & 0x3f]; + } while ($i < $count); + + return '$'.$magic.'$'.$iterc.$salt.$output; + } + + /** + * Alias for hash_pmd5 + */ + public function hash_hmd5($clear, $salt=null, $magic='H', $compute=8){ + return $this->hash_pmd5($clear, $salt, $magic, $compute); + } + + /** + * Password hashing method 'djangosha1' + * + * Uses salted SHA1 hashs. Salt is 5 bytes long. + * This is used by the Django Python framework + * + * @link http://docs.djangoproject.com/en/dev/topics/auth/#passwords + * @param string $clear - the clear text to hash + * @param string $salt - the salt to use, null for random + * @returns string - hashed password + */ + public function hash_djangosha1($clear, $salt=null){ + $this->init_salt($salt,5); + return 'sha1$'.$salt.'$'.sha1($salt.$clear); + } + + /** + * Password hashing method 'djangomd5' + * + * Uses salted MD5 hashs. Salt is 5 bytes long. + * This is used by the Django Python framework + * + * @link http://docs.djangoproject.com/en/dev/topics/auth/#passwords + * @param string $clear - the clear text to hash + * @param string $salt - the salt to use, null for random + * @returns string - hashed password + */ + public function hash_djangomd5($clear, $salt=null){ + $this->init_salt($salt,5); + return 'md5$'.$salt.'$'.md5($salt.$clear); + } + +} diff --git a/inc/SafeFN.class.php b/inc/SafeFN.class.php new file mode 100644 index 0000000..43b19e9 --- /dev/null +++ b/inc/SafeFN.class.php @@ -0,0 +1,157 @@ + + */ + public function encode($filename) { + return self::unicode_to_safe(utf8_to_unicode($filename)); + } + + /** + * decoding process + * - split the string into substrings at any occurrence of pre or post indicator characters + * - check the first character of the substring + * - if its not a pre_indicator character + * - if previous character was converted, skip over post_indicator character + * - copy codepoint values of remaining characters to the output array + * - clear any converted flag + * (continue to next substring) + * + * _ else (its a pre_indicator character) + * - if string length is 1, copy the post_indicator character to the output array + * (continue to next substring) + * + * - else (string length > 1) + * - skip the pre-indicator character and convert remaining string from base36 to base10 + * - increase codepoint value for non-printable ASCII characters (add 0x20) + * - append codepoint to output array + * (continue to next substring) + * + * @param string $filename a 'safe' encoded ASCII string, + * @return string decoded utf8 representation of $filename + * + * @author Christopher Smith + */ + public function decode($filename) { + return unicode_to_utf8(self::safe_to_unicode(strtolower($filename))); + } + + public function validate_printable_utf8($printable_utf8) { + return !preg_match('#[\x01-\x1f]#',$printable_utf8); + } + + public function validate_safe($safe) { + return !preg_match('#[^'.self::$plain.self::$post_indicator.self::$pre_indicator.']#',$safe); + } + + /** + * convert an array of unicode codepoints into 'safe_filename' format + * + * @param array int $unicode an array of unicode codepoints + * @return string the unicode represented in 'safe_filename' format + * + * @author Christopher Smith + */ + private function unicode_to_safe($unicode) { + + $safe = ''; + $converted = false; + + foreach ($unicode as $codepoint) { + if ($codepoint < 127 && (strpos(self::$plain.self::$post_indicator,chr($codepoint))!==false)) { + if ($converted) { + $safe .= self::$post_indicator; + $converted = false; + } + $safe .= chr($codepoint); + + } else if ($codepoint == ord(self::$pre_indicator)) { + $safe .= self::$pre_indicator; + $converted = true; + } else { + $safe .= self::$pre_indicator.base_convert((string)($codepoint-32),10,36); + $converted = true; + } + } + if($converted) $safe .= self::$post_indicator; + return $safe; + } + + /** + * convert a 'safe_filename' string into an array of unicode codepoints + * + * @param string $safe a filename in 'safe_filename' format + * @return array int an array of unicode codepoints + * + * @author Christopher Smith + */ + private function safe_to_unicode($safe) { + + $unicode = array(); + $split = preg_split('#(?=['.self::$post_indicator.self::$pre_indicator.'])#',$safe,-1,PREG_SPLIT_NO_EMPTY); + + $converted = false; + foreach ($split as $sub) { + if ($sub[0] != self::$pre_indicator) { + // plain (unconverted) characters, optionally starting with a post_indicator + // set initial value to skip any post_indicator + for ($i=($converted?1:0); $i < strlen($sub); $i++) { + $unicode[] = ord($sub[$i]); + } + $converted = false; + } else if (strlen($sub)==1) { + // a pre_indicator character in the real data + $unicode[] = ord($sub); + $converted = true; + } else { + // a single codepoint in base36, adjusted for initial 32 non-printable chars + $unicode[] = 32 + (int)base_convert(substr($sub,1),36,10); + $converted = true; + } + } + + return $unicode; + } + +} diff --git a/inc/SimplePie.php b/inc/SimplePie.php new file mode 100644 index 0000000..10d8141 --- /dev/null +++ b/inc/SimplePie.php @@ -0,0 +1,15299 @@ +' . SIMPLEPIE_NAME . ''); + +/** + * No Autodiscovery + * @see SimplePie::set_autodiscovery_level() + */ +define('SIMPLEPIE_LOCATOR_NONE', 0); + +/** + * Feed Link Element Autodiscovery + * @see SimplePie::set_autodiscovery_level() + */ +define('SIMPLEPIE_LOCATOR_AUTODISCOVERY', 1); + +/** + * Local Feed Extension Autodiscovery + * @see SimplePie::set_autodiscovery_level() + */ +define('SIMPLEPIE_LOCATOR_LOCAL_EXTENSION', 2); + +/** + * Local Feed Body Autodiscovery + * @see SimplePie::set_autodiscovery_level() + */ +define('SIMPLEPIE_LOCATOR_LOCAL_BODY', 4); + +/** + * Remote Feed Extension Autodiscovery + * @see SimplePie::set_autodiscovery_level() + */ +define('SIMPLEPIE_LOCATOR_REMOTE_EXTENSION', 8); + +/** + * Remote Feed Body Autodiscovery + * @see SimplePie::set_autodiscovery_level() + */ +define('SIMPLEPIE_LOCATOR_REMOTE_BODY', 16); + +/** + * All Feed Autodiscovery + * @see SimplePie::set_autodiscovery_level() + */ +define('SIMPLEPIE_LOCATOR_ALL', 31); + +/** + * No known feed type + */ +define('SIMPLEPIE_TYPE_NONE', 0); + +/** + * RSS 0.90 + */ +define('SIMPLEPIE_TYPE_RSS_090', 1); + +/** + * RSS 0.91 (Netscape) + */ +define('SIMPLEPIE_TYPE_RSS_091_NETSCAPE', 2); + +/** + * RSS 0.91 (Userland) + */ +define('SIMPLEPIE_TYPE_RSS_091_USERLAND', 4); + +/** + * RSS 0.91 (both Netscape and Userland) + */ +define('SIMPLEPIE_TYPE_RSS_091', 6); + +/** + * RSS 0.92 + */ +define('SIMPLEPIE_TYPE_RSS_092', 8); + +/** + * RSS 0.93 + */ +define('SIMPLEPIE_TYPE_RSS_093', 16); + +/** + * RSS 0.94 + */ +define('SIMPLEPIE_TYPE_RSS_094', 32); + +/** + * RSS 1.0 + */ +define('SIMPLEPIE_TYPE_RSS_10', 64); + +/** + * RSS 2.0 + */ +define('SIMPLEPIE_TYPE_RSS_20', 128); + +/** + * RDF-based RSS + */ +define('SIMPLEPIE_TYPE_RSS_RDF', 65); + +/** + * Non-RDF-based RSS (truly intended as syndication format) + */ +define('SIMPLEPIE_TYPE_RSS_SYNDICATION', 190); + +/** + * All RSS + */ +define('SIMPLEPIE_TYPE_RSS_ALL', 255); + +/** + * Atom 0.3 + */ +define('SIMPLEPIE_TYPE_ATOM_03', 256); + +/** + * Atom 1.0 + */ +define('SIMPLEPIE_TYPE_ATOM_10', 512); + +/** + * All Atom + */ +define('SIMPLEPIE_TYPE_ATOM_ALL', 768); + +/** + * All feed types + */ +define('SIMPLEPIE_TYPE_ALL', 1023); + +/** + * No construct + */ +define('SIMPLEPIE_CONSTRUCT_NONE', 0); + +/** + * Text construct + */ +define('SIMPLEPIE_CONSTRUCT_TEXT', 1); + +/** + * HTML construct + */ +define('SIMPLEPIE_CONSTRUCT_HTML', 2); + +/** + * XHTML construct + */ +define('SIMPLEPIE_CONSTRUCT_XHTML', 4); + +/** + * base64-encoded construct + */ +define('SIMPLEPIE_CONSTRUCT_BASE64', 8); + +/** + * IRI construct + */ +define('SIMPLEPIE_CONSTRUCT_IRI', 16); + +/** + * A construct that might be HTML + */ +define('SIMPLEPIE_CONSTRUCT_MAYBE_HTML', 32); + +/** + * All constructs + */ +define('SIMPLEPIE_CONSTRUCT_ALL', 63); + +/** + * Don't change case + */ +define('SIMPLEPIE_SAME_CASE', 1); + +/** + * Change to lowercase + */ +define('SIMPLEPIE_LOWERCASE', 2); + +/** + * Change to uppercase + */ +define('SIMPLEPIE_UPPERCASE', 4); + +/** + * PCRE for HTML attributes + */ +define('SIMPLEPIE_PCRE_HTML_ATTRIBUTE', '((?:[\x09\x0A\x0B\x0C\x0D\x20]+[^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"(?:[^"]*)"|\'(?:[^\']*)\'|(?:[^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?)*)[\x09\x0A\x0B\x0C\x0D\x20]*'); + +/** + * PCRE for XML attributes + */ +define('SIMPLEPIE_PCRE_XML_ATTRIBUTE', '((?:\s+(?:(?:[^\s:]+:)?[^\s:]+)\s*=\s*(?:"(?:[^"]*)"|\'(?:[^\']*)\'))*)\s*'); + +/** + * XML Namespace + */ +define('SIMPLEPIE_NAMESPACE_XML', 'http://www.w3.org/XML/1998/namespace'); + +/** + * Atom 1.0 Namespace + */ +define('SIMPLEPIE_NAMESPACE_ATOM_10', 'http://www.w3.org/2005/Atom'); + +/** + * Atom 0.3 Namespace + */ +define('SIMPLEPIE_NAMESPACE_ATOM_03', 'http://purl.org/atom/ns#'); + +/** + * RDF Namespace + */ +define('SIMPLEPIE_NAMESPACE_RDF', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'); + +/** + * RSS 0.90 Namespace + */ +define('SIMPLEPIE_NAMESPACE_RSS_090', 'http://my.netscape.com/rdf/simple/0.9/'); + +/** + * RSS 1.0 Namespace + */ +define('SIMPLEPIE_NAMESPACE_RSS_10', 'http://purl.org/rss/1.0/'); + +/** + * RSS 1.0 Content Module Namespace + */ +define('SIMPLEPIE_NAMESPACE_RSS_10_MODULES_CONTENT', 'http://purl.org/rss/1.0/modules/content/'); + +/** + * RSS 2.0 Namespace + * (Stupid, I know, but I'm certain it will confuse people less with support.) + */ +define('SIMPLEPIE_NAMESPACE_RSS_20', ''); + +/** + * DC 1.0 Namespace + */ +define('SIMPLEPIE_NAMESPACE_DC_10', 'http://purl.org/dc/elements/1.0/'); + +/** + * DC 1.1 Namespace + */ +define('SIMPLEPIE_NAMESPACE_DC_11', 'http://purl.org/dc/elements/1.1/'); + +/** + * W3C Basic Geo (WGS84 lat/long) Vocabulary Namespace + */ +define('SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO', 'http://www.w3.org/2003/01/geo/wgs84_pos#'); + +/** + * GeoRSS Namespace + */ +define('SIMPLEPIE_NAMESPACE_GEORSS', 'http://www.georss.org/georss'); + +/** + * Media RSS Namespace + */ +define('SIMPLEPIE_NAMESPACE_MEDIARSS', 'http://search.yahoo.com/mrss/'); + +/** + * Wrong Media RSS Namespace + */ +define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG', 'http://search.yahoo.com/mrss'); + +/** + * iTunes RSS Namespace + */ +define('SIMPLEPIE_NAMESPACE_ITUNES', 'http://www.itunes.com/dtds/podcast-1.0.dtd'); + +/** + * XHTML Namespace + */ +define('SIMPLEPIE_NAMESPACE_XHTML', 'http://www.w3.org/1999/xhtml'); + +/** + * IANA Link Relations Registry + */ +define('SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY', 'http://www.iana.org/assignments/relation/'); + +/** + * Whether we're running on PHP5 + */ +define('SIMPLEPIE_PHP5', version_compare(PHP_VERSION, '5.0.0', '>=')); + +/** + * No file source + */ +define('SIMPLEPIE_FILE_SOURCE_NONE', 0); + +/** + * Remote file source + */ +define('SIMPLEPIE_FILE_SOURCE_REMOTE', 1); + +/** + * Local file source + */ +define('SIMPLEPIE_FILE_SOURCE_LOCAL', 2); + +/** + * fsockopen() file source + */ +define('SIMPLEPIE_FILE_SOURCE_FSOCKOPEN', 4); + +/** + * cURL file source + */ +define('SIMPLEPIE_FILE_SOURCE_CURL', 8); + +/** + * file_get_contents() file source + */ +define('SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS', 16); + +/** + * SimplePie + * + * @package SimplePie + */ +class SimplePie +{ + /** + * @var array Raw data + * @access private + */ + var $data = array(); + + /** + * @var mixed Error string + * @access private + */ + var $error; + + /** + * @var object Instance of SimplePie_Sanitize (or other class) + * @see SimplePie::set_sanitize_class() + * @access private + */ + var $sanitize; + + /** + * @var string SimplePie Useragent + * @see SimplePie::set_useragent() + * @access private + */ + var $useragent = SIMPLEPIE_USERAGENT; + + /** + * @var string Feed URL + * @see SimplePie::set_feed_url() + * @access private + */ + var $feed_url; + + /** + * @var object Instance of SimplePie_File to use as a feed + * @see SimplePie::set_file() + * @access private + */ + var $file; + + /** + * @var string Raw feed data + * @see SimplePie::set_raw_data() + * @access private + */ + var $raw_data; + + /** + * @var int Timeout for fetching remote files + * @see SimplePie::set_timeout() + * @access private + */ + var $timeout = 10; + + /** + * @var bool Forces fsockopen() to be used for remote files instead + * of cURL, even if a new enough version is installed + * @see SimplePie::force_fsockopen() + * @access private + */ + var $force_fsockopen = false; + + /** + * @var bool Force the given data/URL to be treated as a feed no matter what + * it appears like + * @see SimplePie::force_feed() + * @access private + */ + var $force_feed = false; + + /** + * @var bool Enable/Disable XML dump + * @see SimplePie::enable_xml_dump() + * @access private + */ + var $xml_dump = false; + + /** + * @var bool Enable/Disable Caching + * @see SimplePie::enable_cache() + * @access private + */ + var $cache = true; + + /** + * @var int Cache duration (in seconds) + * @see SimplePie::set_cache_duration() + * @access private + */ + var $cache_duration = 3600; + + /** + * @var int Auto-discovery cache duration (in seconds) + * @see SimplePie::set_autodiscovery_cache_duration() + * @access private + */ + var $autodiscovery_cache_duration = 604800; // 7 Days. + + /** + * @var string Cache location (relative to executing script) + * @see SimplePie::set_cache_location() + * @access private + */ + var $cache_location = './cache'; + + /** + * @var string Function that creates the cache filename + * @see SimplePie::set_cache_name_function() + * @access private + */ + var $cache_name_function = 'md5'; + + /** + * @var bool Reorder feed by date descending + * @see SimplePie::enable_order_by_date() + * @access private + */ + var $order_by_date = true; + + /** + * @var mixed Force input encoding to be set to the follow value + * (false, or anything type-cast to false, disables this feature) + * @see SimplePie::set_input_encoding() + * @access private + */ + var $input_encoding = false; + + /** + * @var int Feed Autodiscovery Level + * @see SimplePie::set_autodiscovery_level() + * @access private + */ + var $autodiscovery = SIMPLEPIE_LOCATOR_ALL; + + /** + * @var string Class used for caching feeds + * @see SimplePie::set_cache_class() + * @access private + */ + var $cache_class = 'SimplePie_Cache'; + + /** + * @var string Class used for locating feeds + * @see SimplePie::set_locator_class() + * @access private + */ + var $locator_class = 'SimplePie_Locator'; + + /** + * @var string Class used for parsing feeds + * @see SimplePie::set_parser_class() + * @access private + */ + var $parser_class = 'SimplePie_Parser'; + + /** + * @var string Class used for fetching feeds + * @see SimplePie::set_file_class() + * @access private + */ + var $file_class = 'SimplePie_File'; + + /** + * @var string Class used for items + * @see SimplePie::set_item_class() + * @access private + */ + var $item_class = 'SimplePie_Item'; + + /** + * @var string Class used for authors + * @see SimplePie::set_author_class() + * @access private + */ + var $author_class = 'SimplePie_Author'; + + /** + * @var string Class used for categories + * @see SimplePie::set_category_class() + * @access private + */ + var $category_class = 'SimplePie_Category'; + + /** + * @var string Class used for enclosures + * @see SimplePie::set_enclosures_class() + * @access private + */ + var $enclosure_class = 'SimplePie_Enclosure'; + + /** + * @var string Class used for Media RSS captions + * @see SimplePie::set_caption_class() + * @access private + */ + var $caption_class = 'SimplePie_Caption'; + + /** + * @var string Class used for Media RSS + * @see SimplePie::set_copyright_class() + * @access private + */ + var $copyright_class = 'SimplePie_Copyright'; + + /** + * @var string Class used for Media RSS + * @see SimplePie::set_credit_class() + * @access private + */ + var $credit_class = 'SimplePie_Credit'; + + /** + * @var string Class used for Media RSS + * @see SimplePie::set_rating_class() + * @access private + */ + var $rating_class = 'SimplePie_Rating'; + + /** + * @var string Class used for Media RSS + * @see SimplePie::set_restriction_class() + * @access private + */ + var $restriction_class = 'SimplePie_Restriction'; + + /** + * @var string Class used for content-type sniffing + * @see SimplePie::set_content_type_sniffer_class() + * @access private + */ + var $content_type_sniffer_class = 'SimplePie_Content_Type_Sniffer'; + + /** + * @var string Class used for item sources. + * @see SimplePie::set_source_class() + * @access private + */ + var $source_class = 'SimplePie_Source'; + + /** + * @var mixed Set javascript query string parameter (false, or + * anything type-cast to false, disables this feature) + * @see SimplePie::set_javascript() + * @access private + */ + var $javascript = 'js'; + + /** + * @var int Maximum number of feeds to check with autodiscovery + * @see SimplePie::set_max_checked_feeds() + * @access private + */ + var $max_checked_feeds = 10; + + /** + * @var array All the feeds found during the autodiscovery process + * @see SimplePie::get_all_discovered_feeds() + * @access private + */ + var $all_discovered_feeds = array(); + + /** + * @var string Web-accessible path to the handler_favicon.php file. + * @see SimplePie::set_favicon_handler() + * @access private + */ + var $favicon_handler = ''; + + /** + * @var string Web-accessible path to the handler_image.php file. + * @see SimplePie::set_image_handler() + * @access private + */ + var $image_handler = ''; + + /** + * @var array Stores the URLs when multiple feeds are being initialized. + * @see SimplePie::set_feed_url() + * @access private + */ + var $multifeed_url = array(); + + /** + * @var array Stores SimplePie objects when multiple feeds initialized. + * @access private + */ + var $multifeed_objects = array(); + + /** + * @var array Stores the get_object_vars() array for use with multifeeds. + * @see SimplePie::set_feed_url() + * @access private + */ + var $config_settings = null; + + /** + * @var integer Stores the number of items to return per-feed with multifeeds. + * @see SimplePie::set_item_limit() + * @access private + */ + var $item_limit = 0; + + /** + * @var array Stores the default attributes to be stripped by strip_attributes(). + * @see SimplePie::strip_attributes() + * @access private + */ + var $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'); + + /** + * @var array Stores the default tags to be stripped by strip_htmltags(). + * @see SimplePie::strip_htmltags() + * @access private + */ + var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'); + + /** + * The SimplePie class contains feed level data and options + * + * There are two ways that you can create a new SimplePie object. The first + * is by passing a feed URL as a parameter to the SimplePie constructor + * (as well as optionally setting the cache location and cache expiry). This + * will initialise the whole feed with all of the default settings, and you + * can begin accessing methods and properties immediately. + * + * The second way is to create the SimplePie object with no parameters + * at all. This will enable you to set configuration options. After setting + * them, you must initialise the feed using $feed->init(). At that point the + * object's methods and properties will be available to you. This format is + * what is used throughout this documentation. + * + * @access public + * @since 1.0 Preview Release + * @param string $feed_url This is the URL you want to parse. + * @param string $cache_location This is where you want the cache to be stored. + * @param int $cache_duration This is the number of seconds that you want to store the cache file for. + */ + function SimplePie($feed_url = null, $cache_location = null, $cache_duration = null) + { + // Other objects, instances created here so we can set options on them + $this->sanitize = new SimplePie_Sanitize; + + // Set options if they're passed to the constructor + if ($cache_location !== null) + { + $this->set_cache_location($cache_location); + } + + if ($cache_duration !== null) + { + $this->set_cache_duration($cache_duration); + } + + // Only init the script if we're passed a feed URL + if ($feed_url !== null) + { + $this->set_feed_url($feed_url); + $this->init(); + } + } + + /** + * Used for converting object to a string + */ + function __toString() + { + return md5(serialize($this->data)); + } + + /** + * Remove items that link back to this before destroying this object + */ + function __destruct() + { + if ((version_compare(PHP_VERSION, '5.3', '<') || !gc_enabled()) && !ini_get('zend.ze1_compatibility_mode')) + { + if (!empty($this->data['items'])) + { + foreach ($this->data['items'] as $item) + { + $item->__destruct(); + } + unset($item, $this->data['items']); + } + if (!empty($this->data['ordered_items'])) + { + foreach ($this->data['ordered_items'] as $item) + { + $item->__destruct(); + } + unset($item, $this->data['ordered_items']); + } + } + } + + /** + * Force the given data/URL to be treated as a feed no matter what it + * appears like + * + * @access public + * @since 1.1 + * @param bool $enable Force the given data/URL to be treated as a feed + */ + function force_feed($enable = false) + { + $this->force_feed = (bool) $enable; + } + + /** + * This is the URL of the feed you want to parse. + * + * This allows you to enter the URL of the feed you want to parse, or the + * website you want to try to use auto-discovery on. This takes priority + * over any set raw data. + * + * You can set multiple feeds to mash together by passing an array instead + * of a string for the $url. Remember that with each additional feed comes + * additional processing and resources. + * + * @access public + * @since 1.0 Preview Release + * @param mixed $url This is the URL (or array of URLs) that you want to parse. + * @see SimplePie::set_raw_data() + */ + function set_feed_url($url) + { + if (is_array($url)) + { + $this->multifeed_url = array(); + foreach ($url as $value) + { + $this->multifeed_url[] = SimplePie_Misc::fix_protocol($value, 1); + } + } + else + { + $this->feed_url = SimplePie_Misc::fix_protocol($url, 1); + } + } + + /** + * Provides an instance of SimplePie_File to use as a feed + * + * @access public + * @param object &$file Instance of SimplePie_File (or subclass) + * @return bool True on success, false on failure + */ + function set_file(&$file) + { + if (is_a($file, 'SimplePie_File')) + { + $this->feed_url = $file->url; + $this->file =& $file; + return true; + } + return false; + } + + /** + * Allows you to use a string of RSS/Atom data instead of a remote feed. + * + * If you have a feed available as a string in PHP, you can tell SimplePie + * to parse that data string instead of a remote feed. Any set feed URL + * takes precedence. + * + * @access public + * @since 1.0 Beta 3 + * @param string $data RSS or Atom data as a string. + * @see SimplePie::set_feed_url() + */ + function set_raw_data($data) + { + $this->raw_data = $data; + } + + /** + * Allows you to override the default timeout for fetching remote feeds. + * + * This allows you to change the maximum time the feed's server to respond + * and send the feed back. + * + * @access public + * @since 1.0 Beta 3 + * @param int $timeout The maximum number of seconds to spend waiting to retrieve a feed. + */ + function set_timeout($timeout = 10) + { + $this->timeout = (int) $timeout; + } + + /** + * Forces SimplePie to use fsockopen() instead of the preferred cURL + * functions. + * + * @access public + * @since 1.0 Beta 3 + * @param bool $enable Force fsockopen() to be used + */ + function force_fsockopen($enable = false) + { + $this->force_fsockopen = (bool) $enable; + } + + /** + * Outputs the raw XML content of the feed, after it has gone through + * SimplePie's filters. + * + * Used only for debugging, this function will output the XML content as + * text/xml. When SimplePie reads in a feed, it does a bit of cleaning up + * before trying to parse it. Many parts of the feed are re-written in + * memory, and in the end, you have a parsable feed. XML dump shows you the + * actual XML that SimplePie tries to parse, which may or may not be very + * different from the original feed. + * + * @access public + * @since 1.0 Preview Release + * @param bool $enable Enable XML dump + */ + function enable_xml_dump($enable = false) + { + $this->xml_dump = (bool) $enable; + } + + /** + * Enables/disables caching in SimplePie. + * + * This option allows you to disable caching all-together in SimplePie. + * However, disabling the cache can lead to longer load times. + * + * @access public + * @since 1.0 Preview Release + * @param bool $enable Enable caching + */ + function enable_cache($enable = true) + { + $this->cache = (bool) $enable; + } + + /** + * Set the length of time (in seconds) that the contents of a feed + * will be cached. + * + * @access public + * @param int $seconds The feed content cache duration. + */ + function set_cache_duration($seconds = 3600) + { + $this->cache_duration = (int) $seconds; + } + + /** + * Set the length of time (in seconds) that the autodiscovered feed + * URL will be cached. + * + * @access public + * @param int $seconds The autodiscovered feed URL cache duration. + */ + function set_autodiscovery_cache_duration($seconds = 604800) + { + $this->autodiscovery_cache_duration = (int) $seconds; + } + + /** + * Set the file system location where the cached files should be stored. + * + * @access public + * @param string $location The file system location. + */ + function set_cache_location($location = './cache') + { + $this->cache_location = (string) $location; + } + + /** + * Determines whether feed items should be sorted into reverse chronological order. + * + * @access public + * @param bool $enable Sort as reverse chronological order. + */ + function enable_order_by_date($enable = true) + { + $this->order_by_date = (bool) $enable; + } + + /** + * Allows you to override the character encoding reported by the feed. + * + * @access public + * @param string $encoding Character encoding. + */ + function set_input_encoding($encoding = false) + { + if ($encoding) + { + $this->input_encoding = (string) $encoding; + } + else + { + $this->input_encoding = false; + } + } + + /** + * Set how much feed autodiscovery to do + * + * @access public + * @see SIMPLEPIE_LOCATOR_NONE + * @see SIMPLEPIE_LOCATOR_AUTODISCOVERY + * @see SIMPLEPIE_LOCATOR_LOCAL_EXTENSION + * @see SIMPLEPIE_LOCATOR_LOCAL_BODY + * @see SIMPLEPIE_LOCATOR_REMOTE_EXTENSION + * @see SIMPLEPIE_LOCATOR_REMOTE_BODY + * @see SIMPLEPIE_LOCATOR_ALL + * @param int $level Feed Autodiscovery Level (level can be a + * combination of the above constants, see bitwise OR operator) + */ + function set_autodiscovery_level($level = SIMPLEPIE_LOCATOR_ALL) + { + $this->autodiscovery = (int) $level; + } + + /** + * Allows you to change which class SimplePie uses for caching. + * Useful when you are overloading or extending SimplePie's default classes. + * + * @access public + * @param string $class Name of custom class. + * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation + * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation + */ + function set_cache_class($class = 'SimplePie_Cache') + { + if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Cache')) + { + $this->cache_class = $class; + return true; + } + return false; + } + + /** + * Allows you to change which class SimplePie uses for auto-discovery. + * Useful when you are overloading or extending SimplePie's default classes. + * + * @access public + * @param string $class Name of custom class. + * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation + * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation + */ + function set_locator_class($class = 'SimplePie_Locator') + { + if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Locator')) + { + $this->locator_class = $class; + return true; + } + return false; + } + + /** + * Allows you to change which class SimplePie uses for XML parsing. + * Useful when you are overloading or extending SimplePie's default classes. + * + * @access public + * @param string $class Name of custom class. + * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation + * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation + */ + function set_parser_class($class = 'SimplePie_Parser') + { + if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Parser')) + { + $this->parser_class = $class; + return true; + } + return false; + } + + /** + * Allows you to change which class SimplePie uses for remote file fetching. + * Useful when you are overloading or extending SimplePie's default classes. + * + * @access public + * @param string $class Name of custom class. + * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation + * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation + */ + function set_file_class($class = 'SimplePie_File') + { + if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_File')) + { + $this->file_class = $class; + return true; + } + return false; + } + + /** + * Allows you to change which class SimplePie uses for data sanitization. + * Useful when you are overloading or extending SimplePie's default classes. + * + * @access public + * @param string $class Name of custom class. + * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation + * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation + */ + function set_sanitize_class($class = 'SimplePie_Sanitize') + { + if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Sanitize')) + { + $this->sanitize = new $class; + return true; + } + return false; + } + + /** + * Allows you to change which class SimplePie uses for handling feed items. + * Useful when you are overloading or extending SimplePie's default classes. + * + * @access public + * @param string $class Name of custom class. + * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation + * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation + */ + function set_item_class($class = 'SimplePie_Item') + { + if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Item')) + { + $this->item_class = $class; + return true; + } + return false; + } + + /** + * Allows you to change which class SimplePie uses for handling author data. + * Useful when you are overloading or extending SimplePie's default classes. + * + * @access public + * @param string $class Name of custom class. + * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation + * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation + */ + function set_author_class($class = 'SimplePie_Author') + { + if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Author')) + { + $this->author_class = $class; + return true; + } + return false; + } + + /** + * Allows you to change which class SimplePie uses for handling category data. + * Useful when you are overloading or extending SimplePie's default classes. + * + * @access public + * @param string $class Name of custom class. + * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation + * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation + */ + function set_category_class($class = 'SimplePie_Category') + { + if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Category')) + { + $this->category_class = $class; + return true; + } + return false; + } + + /** + * Allows you to change which class SimplePie uses for feed enclosures. + * Useful when you are overloading or extending SimplePie's default classes. + * + * @access public + * @param string $class Name of custom class. + * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation + * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation + */ + function set_enclosure_class($class = 'SimplePie_Enclosure') + { + if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Enclosure')) + { + $this->enclosure_class = $class; + return true; + } + return false; + } + + /** + * Allows you to change which class SimplePie uses for captions + * Useful when you are overloading or extending SimplePie's default classes. + * + * @access public + * @param string $class Name of custom class. + * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation + * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation + */ + function set_caption_class($class = 'SimplePie_Caption') + { + if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Caption')) + { + $this->caption_class = $class; + return true; + } + return false; + } + + /** + * Allows you to change which class SimplePie uses for + * Useful when you are overloading or extending SimplePie's default classes. + * + * @access public + * @param string $class Name of custom class. + * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation + * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation + */ + function set_copyright_class($class = 'SimplePie_Copyright') + { + if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Copyright')) + { + $this->copyright_class = $class; + return true; + } + return false; + } + + /** + * Allows you to change which class SimplePie uses for + * Useful when you are overloading or extending SimplePie's default classes. + * + * @access public + * @param string $class Name of custom class. + * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation + * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation + */ + function set_credit_class($class = 'SimplePie_Credit') + { + if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Credit')) + { + $this->credit_class = $class; + return true; + } + return false; + } + + /** + * Allows you to change which class SimplePie uses for + * Useful when you are overloading or extending SimplePie's default classes. + * + * @access public + * @param string $class Name of custom class. + * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation + * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation + */ + function set_rating_class($class = 'SimplePie_Rating') + { + if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Rating')) + { + $this->rating_class = $class; + return true; + } + return false; + } + + /** + * Allows you to change which class SimplePie uses for + * Useful when you are overloading or extending SimplePie's default classes. + * + * @access public + * @param string $class Name of custom class. + * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation + * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation + */ + function set_restriction_class($class = 'SimplePie_Restriction') + { + if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Restriction')) + { + $this->restriction_class = $class; + return true; + } + return false; + } + + /** + * Allows you to change which class SimplePie uses for content-type sniffing. + * Useful when you are overloading or extending SimplePie's default classes. + * + * @access public + * @param string $class Name of custom class. + * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation + * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation + */ + function set_content_type_sniffer_class($class = 'SimplePie_Content_Type_Sniffer') + { + if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Content_Type_Sniffer')) + { + $this->content_type_sniffer_class = $class; + return true; + } + return false; + } + + /** + * Allows you to change which class SimplePie uses item sources. + * Useful when you are overloading or extending SimplePie's default classes. + * + * @access public + * @param string $class Name of custom class. + * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation + * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation + */ + function set_source_class($class = 'SimplePie_Source') + { + if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Source')) + { + $this->source_class = $class; + return true; + } + return false; + } + + /** + * Allows you to override the default user agent string. + * + * @access public + * @param string $ua New user agent string. + */ + function set_useragent($ua = SIMPLEPIE_USERAGENT) + { + $this->useragent = (string) $ua; + } + + /** + * Set callback function to create cache filename with + * + * @access public + * @param mixed $function Callback function + */ + function set_cache_name_function($function = 'md5') + { + if (is_callable($function)) + { + $this->cache_name_function = $function; + } + } + + /** + * Set javascript query string parameter + * + * @access public + * @param mixed $get Javascript query string parameter + */ + function set_javascript($get = 'js') + { + if ($get) + { + $this->javascript = (string) $get; + } + else + { + $this->javascript = false; + } + } + + /** + * Set options to make SP as fast as possible. Forgoes a + * substantial amount of data sanitization in favor of speed. + * + * @access public + * @param bool $set Whether to set them or not + */ + function set_stupidly_fast($set = false) + { + if ($set) + { + $this->enable_order_by_date(false); + $this->remove_div(false); + $this->strip_comments(false); + $this->strip_htmltags(false); + $this->strip_attributes(false); + $this->set_image_handler(false); + } + } + + /** + * Set maximum number of feeds to check with autodiscovery + * + * @access public + * @param int $max Maximum number of feeds to check + */ + function set_max_checked_feeds($max = 10) + { + $this->max_checked_feeds = (int) $max; + } + + function remove_div($enable = true) + { + $this->sanitize->remove_div($enable); + } + + function strip_htmltags($tags = '', $encode = null) + { + if ($tags === '') + { + $tags = $this->strip_htmltags; + } + $this->sanitize->strip_htmltags($tags); + if ($encode !== null) + { + $this->sanitize->encode_instead_of_strip($tags); + } + } + + function encode_instead_of_strip($enable = true) + { + $this->sanitize->encode_instead_of_strip($enable); + } + + function strip_attributes($attribs = '') + { + if ($attribs === '') + { + $attribs = $this->strip_attributes; + } + $this->sanitize->strip_attributes($attribs); + } + + function set_output_encoding($encoding = 'UTF-8') + { + $this->sanitize->set_output_encoding($encoding); + } + + function strip_comments($strip = false) + { + $this->sanitize->strip_comments($strip); + } + + /** + * Set element/attribute key/value pairs of HTML attributes + * containing URLs that need to be resolved relative to the feed + * + * @access public + * @since 1.0 + * @param array $element_attribute Element/attribute key/value pairs + */ + function set_url_replacements($element_attribute = array('a' => 'href', 'area' => 'href', 'blockquote' => 'cite', 'del' => 'cite', 'form' => 'action', 'img' => array('longdesc', 'src'), 'input' => 'src', 'ins' => 'cite', 'q' => 'cite')) + { + $this->sanitize->set_url_replacements($element_attribute); + } + + /** + * Set the handler to enable the display of cached favicons. + * + * @access public + * @param str $page Web-accessible path to the handler_favicon.php file. + * @param str $qs The query string that the value should be passed to. + */ + function set_favicon_handler($page = false, $qs = 'i') + { + if ($page !== false) + { + $this->favicon_handler = $page . '?' . $qs . '='; + } + else + { + $this->favicon_handler = ''; + } + } + + /** + * Set the handler to enable the display of cached images. + * + * @access public + * @param str $page Web-accessible path to the handler_image.php file. + * @param str $qs The query string that the value should be passed to. + */ + function set_image_handler($page = false, $qs = 'i') + { + if ($page !== false) + { + $this->sanitize->set_image_handler($page . '?' . $qs . '='); + } + else + { + $this->image_handler = ''; + } + } + + /** + * Set the limit for items returned per-feed with multifeeds. + * + * @access public + * @param integer $limit The maximum number of items to return. + */ + function set_item_limit($limit = 0) + { + $this->item_limit = (int) $limit; + } + + function init() + { + // Check absolute bare minimum requirements. + if ((function_exists('version_compare') && version_compare(PHP_VERSION, '4.3.0', '<')) || !extension_loaded('xml') || !extension_loaded('pcre')) + { + return false; + } + // Then check the xml extension is sane (i.e., libxml 2.7.x issue on PHP < 5.2.9 and libxml 2.7.0 to 2.7.2 on any version) if we don't have xmlreader. + elseif (!extension_loaded('xmlreader')) + { + static $xml_is_sane = null; + if ($xml_is_sane === null) + { + $parser_check = xml_parser_create(); + xml_parse_into_struct($parser_check, '&', $values); + xml_parser_free($parser_check); + $xml_is_sane = isset($values[0]['value']); + } + if (!$xml_is_sane) + { + return false; + } + } + + if (isset($_GET[$this->javascript])) + { + SimplePie_Misc::output_javascript(); + exit; + } + + // Pass whatever was set with config options over to the sanitizer. + $this->sanitize->pass_cache_data($this->cache, $this->cache_location, $this->cache_name_function, $this->cache_class); + $this->sanitize->pass_file_data($this->file_class, $this->timeout, $this->useragent, $this->force_fsockopen); + + if ($this->feed_url !== null || $this->raw_data !== null) + { + $this->data = array(); + $this->multifeed_objects = array(); + $cache = false; + + if ($this->feed_url !== null) + { + $parsed_feed_url = SimplePie_Misc::parse_url($this->feed_url); + // Decide whether to enable caching + if ($this->cache && $parsed_feed_url['scheme'] !== '') + { + $cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, call_user_func($this->cache_name_function, $this->feed_url), 'spc'); + } + // If it's enabled and we don't want an XML dump, use the cache + if ($cache && !$this->xml_dump) + { + // Load the Cache + $this->data = $cache->load(); + if (!empty($this->data)) + { + // If the cache is for an outdated build of SimplePie + if (!isset($this->data['build']) || $this->data['build'] !== SIMPLEPIE_BUILD) + { + $cache->unlink(); + $this->data = array(); + } + // If we've hit a collision just rerun it with caching disabled + elseif (isset($this->data['url']) && $this->data['url'] !== $this->feed_url) + { + $cache = false; + $this->data = array(); + } + // If we've got a non feed_url stored (if the page isn't actually a feed, or is a redirect) use that URL. + elseif (isset($this->data['feed_url'])) + { + // If the autodiscovery cache is still valid use it. + if ($cache->mtime() + $this->autodiscovery_cache_duration > time()) + { + // Do not need to do feed autodiscovery yet. + if ($this->data['feed_url'] === $this->data['url']) + { + $cache->unlink(); + $this->data = array(); + } + else + { + $this->set_feed_url($this->data['feed_url']); + return $this->init(); + } + } + } + // Check if the cache has been updated + elseif ($cache->mtime() + $this->cache_duration < time()) + { + // If we have last-modified and/or etag set + if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag'])) + { + $headers = array(); + if (isset($this->data['headers']['last-modified'])) + { + $headers['if-modified-since'] = $this->data['headers']['last-modified']; + } + if (isset($this->data['headers']['etag'])) + { + $headers['if-none-match'] = '"' . $this->data['headers']['etag'] . '"'; + } + $file = new $this->file_class($this->feed_url, $this->timeout/10, 5, $headers, $this->useragent, $this->force_fsockopen); + if ($file->success) + { + if ($file->status_code === 304) + { + $cache->touch(); + return true; + } + else + { + $headers = $file->headers; + } + } + else + { + unset($file); + } + } + } + // If the cache is still valid, just return true + else + { + return true; + } + } + // If the cache is empty, delete it + else + { + $cache->unlink(); + $this->data = array(); + } + } + // If we don't already have the file (it'll only exist if we've opened it to check if the cache has been modified), open it. + if (!isset($file)) + { + if (is_a($this->file, 'SimplePie_File') && $this->file->url === $this->feed_url) + { + $file =& $this->file; + } + else + { + $file = new $this->file_class($this->feed_url, $this->timeout, 5, null, $this->useragent, $this->force_fsockopen); + } + } + // If the file connection has an error, set SimplePie::error to that and quit + if (!$file->success && !($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300))) + { + $this->error = $file->error; + if (!empty($this->data)) + { + return true; + } + else + { + return false; + } + } + + if (!$this->force_feed) + { + // Check if the supplied URL is a feed, if it isn't, look for it. + $locate = new $this->locator_class($file, $this->timeout, $this->useragent, $this->file_class, $this->max_checked_feeds, $this->content_type_sniffer_class); + if (!$locate->is_feed($file)) + { + // We need to unset this so that if SimplePie::set_file() has been called that object is untouched + unset($file); + if ($file = $locate->find($this->autodiscovery, $this->all_discovered_feeds)) + { + if ($cache) + { + $this->data = array('url' => $this->feed_url, 'feed_url' => $file->url, 'build' => SIMPLEPIE_BUILD); + if (!$cache->save($this)) + { + trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); + } + $cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, call_user_func($this->cache_name_function, $file->url), 'spc'); + } + $this->feed_url = $file->url; + } + else + { + $this->error = "A feed could not be found at $this->feed_url. A feed with an invalid mime type may fall victim to this error, or " . SIMPLEPIE_NAME . " was unable to auto-discover it.. Use force_feed() if you are certain this URL is a real feed."; + SimplePie_Misc::error($this->error, E_USER_NOTICE, __FILE__, __LINE__); + return false; + } + } + $locate = null; + } + + $headers = $file->headers; + $data = $file->body; + $sniffer = new $this->content_type_sniffer_class($file); + $sniffed = $sniffer->get_type(); + } + else + { + $data = $this->raw_data; + } + + // Set up array of possible encodings + $encodings = array(); + + // First check to see if input has been overridden. + if ($this->input_encoding !== false) + { + $encodings[] = $this->input_encoding; + } + + $application_types = array('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity'); + $text_types = array('text/xml', 'text/xml-external-parsed-entity'); + + // RFC 3023 (only applies to sniffed content) + if (isset($sniffed)) + { + if (in_array($sniffed, $application_types) || substr($sniffed, 0, 12) === 'application/' && substr($sniffed, -4) === '+xml') + { + if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset)) + { + $encodings[] = strtoupper($charset[1]); + } + $encodings = array_merge($encodings, SimplePie_Misc::xml_encoding($data)); + $encodings[] = 'UTF-8'; + } + elseif (in_array($sniffed, $text_types) || substr($sniffed, 0, 5) === 'text/' && substr($sniffed, -4) === '+xml') + { + if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset)) + { + $encodings[] = $charset[1]; + } + $encodings[] = 'US-ASCII'; + } + // Text MIME-type default + elseif (substr($sniffed, 0, 5) === 'text/') + { + $encodings[] = 'US-ASCII'; + } + } + + // Fallback to XML 1.0 Appendix F.1/UTF-8/ISO-8859-1 + $encodings = array_merge($encodings, SimplePie_Misc::xml_encoding($data)); + $encodings[] = 'UTF-8'; + $encodings[] = 'ISO-8859-1'; + + // There's no point in trying an encoding twice + $encodings = array_unique($encodings); + + // If we want the XML, just output that with the most likely encoding and quit + if ($this->xml_dump) + { + header('Content-type: text/xml; charset=' . $encodings[0]); + echo $data; + exit; + } + + // Loop through each possible encoding, till we return something, or run out of possibilities + foreach ($encodings as $encoding) + { + // Change the encoding to UTF-8 (as we always use UTF-8 internally) + if ($utf8_data = SimplePie_Misc::change_encoding($data, $encoding, 'UTF-8')) + { + // Create new parser + $parser = new $this->parser_class(); + + // If it's parsed fine + if ($parser->parse($utf8_data, 'UTF-8')) + { + $this->data = $parser->get_data(); + if ($this->get_type() & ~SIMPLEPIE_TYPE_NONE) + { + if (isset($headers)) + { + $this->data['headers'] = $headers; + } + $this->data['build'] = SIMPLEPIE_BUILD; + + // Cache the file if caching is enabled + if ($cache && !$cache->save($this)) + { + trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); + } + return true; + } + else + { + $this->error = "A feed could not be found at $this->feed_url. This does not appear to be a valid RSS or Atom feed."; + SimplePie_Misc::error($this->error, E_USER_NOTICE, __FILE__, __LINE__); + return false; + } + } + } + } + if (isset($parser)) + { + // We have an error, just set SimplePie_Misc::error to it and quit + $this->error = sprintf('This XML document is invalid, likely due to invalid characters. XML error: %s at line %d, column %d', $parser->get_error_string(), $parser->get_current_line(), $parser->get_current_column()); + } + else + { + $this->error = 'The data could not be converted to UTF-8. You MUST have either the iconv or mbstring extension installed. Upgrading to PHP 5.x (which includes iconv) is highly recommended.'; + } + SimplePie_Misc::error($this->error, E_USER_NOTICE, __FILE__, __LINE__); + return false; + } + elseif (!empty($this->multifeed_url)) + { + $i = 0; + $success = 0; + $this->multifeed_objects = array(); + foreach ($this->multifeed_url as $url) + { + if (SIMPLEPIE_PHP5) + { + // This keyword needs to defy coding standards for PHP4 compatibility + $this->multifeed_objects[$i] = clone($this); + } + else + { + $this->multifeed_objects[$i] = $this; + } + $this->multifeed_objects[$i]->set_feed_url($url); + $success |= $this->multifeed_objects[$i]->init(); + $i++; + } + return (bool) $success; + } + else + { + return false; + } + } + + /** + * Return the error message for the occured error + * + * @access public + * @return string Error message + */ + function error() + { + return $this->error; + } + + function get_encoding() + { + return $this->sanitize->output_encoding; + } + + function handle_content_type($mime = 'text/html') + { + if (!headers_sent()) + { + $header = "Content-type: $mime;"; + if ($this->get_encoding()) + { + $header .= ' charset=' . $this->get_encoding(); + } + else + { + $header .= ' charset=UTF-8'; + } + header($header); + } + } + + function get_type() + { + if (!isset($this->data['type'])) + { + $this->data['type'] = SIMPLEPIE_TYPE_ALL; + if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'])) + { + $this->data['type'] &= SIMPLEPIE_TYPE_ATOM_10; + } + elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'])) + { + $this->data['type'] &= SIMPLEPIE_TYPE_ATOM_03; + } + elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'])) + { + if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['channel']) + || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['image']) + || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item']) + || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['textinput'])) + { + $this->data['type'] &= SIMPLEPIE_TYPE_RSS_10; + } + if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['channel']) + || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['image']) + || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item']) + || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['textinput'])) + { + $this->data['type'] &= SIMPLEPIE_TYPE_RSS_090; + } + } + elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'])) + { + $this->data['type'] &= SIMPLEPIE_TYPE_RSS_ALL; + if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version'])) + { + switch (trim($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version'])) + { + case '0.91': + $this->data['type'] &= SIMPLEPIE_TYPE_RSS_091; + if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['skiphours']['hour'][0]['data'])) + { + switch (trim($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['skiphours']['hour'][0]['data'])) + { + case '0': + $this->data['type'] &= SIMPLEPIE_TYPE_RSS_091_NETSCAPE; + break; + + case '24': + $this->data['type'] &= SIMPLEPIE_TYPE_RSS_091_USERLAND; + break; + } + } + break; + + case '0.92': + $this->data['type'] &= SIMPLEPIE_TYPE_RSS_092; + break; + + case '0.93': + $this->data['type'] &= SIMPLEPIE_TYPE_RSS_093; + break; + + case '0.94': + $this->data['type'] &= SIMPLEPIE_TYPE_RSS_094; + break; + + case '2.0': + $this->data['type'] &= SIMPLEPIE_TYPE_RSS_20; + break; + } + } + } + else + { + $this->data['type'] = SIMPLEPIE_TYPE_NONE; + } + } + return $this->data['type']; + } + + /** + * Returns the URL for the favicon of the feed's website. + * + * @todo Cache atom:icon + * @access public + * @since 1.0 + */ + function get_favicon() + { + if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'icon')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); + } + elseif (($url = $this->get_link()) !== null && preg_match('/^http(s)?:\/\//i', $url)) + { + $favicon = SimplePie_Misc::absolutize_url('/favicon.ico', $url); + + if ($this->cache && $this->favicon_handler) + { + $favicon_filename = call_user_func($this->cache_name_function, $favicon); + $cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, $favicon_filename, 'spi'); + + if ($cache->load()) + { + return $this->sanitize($this->favicon_handler . $favicon_filename, SIMPLEPIE_CONSTRUCT_IRI); + } + else + { + $file = new $this->file_class($favicon, $this->timeout / 10, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen); + + if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300)) && strlen($file->body) > 0) + { + $sniffer = new $this->content_type_sniffer_class($file); + if (substr($sniffer->get_type(), 0, 6) === 'image/') + { + if ($cache->save(array('headers' => $file->headers, 'body' => $file->body))) + { + return $this->sanitize($this->favicon_handler . $favicon_filename, SIMPLEPIE_CONSTRUCT_IRI); + } + else + { + trigger_error("$cache->name is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); + return $this->sanitize($favicon, SIMPLEPIE_CONSTRUCT_IRI); + } + } + // not an image + else + { + return false; + } + } + } + } + else + { + return $this->sanitize($favicon, SIMPLEPIE_CONSTRUCT_IRI); + } + } + return false; + } + + /** + * @todo If we have a perm redirect we should return the new URL + * @todo When we make the above change, let's support as well + * @todo Also, |atom:link|@rel=self + */ + function subscribe_url() + { + if ($this->feed_url !== null) + { + return $this->sanitize($this->feed_url, SIMPLEPIE_CONSTRUCT_IRI); + } + else + { + return null; + } + } + + function subscribe_feed() + { + if ($this->feed_url !== null) + { + return $this->sanitize(SimplePie_Misc::fix_protocol($this->feed_url, 2), SIMPLEPIE_CONSTRUCT_IRI); + } + else + { + return null; + } + } + + function subscribe_outlook() + { + if ($this->feed_url !== null) + { + return $this->sanitize('outlook' . SimplePie_Misc::fix_protocol($this->feed_url, 2), SIMPLEPIE_CONSTRUCT_IRI); + } + else + { + return null; + } + } + + function subscribe_podcast() + { + if ($this->feed_url !== null) + { + return $this->sanitize(SimplePie_Misc::fix_protocol($this->feed_url, 3), SIMPLEPIE_CONSTRUCT_IRI); + } + else + { + return null; + } + } + + function subscribe_itunes() + { + if ($this->feed_url !== null) + { + return $this->sanitize(SimplePie_Misc::fix_protocol($this->feed_url, 4), SIMPLEPIE_CONSTRUCT_IRI); + } + else + { + return null; + } + } + + /** + * Creates the subscribe_* methods' return data + * + * @access private + * @param string $feed_url String to prefix to the feed URL + * @param string $site_url String to prefix to the site URL (and + * suffix to the feed URL) + * @return mixed URL if feed exists, false otherwise + */ + function subscribe_service($feed_url, $site_url = null) + { + if ($this->subscribe_url()) + { + $return = $feed_url . rawurlencode($this->feed_url); + if ($site_url !== null && $this->get_link() !== null) + { + $return .= $site_url . rawurlencode($this->get_link()); + } + return $this->sanitize($return, SIMPLEPIE_CONSTRUCT_IRI); + } + else + { + return null; + } + } + + function subscribe_aol() + { + return $this->subscribe_service('http://feeds.my.aol.com/add.jsp?url='); + } + + function subscribe_bloglines() + { + return $this->subscribe_service('http://www.bloglines.com/sub/'); + } + + function subscribe_eskobo() + { + return $this->subscribe_service('http://www.eskobo.com/?AddToMyPage='); + } + + function subscribe_feedfeeds() + { + return $this->subscribe_service('http://www.feedfeeds.com/add?feed='); + } + + function subscribe_feedster() + { + return $this->subscribe_service('http://www.feedster.com/myfeedster.php?action=addrss&confirm=no&rssurl='); + } + + function subscribe_google() + { + return $this->subscribe_service('http://fusion.google.com/add?feedurl='); + } + + function subscribe_gritwire() + { + return $this->subscribe_service('http://my.gritwire.com/feeds/addExternalFeed.aspx?FeedUrl='); + } + + function subscribe_msn() + { + return $this->subscribe_service('http://my.msn.com/addtomymsn.armx?id=rss&ut=', '&ru='); + } + + function subscribe_netvibes() + { + return $this->subscribe_service('http://www.netvibes.com/subscribe.php?url='); + } + + function subscribe_newsburst() + { + return $this->subscribe_service('http://www.newsburst.com/Source/?add='); + } + + function subscribe_newsgator() + { + return $this->subscribe_service('http://www.newsgator.com/ngs/subscriber/subext.aspx?url='); + } + + function subscribe_odeo() + { + return $this->subscribe_service('http://www.odeo.com/listen/subscribe?feed='); + } + + function subscribe_podnova() + { + return $this->subscribe_service('http://www.podnova.com/index_your_podcasts.srf?action=add&url='); + } + + function subscribe_rojo() + { + return $this->subscribe_service('http://www.rojo.com/add-subscription?resource='); + } + + function subscribe_yahoo() + { + return $this->subscribe_service('http://add.my.yahoo.com/rss?url='); + } + + function get_feed_tags($namespace, $tag) + { + $type = $this->get_type(); + if ($type & SIMPLEPIE_TYPE_ATOM_10) + { + if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag])) + { + return $this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag]; + } + } + if ($type & SIMPLEPIE_TYPE_ATOM_03) + { + if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag])) + { + return $this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag]; + } + } + if ($type & SIMPLEPIE_TYPE_RSS_RDF) + { + if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag])) + { + return $this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag]; + } + } + if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION) + { + if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag])) + { + return $this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag]; + } + } + return null; + } + + function get_channel_tags($namespace, $tag) + { + $type = $this->get_type(); + if ($type & SIMPLEPIE_TYPE_ATOM_ALL) + { + if ($return = $this->get_feed_tags($namespace, $tag)) + { + return $return; + } + } + if ($type & SIMPLEPIE_TYPE_RSS_10) + { + if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'channel')) + { + if (isset($channel[0]['child'][$namespace][$tag])) + { + return $channel[0]['child'][$namespace][$tag]; + } + } + } + if ($type & SIMPLEPIE_TYPE_RSS_090) + { + if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'channel')) + { + if (isset($channel[0]['child'][$namespace][$tag])) + { + return $channel[0]['child'][$namespace][$tag]; + } + } + } + if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION) + { + if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'channel')) + { + if (isset($channel[0]['child'][$namespace][$tag])) + { + return $channel[0]['child'][$namespace][$tag]; + } + } + } + return null; + } + + function get_image_tags($namespace, $tag) + { + $type = $this->get_type(); + if ($type & SIMPLEPIE_TYPE_RSS_10) + { + if ($image = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'image')) + { + if (isset($image[0]['child'][$namespace][$tag])) + { + return $image[0]['child'][$namespace][$tag]; + } + } + } + if ($type & SIMPLEPIE_TYPE_RSS_090) + { + if ($image = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'image')) + { + if (isset($image[0]['child'][$namespace][$tag])) + { + return $image[0]['child'][$namespace][$tag]; + } + } + } + if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION) + { + if ($image = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'image')) + { + if (isset($image[0]['child'][$namespace][$tag])) + { + return $image[0]['child'][$namespace][$tag]; + } + } + } + return null; + } + + function get_base($element = array()) + { + if (!($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION) && !empty($element['xml_base_explicit']) && isset($element['xml_base'])) + { + return $element['xml_base']; + } + elseif ($this->get_link() !== null) + { + return $this->get_link(); + } + else + { + return $this->subscribe_url(); + } + } + + function sanitize($data, $type, $base = '') + { + return $this->sanitize->sanitize($data, $type, $base); + } + + function get_title() + { + if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + return null; + } + } + + function get_category($key = 0) + { + $categories = $this->get_categories(); + if (isset($categories[$key])) + { + return $categories[$key]; + } + else + { + return null; + } + } + + function get_categories() + { + $categories = array(); + + foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category) + { + $term = null; + $scheme = null; + $label = null; + if (isset($category['attribs']['']['term'])) + { + $term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) + { + $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['label'])) + { + $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories[] = new $this->category_class($term, $scheme, $label); + } + foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category) + { + // This is really the label, but keep this as the term also for BC. + // Label will also work on retrieving because that falls back to term. + $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); + if (isset($category['attribs']['']['domain'])) + { + $scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $scheme = null; + } + $categories[] = new $this->category_class($term, $scheme, null); + } + foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category) + { + $categories[] = new $this->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); + } + foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category) + { + $categories[] = new $this->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); + } + + if (!empty($categories)) + { + return SimplePie_Misc::array_unique($categories); + } + else + { + return null; + } + } + + function get_author($key = 0) + { + $authors = $this->get_authors(); + if (isset($authors[$key])) + { + return $authors[$key]; + } + else + { + return null; + } + } + + function get_authors() + { + $authors = array(); + foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author) + { + $name = null; + $uri = null; + $email = null; + if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) + { + $name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) + { + $uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); + } + if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) + { + $email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $uri !== null) + { + $authors[] = new $this->author_class($name, $uri, $email); + } + } + if ($author = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author')) + { + $name = null; + $url = null; + $email = null; + if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) + { + $name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) + { + $url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); + } + if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) + { + $email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $url !== null) + { + $authors[] = new $this->author_class($name, $url, $email); + } + } + foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author) + { + $authors[] = new $this->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); + } + foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author) + { + $authors[] = new $this->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); + } + foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author) + { + $authors[] = new $this->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); + } + + if (!empty($authors)) + { + return SimplePie_Misc::array_unique($authors); + } + else + { + return null; + } + } + + function get_contributor($key = 0) + { + $contributors = $this->get_contributors(); + if (isset($contributors[$key])) + { + return $contributors[$key]; + } + else + { + return null; + } + } + + function get_contributors() + { + $contributors = array(); + foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor) + { + $name = null; + $uri = null; + $email = null; + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) + { + $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) + { + $uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) + { + $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $uri !== null) + { + $contributors[] = new $this->author_class($name, $uri, $email); + } + } + foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor) + { + $name = null; + $url = null; + $email = null; + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) + { + $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) + { + $url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) + { + $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $url !== null) + { + $contributors[] = new $this->author_class($name, $url, $email); + } + } + + if (!empty($contributors)) + { + return SimplePie_Misc::array_unique($contributors); + } + else + { + return null; + } + } + + function get_link($key = 0, $rel = 'alternate') + { + $links = $this->get_links($rel); + if (isset($links[$key])) + { + return $links[$key]; + } + else + { + return null; + } + } + + /** + * Added for parity between the parent-level and the item/entry-level. + */ + function get_permalink() + { + return $this->get_link(0); + } + + function get_links($rel = 'alternate') + { + if (!isset($this->data['links'])) + { + $this->data['links'] = array(); + if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link')) + { + foreach ($links as $link) + { + if (isset($link['attribs']['']['href'])) + { + $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; + $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); + } + } + } + if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link')) + { + foreach ($links as $link) + { + if (isset($link['attribs']['']['href'])) + { + $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; + $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); + + } + } + } + if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link')) + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link')) + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link')) + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + + $keys = array_keys($this->data['links']); + foreach ($keys as $key) + { + if (SimplePie_Misc::is_isegment_nz_nc($key)) + { + if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key])) + { + $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]); + $this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]; + } + else + { + $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key]; + } + } + elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY) + { + $this->data['links'][substr($key, 41)] =& $this->data['links'][$key]; + } + $this->data['links'][$key] = array_unique($this->data['links'][$key]); + } + } + + if (isset($this->data['links'][$rel])) + { + return $this->data['links'][$rel]; + } + else + { + return null; + } + } + + function get_all_discovered_feeds() + { + return $this->all_discovered_feeds; + } + + function get_description() + { + if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'subtitle')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'tagline')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); + } + else + { + return null; + } + } + + function get_copyright() + { + if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'copyright')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'copyright')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + return null; + } + } + + function get_language() + { + if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'language')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'language')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'language')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['xml_lang'])) + { + return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['xml_lang'])) + { + return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['xml_lang'])) + { + return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif (isset($this->data['headers']['content-language'])) + { + return $this->sanitize($this->data['headers']['content-language'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + return null; + } + } + + function get_latitude() + { + + if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat')) + { + return (float) $return[0]['data']; + } + elseif (($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) + { + return (float) $match[1]; + } + else + { + return null; + } + } + + function get_longitude() + { + if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long')) + { + return (float) $return[0]['data']; + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon')) + { + return (float) $return[0]['data']; + } + elseif (($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) + { + return (float) $match[2]; + } + else + { + return null; + } + } + + function get_image_title() + { + if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + return null; + } + } + + function get_image_url() + { + if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'image')) + { + return $this->sanitize($return[0]['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'logo')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); + } + elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'icon')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); + } + elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'url')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); + } + elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'url')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); + } + elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); + } + else + { + return null; + } + } + + function get_image_link() + { + if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); + } + elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); + } + elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); + } + else + { + return null; + } + } + + function get_image_width() + { + if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'width')) + { + return round($return[0]['data']); + } + elseif ($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION && $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url')) + { + return 88.0; + } + else + { + return null; + } + } + + function get_image_height() + { + if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'height')) + { + return round($return[0]['data']); + } + elseif ($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION && $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url')) + { + return 31.0; + } + else + { + return null; + } + } + + function get_item_quantity($max = 0) + { + $max = (int) $max; + $qty = count($this->get_items()); + if ($max === 0) + { + return $qty; + } + else + { + return ($qty > $max) ? $max : $qty; + } + } + + function get_item($key = 0) + { + $items = $this->get_items(); + if (isset($items[$key])) + { + return $items[$key]; + } + else + { + return null; + } + } + + function get_items($start = 0, $end = 0) + { + if (!isset($this->data['items'])) + { + if (!empty($this->multifeed_objects)) + { + $this->data['items'] = SimplePie::merge_items($this->multifeed_objects, $start, $end, $this->item_limit); + } + else + { + $this->data['items'] = array(); + if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'entry')) + { + $keys = array_keys($items); + foreach ($keys as $key) + { + $this->data['items'][] = new $this->item_class($this, $items[$key]); + } + } + if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'entry')) + { + $keys = array_keys($items); + foreach ($keys as $key) + { + $this->data['items'][] = new $this->item_class($this, $items[$key]); + } + } + if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'item')) + { + $keys = array_keys($items); + foreach ($keys as $key) + { + $this->data['items'][] = new $this->item_class($this, $items[$key]); + } + } + if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'item')) + { + $keys = array_keys($items); + foreach ($keys as $key) + { + $this->data['items'][] = new $this->item_class($this, $items[$key]); + } + } + if ($items = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'item')) + { + $keys = array_keys($items); + foreach ($keys as $key) + { + $this->data['items'][] = new $this->item_class($this, $items[$key]); + } + } + } + } + + if (!empty($this->data['items'])) + { + // If we want to order it by date, check if all items have a date, and then sort it + if ($this->order_by_date && empty($this->multifeed_objects)) + { + if (!isset($this->data['ordered_items'])) + { + $do_sort = true; + foreach ($this->data['items'] as $item) + { + if (!$item->get_date('U')) + { + $do_sort = false; + break; + } + } + $item = null; + $this->data['ordered_items'] = $this->data['items']; + if ($do_sort) + { + usort($this->data['ordered_items'], array(&$this, 'sort_items')); + } + } + $items = $this->data['ordered_items']; + } + else + { + $items = $this->data['items']; + } + + // Slice the data as desired + if ($end === 0) + { + return array_slice($items, $start); + } + else + { + return array_slice($items, $start, $end); + } + } + else + { + return array(); + } + } + + /** + * @static + */ + function sort_items($a, $b) + { + return $a->get_date('U') <= $b->get_date('U'); + } + + /** + * @static + */ + function merge_items($urls, $start = 0, $end = 0, $limit = 0) + { + if (is_array($urls) && sizeof($urls) > 0) + { + $items = array(); + foreach ($urls as $arg) + { + if (is_a($arg, 'SimplePie')) + { + $items = array_merge($items, $arg->get_items(0, $limit)); + } + else + { + trigger_error('Arguments must be SimplePie objects', E_USER_WARNING); + } + } + + $do_sort = true; + foreach ($items as $item) + { + if (!$item->get_date('U')) + { + $do_sort = false; + break; + } + } + $item = null; + if ($do_sort) + { + usort($items, array('SimplePie', 'sort_items')); + } + + if ($end === 0) + { + return array_slice($items, $start); + } + else + { + return array_slice($items, $start, $end); + } + } + else + { + trigger_error('Cannot merge zero SimplePie objects', E_USER_WARNING); + return array(); + } + } +} + +class SimplePie_Item +{ + var $feed; + var $data = array(); + + function SimplePie_Item($feed, $data) + { + $this->feed = $feed; + $this->data = $data; + } + + function __toString() + { + return md5(serialize($this->data)); + } + + /** + * Remove items that link back to this before destroying this object + */ + function __destruct() + { + if ((version_compare(PHP_VERSION, '5.3', '<') || !gc_enabled()) && !ini_get('zend.ze1_compatibility_mode')) + { + unset($this->feed); + } + } + + function get_item_tags($namespace, $tag) + { + if (isset($this->data['child'][$namespace][$tag])) + { + return $this->data['child'][$namespace][$tag]; + } + else + { + return null; + } + } + + function get_base($element = array()) + { + return $this->feed->get_base($element); + } + + function sanitize($data, $type, $base = '') + { + return $this->feed->sanitize($data, $type, $base); + } + + function get_feed() + { + return $this->feed; + } + + function get_id($hash = false) + { + if (!$hash) + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'id')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'id')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'identifier')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'identifier')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif (($return = $this->get_permalink()) !== null) + { + return $return; + } + elseif (($return = $this->get_title()) !== null) + { + return $return; + } + } + if ($this->get_permalink() !== null || $this->get_title() !== null) + { + return md5($this->get_permalink() . $this->get_title()); + } + else + { + return md5(serialize($this->data)); + } + } + + function get_title() + { + if (!isset($this->data['title'])) + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title')) + { + $this->data['title'] = $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title')) + { + $this->data['title'] = $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title')) + { + $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title')) + { + $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title')) + { + $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title')) + { + $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title')) + { + $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $this->data['title'] = null; + } + } + return $this->data['title']; + } + + function get_description($description_only = false) + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'summary')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'summary')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML); + } + + elseif (!$description_only) + { + return $this->get_content(true); + } + else + { + return null; + } + } + + function get_content($content_only = false) + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'content')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_content_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'content')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10_MODULES_CONTENT, 'encoded')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); + } + elseif (!$content_only) + { + return $this->get_description(true); + } + else + { + return null; + } + } + + function get_category($key = 0) + { + $categories = $this->get_categories(); + if (isset($categories[$key])) + { + return $categories[$key]; + } + else + { + return null; + } + } + + function get_categories() + { + $categories = array(); + + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category) + { + $term = null; + $scheme = null; + $label = null; + if (isset($category['attribs']['']['term'])) + { + $term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) + { + $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['label'])) + { + $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories[] = new $this->feed->category_class($term, $scheme, $label); + } + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category) + { + // This is really the label, but keep this as the term also for BC. + // Label will also work on retrieving because that falls back to term. + $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); + if (isset($category['attribs']['']['domain'])) + { + $scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $scheme = null; + } + $categories[] = new $this->feed->category_class($term, $scheme, null); + } + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category) + { + $categories[] = new $this->feed->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); + } + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category) + { + $categories[] = new $this->feed->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); + } + + if (!empty($categories)) + { + return SimplePie_Misc::array_unique($categories); + } + else + { + return null; + } + } + + function get_author($key = 0) + { + $authors = $this->get_authors(); + if (isset($authors[$key])) + { + return $authors[$key]; + } + else + { + return null; + } + } + + function get_contributor($key = 0) + { + $contributors = $this->get_contributors(); + if (isset($contributors[$key])) + { + return $contributors[$key]; + } + else + { + return null; + } + } + + function get_contributors() + { + $contributors = array(); + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor) + { + $name = null; + $uri = null; + $email = null; + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) + { + $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) + { + $uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) + { + $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $uri !== null) + { + $contributors[] = new $this->feed->author_class($name, $uri, $email); + } + } + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor) + { + $name = null; + $url = null; + $email = null; + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) + { + $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) + { + $url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) + { + $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $url !== null) + { + $contributors[] = new $this->feed->author_class($name, $url, $email); + } + } + + if (!empty($contributors)) + { + return SimplePie_Misc::array_unique($contributors); + } + else + { + return null; + } + } + + function get_authors() + { + $authors = array(); + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author) + { + $name = null; + $uri = null; + $email = null; + if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) + { + $name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) + { + $uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); + } + if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) + { + $email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $uri !== null) + { + $authors[] = new $this->feed->author_class($name, $uri, $email); + } + } + if ($author = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author')) + { + $name = null; + $url = null; + $email = null; + if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) + { + $name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) + { + $url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); + } + if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) + { + $email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $url !== null) + { + $authors[] = new $this->feed->author_class($name, $url, $email); + } + } + if ($author = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'author')) + { + $authors[] = new $this->feed->author_class(null, null, $this->sanitize($author[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + } + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author) + { + $authors[] = new $this->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); + } + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author) + { + $authors[] = new $this->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); + } + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author) + { + $authors[] = new $this->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); + } + + if (!empty($authors)) + { + return SimplePie_Misc::array_unique($authors); + } + elseif (($source = $this->get_source()) && ($authors = $source->get_authors())) + { + return $authors; + } + elseif ($authors = $this->feed->get_authors()) + { + return $authors; + } + else + { + return null; + } + } + + function get_copyright() + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + return null; + } + } + + function get_date($date_format = 'j F Y, g:i a') + { + if (!isset($this->data['date'])) + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published')) + { + $this->data['date']['raw'] = $return[0]['data']; + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated')) + { + $this->data['date']['raw'] = $return[0]['data']; + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'issued')) + { + $this->data['date']['raw'] = $return[0]['data']; + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'created')) + { + $this->data['date']['raw'] = $return[0]['data']; + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'modified')) + { + $this->data['date']['raw'] = $return[0]['data']; + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'pubDate')) + { + $this->data['date']['raw'] = $return[0]['data']; + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'date')) + { + $this->data['date']['raw'] = $return[0]['data']; + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'date')) + { + $this->data['date']['raw'] = $return[0]['data']; + } + + if (!empty($this->data['date']['raw'])) + { + $parser = SimplePie_Parse_Date::get(); + $this->data['date']['parsed'] = $parser->parse($this->data['date']['raw']); + } + else + { + $this->data['date'] = null; + } + } + if ($this->data['date']) + { + $date_format = (string) $date_format; + switch ($date_format) + { + case '': + return $this->sanitize($this->data['date']['raw'], SIMPLEPIE_CONSTRUCT_TEXT); + + case 'U': + return $this->data['date']['parsed']; + + default: + return date($date_format, $this->data['date']['parsed']); + } + } + else + { + return null; + } + } + + function get_local_date($date_format = '%c') + { + if (!$date_format) + { + return $this->sanitize($this->get_date(''), SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif (($date = $this->get_date('U')) !== null && $date !== false) + { + return strftime($date_format, $date); + } + else + { + return null; + } + } + + function get_permalink() + { + $link = $this->get_link(); + $enclosure = $this->get_enclosure(0); + if ($link !== null) + { + return $link; + } + elseif ($enclosure !== null) + { + return $enclosure->get_link(); + } + else + { + return null; + } + } + + function get_link($key = 0, $rel = 'alternate') + { + $links = $this->get_links($rel); + if ($links[$key] !== null) + { + return $links[$key]; + } + else + { + return null; + } + } + + function get_links($rel = 'alternate') + { + if (!isset($this->data['links'])) + { + $this->data['links'] = array(); + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link') as $link) + { + if (isset($link['attribs']['']['href'])) + { + $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; + $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); + + } + } + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link') as $link) + { + if (isset($link['attribs']['']['href'])) + { + $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; + $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); + } + } + if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link')) + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link')) + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link')) + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid')) + { + if (!isset($links[0]['attribs']['']['isPermaLink']) || strtolower(trim($links[0]['attribs']['']['isPermaLink'])) === 'true') + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + } + + $keys = array_keys($this->data['links']); + foreach ($keys as $key) + { + if (SimplePie_Misc::is_isegment_nz_nc($key)) + { + if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key])) + { + $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]); + $this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]; + } + else + { + $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key]; + } + } + elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY) + { + $this->data['links'][substr($key, 41)] =& $this->data['links'][$key]; + } + $this->data['links'][$key] = array_unique($this->data['links'][$key]); + } + } + if (isset($this->data['links'][$rel])) + { + return $this->data['links'][$rel]; + } + else + { + return null; + } + } + + /** + * @todo Add ability to prefer one type of content over another (in a media group). + */ + function get_enclosure($key = 0, $prefer = null) + { + $enclosures = $this->get_enclosures(); + if (isset($enclosures[$key])) + { + return $enclosures[$key]; + } + else + { + return null; + } + } + + /** + * Grabs all available enclosures (podcasts, etc.) + * + * Supports the RSS tag, as well as Media RSS and iTunes RSS. + * + * At this point, we're pretty much assuming that all enclosures for an item are the same content. Anything else is too complicated to properly support. + * + * @todo Add support for end-user defined sorting of enclosures by type/handler (so we can prefer the faster-loading FLV over MP4). + * @todo If an element exists at a level, but it's value is empty, we should fall back to the value from the parent (if it exists). + */ + function get_enclosures() + { + if (!isset($this->data['enclosures'])) + { + $this->data['enclosures'] = array(); + + // Elements + $captions_parent = null; + $categories_parent = null; + $copyrights_parent = null; + $credits_parent = null; + $description_parent = null; + $duration_parent = null; + $hashes_parent = null; + $keywords_parent = null; + $player_parent = null; + $ratings_parent = null; + $restrictions_parent = null; + $thumbnails_parent = null; + $title_parent = null; + + // Let's do the channel and item-level ones first, and just re-use them if we need to. + $parent = $this->get_feed(); + + // CAPTIONS + if ($captions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'text')) + { + foreach ($captions as $caption) + { + $caption_type = null; + $caption_lang = null; + $caption_startTime = null; + $caption_endTime = null; + $caption_text = null; + if (isset($caption['attribs']['']['type'])) + { + $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['lang'])) + { + $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['start'])) + { + $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['end'])) + { + $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['data'])) + { + $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $captions_parent[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text); + } + } + elseif ($captions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'text')) + { + foreach ($captions as $caption) + { + $caption_type = null; + $caption_lang = null; + $caption_startTime = null; + $caption_endTime = null; + $caption_text = null; + if (isset($caption['attribs']['']['type'])) + { + $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['lang'])) + { + $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['start'])) + { + $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['end'])) + { + $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['data'])) + { + $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $captions_parent[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text); + } + } + if (is_array($captions_parent)) + { + $captions_parent = array_values(SimplePie_Misc::array_unique($captions_parent)); + } + + // CATEGORIES + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'category') as $category) + { + $term = null; + $scheme = null; + $label = null; + if (isset($category['data'])) + { + $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) + { + $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $scheme = 'http://search.yahoo.com/mrss/category_schema'; + } + if (isset($category['attribs']['']['label'])) + { + $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories_parent[] = new $this->feed->category_class($term, $scheme, $label); + } + foreach ((array) $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'category') as $category) + { + $term = null; + $scheme = null; + $label = null; + if (isset($category['data'])) + { + $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) + { + $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $scheme = 'http://search.yahoo.com/mrss/category_schema'; + } + if (isset($category['attribs']['']['label'])) + { + $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories_parent[] = new $this->feed->category_class($term, $scheme, $label); + } + foreach ((array) $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'category') as $category) + { + $term = null; + $scheme = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; + $label = null; + if (isset($category['attribs']['']['text'])) + { + $label = $this->sanitize($category['attribs']['']['text'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories_parent[] = new $this->feed->category_class($term, $scheme, $label); + + if (isset($category['child'][SIMPLEPIE_NAMESPACE_ITUNES]['category'])) + { + foreach ((array) $category['child'][SIMPLEPIE_NAMESPACE_ITUNES]['category'] as $subcategory) + { + if (isset($subcategory['attribs']['']['text'])) + { + $label = $this->sanitize($subcategory['attribs']['']['text'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories_parent[] = new $this->feed->category_class($term, $scheme, $label); + } + } + } + if (is_array($categories_parent)) + { + $categories_parent = array_values(SimplePie_Misc::array_unique($categories_parent)); + } + + // COPYRIGHT + if ($copyright = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'copyright')) + { + $copyright_url = null; + $copyright_label = null; + if (isset($copyright[0]['attribs']['']['url'])) + { + $copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($copyright[0]['data'])) + { + $copyright_label = $this->sanitize($copyright[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $copyrights_parent = new $this->feed->copyright_class($copyright_url, $copyright_label); + } + elseif ($copyright = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'copyright')) + { + $copyright_url = null; + $copyright_label = null; + if (isset($copyright[0]['attribs']['']['url'])) + { + $copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($copyright[0]['data'])) + { + $copyright_label = $this->sanitize($copyright[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $copyrights_parent = new $this->feed->copyright_class($copyright_url, $copyright_label); + } + + // CREDITS + if ($credits = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'credit')) + { + foreach ($credits as $credit) + { + $credit_role = null; + $credit_scheme = null; + $credit_name = null; + if (isset($credit['attribs']['']['role'])) + { + $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($credit['attribs']['']['scheme'])) + { + $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $credit_scheme = 'urn:ebu'; + } + if (isset($credit['data'])) + { + $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $credits_parent[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name); + } + } + elseif ($credits = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'credit')) + { + foreach ($credits as $credit) + { + $credit_role = null; + $credit_scheme = null; + $credit_name = null; + if (isset($credit['attribs']['']['role'])) + { + $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($credit['attribs']['']['scheme'])) + { + $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $credit_scheme = 'urn:ebu'; + } + if (isset($credit['data'])) + { + $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $credits_parent[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name); + } + } + if (is_array($credits_parent)) + { + $credits_parent = array_values(SimplePie_Misc::array_unique($credits_parent)); + } + + // DESCRIPTION + if ($description_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'description')) + { + if (isset($description_parent[0]['data'])) + { + $description_parent = $this->sanitize($description_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + } + elseif ($description_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'description')) + { + if (isset($description_parent[0]['data'])) + { + $description_parent = $this->sanitize($description_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + } + + // DURATION + if ($duration_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'duration')) + { + $seconds = null; + $minutes = null; + $hours = null; + if (isset($duration_parent[0]['data'])) + { + $temp = explode(':', $this->sanitize($duration_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + if (sizeof($temp) > 0) + { + $seconds = (int) array_pop($temp); + } + if (sizeof($temp) > 0) + { + $minutes = (int) array_pop($temp); + $seconds += $minutes * 60; + } + if (sizeof($temp) > 0) + { + $hours = (int) array_pop($temp); + $seconds += $hours * 3600; + } + unset($temp); + $duration_parent = $seconds; + } + } + + // HASHES + if ($hashes_iterator = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'hash')) + { + foreach ($hashes_iterator as $hash) + { + $value = null; + $algo = null; + if (isset($hash['data'])) + { + $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($hash['attribs']['']['algo'])) + { + $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $algo = 'md5'; + } + $hashes_parent[] = $algo.':'.$value; + } + } + elseif ($hashes_iterator = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'hash')) + { + foreach ($hashes_iterator as $hash) + { + $value = null; + $algo = null; + if (isset($hash['data'])) + { + $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($hash['attribs']['']['algo'])) + { + $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $algo = 'md5'; + } + $hashes_parent[] = $algo.':'.$value; + } + } + if (is_array($hashes_parent)) + { + $hashes_parent = array_values(SimplePie_Misc::array_unique($hashes_parent)); + } + + // KEYWORDS + if ($keywords = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'keywords')) + { + if (isset($keywords[0]['data'])) + { + $temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + foreach ($temp as $word) + { + $keywords_parent[] = trim($word); + } + } + unset($temp); + } + elseif ($keywords = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'keywords')) + { + if (isset($keywords[0]['data'])) + { + $temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + foreach ($temp as $word) + { + $keywords_parent[] = trim($word); + } + } + unset($temp); + } + elseif ($keywords = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'keywords')) + { + if (isset($keywords[0]['data'])) + { + $temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + foreach ($temp as $word) + { + $keywords_parent[] = trim($word); + } + } + unset($temp); + } + elseif ($keywords = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'keywords')) + { + if (isset($keywords[0]['data'])) + { + $temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + foreach ($temp as $word) + { + $keywords_parent[] = trim($word); + } + } + unset($temp); + } + if (is_array($keywords_parent)) + { + $keywords_parent = array_values(SimplePie_Misc::array_unique($keywords_parent)); + } + + // PLAYER + if ($player_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'player')) + { + if (isset($player_parent[0]['attribs']['']['url'])) + { + $player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + } + elseif ($player_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'player')) + { + if (isset($player_parent[0]['attribs']['']['url'])) + { + $player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + } + + // RATINGS + if ($ratings = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'rating')) + { + foreach ($ratings as $rating) + { + $rating_scheme = null; + $rating_value = null; + if (isset($rating['attribs']['']['scheme'])) + { + $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $rating_scheme = 'urn:simple'; + } + if (isset($rating['data'])) + { + $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $ratings_parent[] = new $this->feed->rating_class($rating_scheme, $rating_value); + } + } + elseif ($ratings = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'explicit')) + { + foreach ($ratings as $rating) + { + $rating_scheme = 'urn:itunes'; + $rating_value = null; + if (isset($rating['data'])) + { + $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $ratings_parent[] = new $this->feed->rating_class($rating_scheme, $rating_value); + } + } + elseif ($ratings = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'rating')) + { + foreach ($ratings as $rating) + { + $rating_scheme = null; + $rating_value = null; + if (isset($rating['attribs']['']['scheme'])) + { + $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $rating_scheme = 'urn:simple'; + } + if (isset($rating['data'])) + { + $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $ratings_parent[] = new $this->feed->rating_class($rating_scheme, $rating_value); + } + } + elseif ($ratings = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'explicit')) + { + foreach ($ratings as $rating) + { + $rating_scheme = 'urn:itunes'; + $rating_value = null; + if (isset($rating['data'])) + { + $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $ratings_parent[] = new $this->feed->rating_class($rating_scheme, $rating_value); + } + } + if (is_array($ratings_parent)) + { + $ratings_parent = array_values(SimplePie_Misc::array_unique($ratings_parent)); + } + + // RESTRICTIONS + if ($restrictions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'restriction')) + { + foreach ($restrictions as $restriction) + { + $restriction_relationship = null; + $restriction_type = null; + $restriction_value = null; + if (isset($restriction['attribs']['']['relationship'])) + { + $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['attribs']['']['type'])) + { + $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['data'])) + { + $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $restrictions_parent[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value); + } + } + elseif ($restrictions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'block')) + { + foreach ($restrictions as $restriction) + { + $restriction_relationship = 'allow'; + $restriction_type = null; + $restriction_value = 'itunes'; + if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes') + { + $restriction_relationship = 'deny'; + } + $restrictions_parent[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value); + } + } + elseif ($restrictions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'restriction')) + { + foreach ($restrictions as $restriction) + { + $restriction_relationship = null; + $restriction_type = null; + $restriction_value = null; + if (isset($restriction['attribs']['']['relationship'])) + { + $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['attribs']['']['type'])) + { + $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['data'])) + { + $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $restrictions_parent[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value); + } + } + elseif ($restrictions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'block')) + { + foreach ($restrictions as $restriction) + { + $restriction_relationship = 'allow'; + $restriction_type = null; + $restriction_value = 'itunes'; + if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes') + { + $restriction_relationship = 'deny'; + } + $restrictions_parent[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value); + } + } + if (is_array($restrictions_parent)) + { + $restrictions_parent = array_values(SimplePie_Misc::array_unique($restrictions_parent)); + } + + // THUMBNAILS + if ($thumbnails = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'thumbnail')) + { + foreach ($thumbnails as $thumbnail) + { + if (isset($thumbnail['attribs']['']['url'])) + { + $thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + } + } + elseif ($thumbnails = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'thumbnail')) + { + foreach ($thumbnails as $thumbnail) + { + if (isset($thumbnail['attribs']['']['url'])) + { + $thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + } + } + + // TITLES + if ($title_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'title')) + { + if (isset($title_parent[0]['data'])) + { + $title_parent = $this->sanitize($title_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + } + elseif ($title_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'title')) + { + if (isset($title_parent[0]['data'])) + { + $title_parent = $this->sanitize($title_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + } + + // Clear the memory + unset($parent); + + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + // Elements + $captions = null; + $categories = null; + $copyrights = null; + $credits = null; + $description = null; + $hashes = null; + $keywords = null; + $player = null; + $ratings = null; + $restrictions = null; + $thumbnails = null; + $title = null; + + // If we have media:group tags, loop through them. + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'group') as $group) + { + if(isset($group['child']) && isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'])) + { + // If we have media:content tags, loop through them. + foreach ((array) $group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'] as $content) + { + if (isset($content['attribs']['']['url'])) + { + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + // Elements + $captions = null; + $categories = null; + $copyrights = null; + $credits = null; + $description = null; + $hashes = null; + $keywords = null; + $player = null; + $ratings = null; + $restrictions = null; + $thumbnails = null; + $title = null; + + // Start checking the attributes of media:content + if (isset($content['attribs']['']['bitrate'])) + { + $bitrate = $this->sanitize($content['attribs']['']['bitrate'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['channels'])) + { + $channels = $this->sanitize($content['attribs']['']['channels'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['duration'])) + { + $duration = $this->sanitize($content['attribs']['']['duration'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $duration = $duration_parent; + } + if (isset($content['attribs']['']['expression'])) + { + $expression = $this->sanitize($content['attribs']['']['expression'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['framerate'])) + { + $framerate = $this->sanitize($content['attribs']['']['framerate'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['height'])) + { + $height = $this->sanitize($content['attribs']['']['height'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['lang'])) + { + $lang = $this->sanitize($content['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['fileSize'])) + { + $length = ceil($content['attribs']['']['fileSize']); + } + if (isset($content['attribs']['']['medium'])) + { + $medium = $this->sanitize($content['attribs']['']['medium'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['samplingrate'])) + { + $samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['type'])) + { + $type = $this->sanitize($content['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['width'])) + { + $width = $this->sanitize($content['attribs']['']['width'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $url = $this->sanitize($content['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + + // Checking the other optional media: elements. Priority: media:content, media:group, item, channel + + // CAPTIONS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption) + { + $caption_type = null; + $caption_lang = null; + $caption_startTime = null; + $caption_endTime = null; + $caption_text = null; + if (isset($caption['attribs']['']['type'])) + { + $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['lang'])) + { + $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['start'])) + { + $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['end'])) + { + $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['data'])) + { + $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $captions[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text); + } + if (is_array($captions)) + { + $captions = array_values(SimplePie_Misc::array_unique($captions)); + } + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'])) + { + foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption) + { + $caption_type = null; + $caption_lang = null; + $caption_startTime = null; + $caption_endTime = null; + $caption_text = null; + if (isset($caption['attribs']['']['type'])) + { + $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['lang'])) + { + $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['start'])) + { + $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['end'])) + { + $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['data'])) + { + $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $captions[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text); + } + if (is_array($captions)) + { + $captions = array_values(SimplePie_Misc::array_unique($captions)); + } + } + else + { + $captions = $captions_parent; + } + + // CATEGORIES + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'])) + { + foreach ((array) $content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category) + { + $term = null; + $scheme = null; + $label = null; + if (isset($category['data'])) + { + $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) + { + $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $scheme = 'http://search.yahoo.com/mrss/category_schema'; + } + if (isset($category['attribs']['']['label'])) + { + $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories[] = new $this->feed->category_class($term, $scheme, $label); + } + } + if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'])) + { + foreach ((array) $group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category) + { + $term = null; + $scheme = null; + $label = null; + if (isset($category['data'])) + { + $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) + { + $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $scheme = 'http://search.yahoo.com/mrss/category_schema'; + } + if (isset($category['attribs']['']['label'])) + { + $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories[] = new $this->feed->category_class($term, $scheme, $label); + } + } + if (is_array($categories) && is_array($categories_parent)) + { + $categories = array_values(SimplePie_Misc::array_unique(array_merge($categories, $categories_parent))); + } + elseif (is_array($categories)) + { + $categories = array_values(SimplePie_Misc::array_unique($categories)); + } + elseif (is_array($categories_parent)) + { + $categories = array_values(SimplePie_Misc::array_unique($categories_parent)); + } + + // COPYRIGHTS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'])) + { + $copyright_url = null; + $copyright_label = null; + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) + { + $copyright_url = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'])) + { + $copyright_label = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $copyrights = new $this->feed->copyright_class($copyright_url, $copyright_label); + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'])) + { + $copyright_url = null; + $copyright_label = null; + if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) + { + $copyright_url = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'])) + { + $copyright_label = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $copyrights = new $this->feed->copyright_class($copyright_url, $copyright_label); + } + else + { + $copyrights = $copyrights_parent; + } + + // CREDITS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit) + { + $credit_role = null; + $credit_scheme = null; + $credit_name = null; + if (isset($credit['attribs']['']['role'])) + { + $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($credit['attribs']['']['scheme'])) + { + $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $credit_scheme = 'urn:ebu'; + } + if (isset($credit['data'])) + { + $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $credits[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name); + } + if (is_array($credits)) + { + $credits = array_values(SimplePie_Misc::array_unique($credits)); + } + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'])) + { + foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit) + { + $credit_role = null; + $credit_scheme = null; + $credit_name = null; + if (isset($credit['attribs']['']['role'])) + { + $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($credit['attribs']['']['scheme'])) + { + $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $credit_scheme = 'urn:ebu'; + } + if (isset($credit['data'])) + { + $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $credits[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name); + } + if (is_array($credits)) + { + $credits = array_values(SimplePie_Misc::array_unique($credits)); + } + } + else + { + $credits = $credits_parent; + } + + // DESCRIPTION + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'])) + { + $description = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'])) + { + $description = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $description = $description_parent; + } + + // HASHES + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash) + { + $value = null; + $algo = null; + if (isset($hash['data'])) + { + $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($hash['attribs']['']['algo'])) + { + $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $algo = 'md5'; + } + $hashes[] = $algo.':'.$value; + } + if (is_array($hashes)) + { + $hashes = array_values(SimplePie_Misc::array_unique($hashes)); + } + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'])) + { + foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash) + { + $value = null; + $algo = null; + if (isset($hash['data'])) + { + $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($hash['attribs']['']['algo'])) + { + $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $algo = 'md5'; + } + $hashes[] = $algo.':'.$value; + } + if (is_array($hashes)) + { + $hashes = array_values(SimplePie_Misc::array_unique($hashes)); + } + } + else + { + $hashes = $hashes_parent; + } + + // KEYWORDS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'])) + { + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'])) + { + $temp = explode(',', $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + foreach ($temp as $word) + { + $keywords[] = trim($word); + } + unset($temp); + } + if (is_array($keywords)) + { + $keywords = array_values(SimplePie_Misc::array_unique($keywords)); + } + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'])) + { + if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'])) + { + $temp = explode(',', $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + foreach ($temp as $word) + { + $keywords[] = trim($word); + } + unset($temp); + } + if (is_array($keywords)) + { + $keywords = array_values(SimplePie_Misc::array_unique($keywords)); + } + } + else + { + $keywords = $keywords_parent; + } + + // PLAYER + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'])) + { + $player = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'])) + { + $player = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + else + { + $player = $player_parent; + } + + // RATINGS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating) + { + $rating_scheme = null; + $rating_value = null; + if (isset($rating['attribs']['']['scheme'])) + { + $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $rating_scheme = 'urn:simple'; + } + if (isset($rating['data'])) + { + $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $ratings[] = new $this->feed->rating_class($rating_scheme, $rating_value); + } + if (is_array($ratings)) + { + $ratings = array_values(SimplePie_Misc::array_unique($ratings)); + } + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'])) + { + foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating) + { + $rating_scheme = null; + $rating_value = null; + if (isset($rating['attribs']['']['scheme'])) + { + $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $rating_scheme = 'urn:simple'; + } + if (isset($rating['data'])) + { + $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $ratings[] = new $this->feed->rating_class($rating_scheme, $rating_value); + } + if (is_array($ratings)) + { + $ratings = array_values(SimplePie_Misc::array_unique($ratings)); + } + } + else + { + $ratings = $ratings_parent; + } + + // RESTRICTIONS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction) + { + $restriction_relationship = null; + $restriction_type = null; + $restriction_value = null; + if (isset($restriction['attribs']['']['relationship'])) + { + $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['attribs']['']['type'])) + { + $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['data'])) + { + $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $restrictions[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value); + } + if (is_array($restrictions)) + { + $restrictions = array_values(SimplePie_Misc::array_unique($restrictions)); + } + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'])) + { + foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction) + { + $restriction_relationship = null; + $restriction_type = null; + $restriction_value = null; + if (isset($restriction['attribs']['']['relationship'])) + { + $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['attribs']['']['type'])) + { + $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['data'])) + { + $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $restrictions[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value); + } + if (is_array($restrictions)) + { + $restrictions = array_values(SimplePie_Misc::array_unique($restrictions)); + } + } + else + { + $restrictions = $restrictions_parent; + } + + // THUMBNAILS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) + { + $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + if (is_array($thumbnails)) + { + $thumbnails = array_values(SimplePie_Misc::array_unique($thumbnails)); + } + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'])) + { + foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) + { + $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + if (is_array($thumbnails)) + { + $thumbnails = array_values(SimplePie_Misc::array_unique($thumbnails)); + } + } + else + { + $thumbnails = $thumbnails_parent; + } + + // TITLES + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'])) + { + $title = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'])) + { + $title = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $title = $title_parent; + } + + $this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width); + } + } + } + } + + // If we have standalone media:content tags, loop through them. + if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'])) + { + foreach ((array) $this->data['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'] as $content) + { + if (isset($content['attribs']['']['url']) || isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'])) + { + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + // Elements + $captions = null; + $categories = null; + $copyrights = null; + $credits = null; + $description = null; + $hashes = null; + $keywords = null; + $player = null; + $ratings = null; + $restrictions = null; + $thumbnails = null; + $title = null; + + // Start checking the attributes of media:content + if (isset($content['attribs']['']['bitrate'])) + { + $bitrate = $this->sanitize($content['attribs']['']['bitrate'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['channels'])) + { + $channels = $this->sanitize($content['attribs']['']['channels'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['duration'])) + { + $duration = $this->sanitize($content['attribs']['']['duration'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $duration = $duration_parent; + } + if (isset($content['attribs']['']['expression'])) + { + $expression = $this->sanitize($content['attribs']['']['expression'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['framerate'])) + { + $framerate = $this->sanitize($content['attribs']['']['framerate'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['height'])) + { + $height = $this->sanitize($content['attribs']['']['height'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['lang'])) + { + $lang = $this->sanitize($content['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['fileSize'])) + { + $length = ceil($content['attribs']['']['fileSize']); + } + if (isset($content['attribs']['']['medium'])) + { + $medium = $this->sanitize($content['attribs']['']['medium'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['samplingrate'])) + { + $samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['type'])) + { + $type = $this->sanitize($content['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['width'])) + { + $width = $this->sanitize($content['attribs']['']['width'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['attribs']['']['url'])) + { + $url = $this->sanitize($content['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + // Checking the other optional media: elements. Priority: media:content, media:group, item, channel + + // CAPTIONS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption) + { + $caption_type = null; + $caption_lang = null; + $caption_startTime = null; + $caption_endTime = null; + $caption_text = null; + if (isset($caption['attribs']['']['type'])) + { + $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['lang'])) + { + $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['start'])) + { + $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['attribs']['']['end'])) + { + $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($caption['data'])) + { + $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $captions[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text); + } + if (is_array($captions)) + { + $captions = array_values(SimplePie_Misc::array_unique($captions)); + } + } + else + { + $captions = $captions_parent; + } + + // CATEGORIES + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'])) + { + foreach ((array) $content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category) + { + $term = null; + $scheme = null; + $label = null; + if (isset($category['data'])) + { + $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) + { + $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $scheme = 'http://search.yahoo.com/mrss/category_schema'; + } + if (isset($category['attribs']['']['label'])) + { + $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories[] = new $this->feed->category_class($term, $scheme, $label); + } + } + if (is_array($categories) && is_array($categories_parent)) + { + $categories = array_values(SimplePie_Misc::array_unique(array_merge($categories, $categories_parent))); + } + elseif (is_array($categories)) + { + $categories = array_values(SimplePie_Misc::array_unique($categories)); + } + elseif (is_array($categories_parent)) + { + $categories = array_values(SimplePie_Misc::array_unique($categories_parent)); + } + else + { + $categories = null; + } + + // COPYRIGHTS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'])) + { + $copyright_url = null; + $copyright_label = null; + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) + { + $copyright_url = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'])) + { + $copyright_label = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $copyrights = new $this->feed->copyright_class($copyright_url, $copyright_label); + } + else + { + $copyrights = $copyrights_parent; + } + + // CREDITS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit) + { + $credit_role = null; + $credit_scheme = null; + $credit_name = null; + if (isset($credit['attribs']['']['role'])) + { + $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($credit['attribs']['']['scheme'])) + { + $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $credit_scheme = 'urn:ebu'; + } + if (isset($credit['data'])) + { + $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $credits[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name); + } + if (is_array($credits)) + { + $credits = array_values(SimplePie_Misc::array_unique($credits)); + } + } + else + { + $credits = $credits_parent; + } + + // DESCRIPTION + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'])) + { + $description = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $description = $description_parent; + } + + // HASHES + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash) + { + $value = null; + $algo = null; + if (isset($hash['data'])) + { + $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($hash['attribs']['']['algo'])) + { + $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $algo = 'md5'; + } + $hashes[] = $algo.':'.$value; + } + if (is_array($hashes)) + { + $hashes = array_values(SimplePie_Misc::array_unique($hashes)); + } + } + else + { + $hashes = $hashes_parent; + } + + // KEYWORDS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'])) + { + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'])) + { + $temp = explode(',', $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); + foreach ($temp as $word) + { + $keywords[] = trim($word); + } + unset($temp); + } + if (is_array($keywords)) + { + $keywords = array_values(SimplePie_Misc::array_unique($keywords)); + } + } + else + { + $keywords = $keywords_parent; + } + + // PLAYER + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'])) + { + $player = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + else + { + $player = $player_parent; + } + + // RATINGS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating) + { + $rating_scheme = null; + $rating_value = null; + if (isset($rating['attribs']['']['scheme'])) + { + $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $rating_scheme = 'urn:simple'; + } + if (isset($rating['data'])) + { + $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $ratings[] = new $this->feed->rating_class($rating_scheme, $rating_value); + } + if (is_array($ratings)) + { + $ratings = array_values(SimplePie_Misc::array_unique($ratings)); + } + } + else + { + $ratings = $ratings_parent; + } + + // RESTRICTIONS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction) + { + $restriction_relationship = null; + $restriction_type = null; + $restriction_value = null; + if (isset($restriction['attribs']['']['relationship'])) + { + $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['attribs']['']['type'])) + { + $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($restriction['data'])) + { + $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $restrictions[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value); + } + if (is_array($restrictions)) + { + $restrictions = array_values(SimplePie_Misc::array_unique($restrictions)); + } + } + else + { + $restrictions = $restrictions_parent; + } + + // THUMBNAILS + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'])) + { + foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) + { + $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); + } + if (is_array($thumbnails)) + { + $thumbnails = array_values(SimplePie_Misc::array_unique($thumbnails)); + } + } + else + { + $thumbnails = $thumbnails_parent; + } + + // TITLES + if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'])) + { + $title = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $title = $title_parent; + } + + $this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width); + } + } + } + + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link') as $link) + { + if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure') + { + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + $url = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); + if (isset($link['attribs']['']['type'])) + { + $type = $this->sanitize($link['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($link['attribs']['']['length'])) + { + $length = ceil($link['attribs']['']['length']); + } + + // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor + $this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width); + } + } + + foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link') as $link) + { + if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure') + { + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + $url = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); + if (isset($link['attribs']['']['type'])) + { + $type = $this->sanitize($link['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($link['attribs']['']['length'])) + { + $length = ceil($link['attribs']['']['length']); + } + + // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor + $this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width); + } + } + + if ($enclosure = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'enclosure')) + { + if (isset($enclosure[0]['attribs']['']['url'])) + { + // Attributes + $bitrate = null; + $channels = null; + $duration = null; + $expression = null; + $framerate = null; + $height = null; + $javascript = null; + $lang = null; + $length = null; + $medium = null; + $samplingrate = null; + $type = null; + $url = null; + $width = null; + + $url = $this->sanitize($enclosure[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($enclosure[0])); + if (isset($enclosure[0]['attribs']['']['type'])) + { + $type = $this->sanitize($enclosure[0]['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($enclosure[0]['attribs']['']['length'])) + { + $length = ceil($enclosure[0]['attribs']['']['length']); + } + + // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor + $this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width); + } + } + + if (sizeof($this->data['enclosures']) === 0 && ($url || $type || $length || $bitrate || $captions_parent || $categories_parent || $channels || $copyrights_parent || $credits_parent || $description_parent || $duration_parent || $expression || $framerate || $hashes_parent || $height || $keywords_parent || $lang || $medium || $player_parent || $ratings_parent || $restrictions_parent || $samplingrate || $thumbnails_parent || $title_parent || $width)) + { + // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor + $this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width); + } + + $this->data['enclosures'] = array_values(SimplePie_Misc::array_unique($this->data['enclosures'])); + } + if (!empty($this->data['enclosures'])) + { + return $this->data['enclosures']; + } + else + { + return null; + } + } + + function get_latitude() + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat')) + { + return (float) $return[0]['data']; + } + elseif (($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) + { + return (float) $match[1]; + } + else + { + return null; + } + } + + function get_longitude() + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long')) + { + return (float) $return[0]['data']; + } + elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon')) + { + return (float) $return[0]['data']; + } + elseif (($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) + { + return (float) $match[2]; + } + else + { + return null; + } + } + + function get_source() + { + if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'source')) + { + return new $this->feed->source_class($this, $return[0]); + } + else + { + return null; + } + } + + /** + * Creates the add_to_* methods' return data + * + * @access private + * @param string $item_url String to prefix to the item permalink + * @param string $title_url String to prefix to the item title + * (and suffix to the item permalink) + * @return mixed URL if feed exists, false otherwise + */ + function add_to_service($item_url, $title_url = null, $summary_url = null) + { + if ($this->get_permalink() !== null) + { + $return = $item_url . rawurlencode($this->get_permalink()); + if ($title_url !== null && $this->get_title() !== null) + { + $return .= $title_url . rawurlencode($this->get_title()); + } + if ($summary_url !== null && $this->get_description() !== null) + { + $return .= $summary_url . rawurlencode($this->get_description()); + } + return $this->sanitize($return, SIMPLEPIE_CONSTRUCT_IRI); + } + else + { + return null; + } + } + + function add_to_blinklist() + { + return $this->add_to_service('http://www.blinklist.com/index.php?Action=Blink/addblink.php&Description=&Url=', '&Title='); + } + + function add_to_blogmarks() + { + return $this->add_to_service('http://blogmarks.net/my/new.php?mini=1&simple=1&url=', '&title='); + } + + function add_to_delicious() + { + return $this->add_to_service('http://del.icio.us/post/?v=4&url=', '&title='); + } + + function add_to_digg() + { + return $this->add_to_service('http://digg.com/submit?url=', '&title=', '&bodytext='); + } + + function add_to_furl() + { + return $this->add_to_service('http://www.furl.net/storeIt.jsp?u=', '&t='); + } + + function add_to_magnolia() + { + return $this->add_to_service('http://ma.gnolia.com/bookmarklet/add?url=', '&title='); + } + + function add_to_myweb20() + { + return $this->add_to_service('http://myweb2.search.yahoo.com/myresults/bookmarklet?u=', '&t='); + } + + function add_to_newsvine() + { + return $this->add_to_service('http://www.newsvine.com/_wine/save?u=', '&h='); + } + + function add_to_reddit() + { + return $this->add_to_service('http://reddit.com/submit?url=', '&title='); + } + + function add_to_segnalo() + { + return $this->add_to_service('http://segnalo.com/post.html.php?url=', '&title='); + } + + function add_to_simpy() + { + return $this->add_to_service('http://www.simpy.com/simpy/LinkAdd.do?href=', '&title='); + } + + function add_to_spurl() + { + return $this->add_to_service('http://www.spurl.net/spurl.php?v=3&url=', '&title='); + } + + function add_to_wists() + { + return $this->add_to_service('http://wists.com/r.php?c=&r=', '&title='); + } + + function search_technorati() + { + return $this->add_to_service('http://www.technorati.com/search/'); + } +} + +class SimplePie_Source +{ + var $item; + var $data = array(); + + function SimplePie_Source($item, $data) + { + $this->item = $item; + $this->data = $data; + } + + function __toString() + { + return md5(serialize($this->data)); + } + + function get_source_tags($namespace, $tag) + { + if (isset($this->data['child'][$namespace][$tag])) + { + return $this->data['child'][$namespace][$tag]; + } + else + { + return null; + } + } + + function get_base($element = array()) + { + return $this->item->get_base($element); + } + + function sanitize($data, $type, $base = '') + { + return $this->item->sanitize($data, $type, $base); + } + + function get_item() + { + return $this->item; + } + + function get_title() + { + if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + return null; + } + } + + function get_category($key = 0) + { + $categories = $this->get_categories(); + if (isset($categories[$key])) + { + return $categories[$key]; + } + else + { + return null; + } + } + + function get_categories() + { + $categories = array(); + + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category) + { + $term = null; + $scheme = null; + $label = null; + if (isset($category['attribs']['']['term'])) + { + $term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['scheme'])) + { + $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($category['attribs']['']['label'])) + { + $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); + } + $categories[] = new $this->item->feed->category_class($term, $scheme, $label); + } + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category) + { + // This is really the label, but keep this as the term also for BC. + // Label will also work on retrieving because that falls back to term. + $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); + if (isset($category['attribs']['']['domain'])) + { + $scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + $scheme = null; + } + $categories[] = new $this->item->feed->category_class($term, $scheme, null); + } + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category) + { + $categories[] = new $this->item->feed->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); + } + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category) + { + $categories[] = new $this->item->feed->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); + } + + if (!empty($categories)) + { + return SimplePie_Misc::array_unique($categories); + } + else + { + return null; + } + } + + function get_author($key = 0) + { + $authors = $this->get_authors(); + if (isset($authors[$key])) + { + return $authors[$key]; + } + else + { + return null; + } + } + + function get_authors() + { + $authors = array(); + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author) + { + $name = null; + $uri = null; + $email = null; + if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) + { + $name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) + { + $uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); + } + if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) + { + $email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $uri !== null) + { + $authors[] = new $this->item->feed->author_class($name, $uri, $email); + } + } + if ($author = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author')) + { + $name = null; + $url = null; + $email = null; + if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) + { + $name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) + { + $url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); + } + if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) + { + $email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $url !== null) + { + $authors[] = new $this->item->feed->author_class($name, $url, $email); + } + } + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author) + { + $authors[] = new $this->item->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); + } + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author) + { + $authors[] = new $this->item->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); + } + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author) + { + $authors[] = new $this->item->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); + } + + if (!empty($authors)) + { + return SimplePie_Misc::array_unique($authors); + } + else + { + return null; + } + } + + function get_contributor($key = 0) + { + $contributors = $this->get_contributors(); + if (isset($contributors[$key])) + { + return $contributors[$key]; + } + else + { + return null; + } + } + + function get_contributors() + { + $contributors = array(); + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor) + { + $name = null; + $uri = null; + $email = null; + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) + { + $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) + { + $uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) + { + $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $uri !== null) + { + $contributors[] = new $this->item->feed->author_class($name, $uri, $email); + } + } + foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor) + { + $name = null; + $url = null; + $email = null; + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) + { + $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) + { + $url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); + } + if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) + { + $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + if ($name !== null || $email !== null || $url !== null) + { + $contributors[] = new $this->item->feed->author_class($name, $url, $email); + } + } + + if (!empty($contributors)) + { + return SimplePie_Misc::array_unique($contributors); + } + else + { + return null; + } + } + + function get_link($key = 0, $rel = 'alternate') + { + $links = $this->get_links($rel); + if (isset($links[$key])) + { + return $links[$key]; + } + else + { + return null; + } + } + + /** + * Added for parity between the parent-level and the item/entry-level. + */ + function get_permalink() + { + return $this->get_link(0); + } + + function get_links($rel = 'alternate') + { + if (!isset($this->data['links'])) + { + $this->data['links'] = array(); + if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link')) + { + foreach ($links as $link) + { + if (isset($link['attribs']['']['href'])) + { + $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; + $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); + } + } + } + if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link')) + { + foreach ($links as $link) + { + if (isset($link['attribs']['']['href'])) + { + $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; + $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); + + } + } + } + if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link')) + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link')) + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link')) + { + $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); + } + + $keys = array_keys($this->data['links']); + foreach ($keys as $key) + { + if (SimplePie_Misc::is_isegment_nz_nc($key)) + { + if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key])) + { + $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]); + $this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]; + } + else + { + $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key]; + } + } + elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY) + { + $this->data['links'][substr($key, 41)] =& $this->data['links'][$key]; + } + $this->data['links'][$key] = array_unique($this->data['links'][$key]); + } + } + + if (isset($this->data['links'][$rel])) + { + return $this->data['links'][$rel]; + } + else + { + return null; + } + } + + function get_description() + { + if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'subtitle')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'tagline')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); + } + else + { + return null; + } + } + + function get_copyright() + { + if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'copyright')) + { + return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'copyright')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + return null; + } + } + + function get_language() + { + if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'language')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'language')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'language')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); + } + elseif (isset($this->data['xml_lang'])) + { + return $this->sanitize($this->data['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT); + } + else + { + return null; + } + } + + function get_latitude() + { + if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat')) + { + return (float) $return[0]['data']; + } + elseif (($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) + { + return (float) $match[1]; + } + else + { + return null; + } + } + + function get_longitude() + { + if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long')) + { + return (float) $return[0]['data']; + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon')) + { + return (float) $return[0]['data']; + } + elseif (($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) + { + return (float) $match[2]; + } + else + { + return null; + } + } + + function get_image_url() + { + if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'image')) + { + return $this->sanitize($return[0]['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'logo')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); + } + elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'icon')) + { + return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); + } + else + { + return null; + } + } +} + +class SimplePie_Author +{ + var $name; + var $link; + var $email; + + // Constructor, used to input the data + function SimplePie_Author($name = null, $link = null, $email = null) + { + $this->name = $name; + $this->link = $link; + $this->email = $email; + } + + function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + function get_name() + { + if ($this->name !== null) + { + return $this->name; + } + else + { + return null; + } + } + + function get_link() + { + if ($this->link !== null) + { + return $this->link; + } + else + { + return null; + } + } + + function get_email() + { + if ($this->email !== null) + { + return $this->email; + } + else + { + return null; + } + } +} + +class SimplePie_Category +{ + var $term; + var $scheme; + var $label; + + // Constructor, used to input the data + function SimplePie_Category($term = null, $scheme = null, $label = null) + { + $this->term = $term; + $this->scheme = $scheme; + $this->label = $label; + } + + function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + function get_term() + { + if ($this->term !== null) + { + return $this->term; + } + else + { + return null; + } + } + + function get_scheme() + { + if ($this->scheme !== null) + { + return $this->scheme; + } + else + { + return null; + } + } + + function get_label() + { + if ($this->label !== null) + { + return $this->label; + } + else + { + return $this->get_term(); + } + } +} + +class SimplePie_Enclosure +{ + var $bitrate; + var $captions; + var $categories; + var $channels; + var $copyright; + var $credits; + var $description; + var $duration; + var $expression; + var $framerate; + var $handler; + var $hashes; + var $height; + var $javascript; + var $keywords; + var $lang; + var $length; + var $link; + var $medium; + var $player; + var $ratings; + var $restrictions; + var $samplingrate; + var $thumbnails; + var $title; + var $type; + var $width; + + // Constructor, used to input the data + function SimplePie_Enclosure($link = null, $type = null, $length = null, $javascript = null, $bitrate = null, $captions = null, $categories = null, $channels = null, $copyright = null, $credits = null, $description = null, $duration = null, $expression = null, $framerate = null, $hashes = null, $height = null, $keywords = null, $lang = null, $medium = null, $player = null, $ratings = null, $restrictions = null, $samplingrate = null, $thumbnails = null, $title = null, $width = null) + { + $this->bitrate = $bitrate; + $this->captions = $captions; + $this->categories = $categories; + $this->channels = $channels; + $this->copyright = $copyright; + $this->credits = $credits; + $this->description = $description; + $this->duration = $duration; + $this->expression = $expression; + $this->framerate = $framerate; + $this->hashes = $hashes; + $this->height = $height; + $this->javascript = $javascript; + $this->keywords = $keywords; + $this->lang = $lang; + $this->length = $length; + $this->link = $link; + $this->medium = $medium; + $this->player = $player; + $this->ratings = $ratings; + $this->restrictions = $restrictions; + $this->samplingrate = $samplingrate; + $this->thumbnails = $thumbnails; + $this->title = $title; + $this->type = $type; + $this->width = $width; + if (class_exists('idna_convert')) + { + $idn = new idna_convert; + $parsed = SimplePie_Misc::parse_url($link); + $this->link = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']); + } + $this->handler = $this->get_handler(); // Needs to load last + } + + function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + function get_bitrate() + { + if ($this->bitrate !== null) + { + return $this->bitrate; + } + else + { + return null; + } + } + + function get_caption($key = 0) + { + $captions = $this->get_captions(); + if (isset($captions[$key])) + { + return $captions[$key]; + } + else + { + return null; + } + } + + function get_captions() + { + if ($this->captions !== null) + { + return $this->captions; + } + else + { + return null; + } + } + + function get_category($key = 0) + { + $categories = $this->get_categories(); + if (isset($categories[$key])) + { + return $categories[$key]; + } + else + { + return null; + } + } + + function get_categories() + { + if ($this->categories !== null) + { + return $this->categories; + } + else + { + return null; + } + } + + function get_channels() + { + if ($this->channels !== null) + { + return $this->channels; + } + else + { + return null; + } + } + + function get_copyright() + { + if ($this->copyright !== null) + { + return $this->copyright; + } + else + { + return null; + } + } + + function get_credit($key = 0) + { + $credits = $this->get_credits(); + if (isset($credits[$key])) + { + return $credits[$key]; + } + else + { + return null; + } + } + + function get_credits() + { + if ($this->credits !== null) + { + return $this->credits; + } + else + { + return null; + } + } + + function get_description() + { + if ($this->description !== null) + { + return $this->description; + } + else + { + return null; + } + } + + function get_duration($convert = false) + { + if ($this->duration !== null) + { + if ($convert) + { + $time = SimplePie_Misc::time_hms($this->duration); + return $time; + } + else + { + return $this->duration; + } + } + else + { + return null; + } + } + + function get_expression() + { + if ($this->expression !== null) + { + return $this->expression; + } + else + { + return 'full'; + } + } + + function get_extension() + { + if ($this->link !== null) + { + $url = SimplePie_Misc::parse_url($this->link); + if ($url['path'] !== '') + { + return pathinfo($url['path'], PATHINFO_EXTENSION); + } + } + return null; + } + + function get_framerate() + { + if ($this->framerate !== null) + { + return $this->framerate; + } + else + { + return null; + } + } + + function get_handler() + { + return $this->get_real_type(true); + } + + function get_hash($key = 0) + { + $hashes = $this->get_hashes(); + if (isset($hashes[$key])) + { + return $hashes[$key]; + } + else + { + return null; + } + } + + function get_hashes() + { + if ($this->hashes !== null) + { + return $this->hashes; + } + else + { + return null; + } + } + + function get_height() + { + if ($this->height !== null) + { + return $this->height; + } + else + { + return null; + } + } + + function get_language() + { + if ($this->lang !== null) + { + return $this->lang; + } + else + { + return null; + } + } + + function get_keyword($key = 0) + { + $keywords = $this->get_keywords(); + if (isset($keywords[$key])) + { + return $keywords[$key]; + } + else + { + return null; + } + } + + function get_keywords() + { + if ($this->keywords !== null) + { + return $this->keywords; + } + else + { + return null; + } + } + + function get_length() + { + if ($this->length !== null) + { + return $this->length; + } + else + { + return null; + } + } + + function get_link() + { + if ($this->link !== null) + { + return urldecode($this->link); + } + else + { + return null; + } + } + + function get_medium() + { + if ($this->medium !== null) + { + return $this->medium; + } + else + { + return null; + } + } + + function get_player() + { + if ($this->player !== null) + { + return $this->player; + } + else + { + return null; + } + } + + function get_rating($key = 0) + { + $ratings = $this->get_ratings(); + if (isset($ratings[$key])) + { + return $ratings[$key]; + } + else + { + return null; + } + } + + function get_ratings() + { + if ($this->ratings !== null) + { + return $this->ratings; + } + else + { + return null; + } + } + + function get_restriction($key = 0) + { + $restrictions = $this->get_restrictions(); + if (isset($restrictions[$key])) + { + return $restrictions[$key]; + } + else + { + return null; + } + } + + function get_restrictions() + { + if ($this->restrictions !== null) + { + return $this->restrictions; + } + else + { + return null; + } + } + + function get_sampling_rate() + { + if ($this->samplingrate !== null) + { + return $this->samplingrate; + } + else + { + return null; + } + } + + function get_size() + { + $length = $this->get_length(); + if ($length !== null) + { + return round($length/1048576, 2); + } + else + { + return null; + } + } + + function get_thumbnail($key = 0) + { + $thumbnails = $this->get_thumbnails(); + if (isset($thumbnails[$key])) + { + return $thumbnails[$key]; + } + else + { + return null; + } + } + + function get_thumbnails() + { + if ($this->thumbnails !== null) + { + return $this->thumbnails; + } + else + { + return null; + } + } + + function get_title() + { + if ($this->title !== null) + { + return $this->title; + } + else + { + return null; + } + } + + function get_type() + { + if ($this->type !== null) + { + return $this->type; + } + else + { + return null; + } + } + + function get_width() + { + if ($this->width !== null) + { + return $this->width; + } + else + { + return null; + } + } + + function native_embed($options='') + { + return $this->embed($options, true); + } + + /** + * @todo If the dimensions for media:content are defined, use them when width/height are set to 'auto'. + */ + function embed($options = '', $native = false) + { + // Set up defaults + $audio = ''; + $video = ''; + $alt = ''; + $altclass = ''; + $loop = 'false'; + $width = 'auto'; + $height = 'auto'; + $bgcolor = '#ffffff'; + $mediaplayer = ''; + $widescreen = false; + $handler = $this->get_handler(); + $type = $this->get_real_type(); + + // Process options and reassign values as necessary + if (is_array($options)) + { + extract($options); + } + else + { + $options = explode(',', $options); + foreach($options as $option) + { + $opt = explode(':', $option, 2); + if (isset($opt[0], $opt[1])) + { + $opt[0] = trim($opt[0]); + $opt[1] = trim($opt[1]); + switch ($opt[0]) + { + case 'audio': + $audio = $opt[1]; + break; + + case 'video': + $video = $opt[1]; + break; + + case 'alt': + $alt = $opt[1]; + break; + + case 'altclass': + $altclass = $opt[1]; + break; + + case 'loop': + $loop = $opt[1]; + break; + + case 'width': + $width = $opt[1]; + break; + + case 'height': + $height = $opt[1]; + break; + + case 'bgcolor': + $bgcolor = $opt[1]; + break; + + case 'mediaplayer': + $mediaplayer = $opt[1]; + break; + + case 'widescreen': + $widescreen = $opt[1]; + break; + } + } + } + } + + $mime = explode('/', $type, 2); + $mime = $mime[0]; + + // Process values for 'auto' + if ($width === 'auto') + { + if ($mime === 'video') + { + if ($height === 'auto') + { + $width = 480; + } + elseif ($widescreen) + { + $width = round((intval($height)/9)*16); + } + else + { + $width = round((intval($height)/3)*4); + } + } + else + { + $width = '100%'; + } + } + + if ($height === 'auto') + { + if ($mime === 'audio') + { + $height = 0; + } + elseif ($mime === 'video') + { + if ($width === 'auto') + { + if ($widescreen) + { + $height = 270; + } + else + { + $height = 360; + } + } + elseif ($widescreen) + { + $height = round((intval($width)/16)*9); + } + else + { + $height = round((intval($width)/4)*3); + } + } + else + { + $height = 376; + } + } + elseif ($mime === 'audio') + { + $height = 0; + } + + // Set proper placeholder value + if ($mime === 'audio') + { + $placeholder = $audio; + } + elseif ($mime === 'video') + { + $placeholder = $video; + } + + $embed = ''; + + // Make sure the JS library is included + if (!$native) + { + static $javascript_outputted = null; + if (!$javascript_outputted && $this->javascript) + { + $embed .= ''; + $javascript_outputted = true; + } + } + + // Odeo Feed MP3's + if ($handler === 'odeo') + { + if ($native) + { + $embed .= ''; + } + else + { + $embed .= ''; + } + } + + // Flash + elseif ($handler === 'flash') + { + if ($native) + { + $embed .= "get_link() . "\" pluginspage=\"http://adobe.com/go/getflashplayer\" type=\"$type\" quality=\"high\" width=\"$width\" height=\"$height\" bgcolor=\"$bgcolor\" loop=\"$loop\">"; + } + else + { + $embed .= ""; + } + } + + // Flash Media Player file types. + // Preferred handler for MP3 file types. + elseif ($handler === 'fmedia' || ($handler === 'mp3' && $mediaplayer !== '')) + { + $height += 20; + if ($native) + { + $embed .= "get_link().'?file_extension=.'.$this->get_extension()) . "&autostart=false&repeat=$loop&showdigits=true&showfsbutton=false\">"; + } + else + { + $embed .= ""; + } + } + + // QuickTime 7 file types. Need to test with QuickTime 6. + // Only handle MP3's if the Flash Media Player is not present. + elseif ($handler === 'quicktime' || ($handler === 'mp3' && $mediaplayer === '')) + { + $height += 16; + if ($native) + { + if ($placeholder !== '') + { + $embed .= "get_link() . "\" src=\"$placeholder\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"false\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\">"; + } + else + { + $embed .= "get_link() . "\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"true\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\">"; + } + } + else + { + $embed .= ""; + } + } + + // Windows Media + elseif ($handler === 'wmedia') + { + $height += 45; + if ($native) + { + $embed .= "get_link() . "\" autosize=\"1\" width=\"$width\" height=\"$height\" showcontrols=\"1\" showstatusbar=\"0\" showdisplay=\"0\" autostart=\"0\">"; + } + else + { + $embed .= ""; + } + } + + // Everything else + else $embed .= '' . $alt . ''; + + return $embed; + } + + function get_real_type($find_handler = false) + { + // If it's Odeo, let's get it out of the way. + if (substr(strtolower($this->get_link()), 0, 15) === 'http://odeo.com') + { + return 'odeo'; + } + + // Mime-types by handler. + $types_flash = array('application/x-shockwave-flash', 'application/futuresplash'); // Flash + $types_fmedia = array('video/flv', 'video/x-flv','flv-application/octet-stream'); // Flash Media Player + $types_quicktime = array('audio/3gpp', 'audio/3gpp2', 'audio/aac', 'audio/x-aac', 'audio/aiff', 'audio/x-aiff', 'audio/mid', 'audio/midi', 'audio/x-midi', 'audio/mp4', 'audio/m4a', 'audio/x-m4a', 'audio/wav', 'audio/x-wav', 'video/3gpp', 'video/3gpp2', 'video/m4v', 'video/x-m4v', 'video/mp4', 'video/mpeg', 'video/x-mpeg', 'video/quicktime', 'video/sd-video'); // QuickTime + $types_wmedia = array('application/asx', 'application/x-mplayer2', 'audio/x-ms-wma', 'audio/x-ms-wax', 'video/x-ms-asf-plugin', 'video/x-ms-asf', 'video/x-ms-wm', 'video/x-ms-wmv', 'video/x-ms-wvx'); // Windows Media + $types_mp3 = array('audio/mp3', 'audio/x-mp3', 'audio/mpeg', 'audio/x-mpeg'); // MP3 + + if ($this->get_type() !== null) + { + $type = strtolower($this->type); + } + else + { + $type = null; + } + + // If we encounter an unsupported mime-type, check the file extension and guess intelligently. + if (!in_array($type, array_merge($types_flash, $types_fmedia, $types_quicktime, $types_wmedia, $types_mp3))) + { + switch (strtolower($this->get_extension())) + { + // Audio mime-types + case 'aac': + case 'adts': + $type = 'audio/acc'; + break; + + case 'aif': + case 'aifc': + case 'aiff': + case 'cdda': + $type = 'audio/aiff'; + break; + + case 'bwf': + $type = 'audio/wav'; + break; + + case 'kar': + case 'mid': + case 'midi': + case 'smf': + $type = 'audio/midi'; + break; + + case 'm4a': + $type = 'audio/x-m4a'; + break; + + case 'mp3': + case 'swa': + $type = 'audio/mp3'; + break; + + case 'wav': + $type = 'audio/wav'; + break; + + case 'wax': + $type = 'audio/x-ms-wax'; + break; + + case 'wma': + $type = 'audio/x-ms-wma'; + break; + + // Video mime-types + case '3gp': + case '3gpp': + $type = 'video/3gpp'; + break; + + case '3g2': + case '3gp2': + $type = 'video/3gpp2'; + break; + + case 'asf': + $type = 'video/x-ms-asf'; + break; + + case 'flv': + $type = 'video/x-flv'; + break; + + case 'm1a': + case 'm1s': + case 'm1v': + case 'm15': + case 'm75': + case 'mp2': + case 'mpa': + case 'mpeg': + case 'mpg': + case 'mpm': + case 'mpv': + $type = 'video/mpeg'; + break; + + case 'm4v': + $type = 'video/x-m4v'; + break; + + case 'mov': + case 'qt': + $type = 'video/quicktime'; + break; + + case 'mp4': + case 'mpg4': + $type = 'video/mp4'; + break; + + case 'sdv': + $type = 'video/sd-video'; + break; + + case 'wm': + $type = 'video/x-ms-wm'; + break; + + case 'wmv': + $type = 'video/x-ms-wmv'; + break; + + case 'wvx': + $type = 'video/x-ms-wvx'; + break; + + // Flash mime-types + case 'spl': + $type = 'application/futuresplash'; + break; + + case 'swf': + $type = 'application/x-shockwave-flash'; + break; + } + } + + if ($find_handler) + { + if (in_array($type, $types_flash)) + { + return 'flash'; + } + elseif (in_array($type, $types_fmedia)) + { + return 'fmedia'; + } + elseif (in_array($type, $types_quicktime)) + { + return 'quicktime'; + } + elseif (in_array($type, $types_wmedia)) + { + return 'wmedia'; + } + elseif (in_array($type, $types_mp3)) + { + return 'mp3'; + } + else + { + return null; + } + } + else + { + return $type; + } + } +} + +class SimplePie_Caption +{ + var $type; + var $lang; + var $startTime; + var $endTime; + var $text; + + // Constructor, used to input the data + function SimplePie_Caption($type = null, $lang = null, $startTime = null, $endTime = null, $text = null) + { + $this->type = $type; + $this->lang = $lang; + $this->startTime = $startTime; + $this->endTime = $endTime; + $this->text = $text; + } + + function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + function get_endtime() + { + if ($this->endTime !== null) + { + return $this->endTime; + } + else + { + return null; + } + } + + function get_language() + { + if ($this->lang !== null) + { + return $this->lang; + } + else + { + return null; + } + } + + function get_starttime() + { + if ($this->startTime !== null) + { + return $this->startTime; + } + else + { + return null; + } + } + + function get_text() + { + if ($this->text !== null) + { + return $this->text; + } + else + { + return null; + } + } + + function get_type() + { + if ($this->type !== null) + { + return $this->type; + } + else + { + return null; + } + } +} + +class SimplePie_Credit +{ + var $role; + var $scheme; + var $name; + + // Constructor, used to input the data + function SimplePie_Credit($role = null, $scheme = null, $name = null) + { + $this->role = $role; + $this->scheme = $scheme; + $this->name = $name; + } + + function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + function get_role() + { + if ($this->role !== null) + { + return $this->role; + } + else + { + return null; + } + } + + function get_scheme() + { + if ($this->scheme !== null) + { + return $this->scheme; + } + else + { + return null; + } + } + + function get_name() + { + if ($this->name !== null) + { + return $this->name; + } + else + { + return null; + } + } +} + +class SimplePie_Copyright +{ + var $url; + var $label; + + // Constructor, used to input the data + function SimplePie_Copyright($url = null, $label = null) + { + $this->url = $url; + $this->label = $label; + } + + function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + function get_url() + { + if ($this->url !== null) + { + return $this->url; + } + else + { + return null; + } + } + + function get_attribution() + { + if ($this->label !== null) + { + return $this->label; + } + else + { + return null; + } + } +} + +class SimplePie_Rating +{ + var $scheme; + var $value; + + // Constructor, used to input the data + function SimplePie_Rating($scheme = null, $value = null) + { + $this->scheme = $scheme; + $this->value = $value; + } + + function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + function get_scheme() + { + if ($this->scheme !== null) + { + return $this->scheme; + } + else + { + return null; + } + } + + function get_value() + { + if ($this->value !== null) + { + return $this->value; + } + else + { + return null; + } + } +} + +class SimplePie_Restriction +{ + var $relationship; + var $type; + var $value; + + // Constructor, used to input the data + function SimplePie_Restriction($relationship = null, $type = null, $value = null) + { + $this->relationship = $relationship; + $this->type = $type; + $this->value = $value; + } + + function __toString() + { + // There is no $this->data here + return md5(serialize($this)); + } + + function get_relationship() + { + if ($this->relationship !== null) + { + return $this->relationship; + } + else + { + return null; + } + } + + function get_type() + { + if ($this->type !== null) + { + return $this->type; + } + else + { + return null; + } + } + + function get_value() + { + if ($this->value !== null) + { + return $this->value; + } + else + { + return null; + } + } +} + +/** + * @todo Move to properly supporting RFC2616 (HTTP/1.1) + */ +class SimplePie_File +{ + var $url; + var $useragent; + var $success = true; + var $headers = array(); + var $body; + var $status_code; + var $redirects = 0; + var $error; + var $method = SIMPLEPIE_FILE_SOURCE_NONE; + + function SimplePie_File($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false) + { + if (class_exists('idna_convert')) + { + $idn = new idna_convert; + $parsed = SimplePie_Misc::parse_url($url); + $url = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']); + } + $this->url = $url; + $this->useragent = $useragent; + if (preg_match('/^http(s)?:\/\//i', $url)) + { + if ($useragent === null) + { + $useragent = ini_get('user_agent'); + $this->useragent = $useragent; + } + if (!is_array($headers)) + { + $headers = array(); + } + if (!$force_fsockopen && function_exists('curl_exec')) + { + $this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_CURL; + $fp = curl_init(); + $headers2 = array(); + foreach ($headers as $key => $value) + { + $headers2[] = "$key: $value"; + } + if (version_compare(SimplePie_Misc::get_curl_version(), '7.10.5', '>=')) + { + curl_setopt($fp, CURLOPT_ENCODING, ''); + } + curl_setopt($fp, CURLOPT_URL, $url); + curl_setopt($fp, CURLOPT_HEADER, 1); + curl_setopt($fp, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($fp, CURLOPT_TIMEOUT, $timeout); + curl_setopt($fp, CURLOPT_CONNECTTIMEOUT, $timeout); + curl_setopt($fp, CURLOPT_REFERER, $url); + curl_setopt($fp, CURLOPT_USERAGENT, $useragent); + curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2); + if (!ini_get('open_basedir') && !ini_get('safe_mode') && version_compare(SimplePie_Misc::get_curl_version(), '7.15.2', '>=')) + { + curl_setopt($fp, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($fp, CURLOPT_MAXREDIRS, $redirects); + } + + $this->headers = curl_exec($fp); + if (curl_errno($fp) === 23 || curl_errno($fp) === 61) + { + curl_setopt($fp, CURLOPT_ENCODING, 'none'); + $this->headers = curl_exec($fp); + } + if (curl_errno($fp)) + { + $this->error = 'cURL error ' . curl_errno($fp) . ': ' . curl_error($fp); + $this->success = false; + } + else + { + $info = curl_getinfo($fp); + curl_close($fp); + $this->headers = explode("\r\n\r\n", $this->headers, $info['redirect_count'] + 1); + $this->headers = array_pop($this->headers); + $parser = new SimplePie_HTTP_Parser($this->headers); + if ($parser->parse()) + { + $this->headers = $parser->headers; + $this->body = $parser->body; + $this->status_code = $parser->status_code; + if ((in_array($this->status_code, array(300, 301, 302, 303, 307)) || $this->status_code > 307 && $this->status_code < 400) && isset($this->headers['location']) && $this->redirects < $redirects) + { + $this->redirects++; + $location = SimplePie_Misc::absolutize_url($this->headers['location'], $url); + return $this->SimplePie_File($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen); + } + } + } + } + else + { + $this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_FSOCKOPEN; + $url_parts = parse_url($url); + $socket_host = $url_parts['host']; + if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') + { + $socket_host = "ssl://$url_parts[host]"; + $url_parts['port'] = 443; + } + if (!isset($url_parts['port'])) + { + $url_parts['port'] = 80; + } + $fp = @fsockopen($socket_host, $url_parts['port'], $errno, $errstr, $timeout); + if (!$fp) + { + $this->error = 'fsockopen error: ' . $errstr; + $this->success = false; + } + else + { + stream_set_timeout($fp, $timeout); + if (isset($url_parts['path'])) + { + if (isset($url_parts['query'])) + { + $get = "$url_parts[path]?$url_parts[query]"; + } + else + { + $get = $url_parts['path']; + } + } + else + { + $get = '/'; + } + $out = "GET $get HTTP/1.0\r\n"; + $out .= "Host: $url_parts[host]\r\n"; + $out .= "User-Agent: $useragent\r\n"; + if (extension_loaded('zlib')) + { + $out .= "Accept-Encoding: x-gzip,gzip,deflate\r\n"; + } + + if (isset($url_parts['user']) && isset($url_parts['pass'])) + { + $out .= "Authorization: Basic " . base64_encode("$url_parts[user]:$url_parts[pass]") . "\r\n"; + } + foreach ($headers as $key => $value) + { + $out .= "$key: $value\r\n"; + } + $out .= "Connection: Close\r\n\r\n"; + fwrite($fp, $out); + + $info = stream_get_meta_data($fp); + + $this->headers = ''; + while (!$info['eof'] && !$info['timed_out']) + { + $this->headers .= fread($fp, 1160); + $info = stream_get_meta_data($fp); + } + if (!$info['timed_out']) + { + $parser = new SimplePie_HTTP_Parser($this->headers); + if ($parser->parse()) + { + $this->headers = $parser->headers; + $this->body = $parser->body; + $this->status_code = $parser->status_code; + if ((in_array($this->status_code, array(300, 301, 302, 303, 307)) || $this->status_code > 307 && $this->status_code < 400) && isset($this->headers['location']) && $this->redirects < $redirects) + { + $this->redirects++; + $location = SimplePie_Misc::absolutize_url($this->headers['location'], $url); + return $this->SimplePie_File($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen); + } + if (isset($this->headers['content-encoding'])) + { + // Hey, we act dumb elsewhere, so let's do that here too + switch (strtolower(trim($this->headers['content-encoding'], "\x09\x0A\x0D\x20"))) + { + case 'gzip': + case 'x-gzip': + $decoder = new SimplePie_gzdecode($this->body); + if (!$decoder->parse()) + { + $this->error = 'Unable to decode HTTP "gzip" stream'; + $this->success = false; + } + else + { + $this->body = $decoder->data; + } + break; + + case 'deflate': + if (($body = gzuncompress($this->body)) === false) + { + if (($body = gzinflate($this->body)) === false) + { + $this->error = 'Unable to decode HTTP "deflate" stream'; + $this->success = false; + } + } + $this->body = $body; + break; + + default: + $this->error = 'Unknown content coding'; + $this->success = false; + } + } + } + } + else + { + $this->error = 'fsocket timed out'; + $this->success = false; + } + fclose($fp); + } + } + } + else + { + $this->method = SIMPLEPIE_FILE_SOURCE_LOCAL | SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS; + if (!$this->body = file_get_contents($url)) + { + $this->error = 'file_get_contents could not read the file'; + $this->success = false; + } + } + } +} + +/** + * HTTP Response Parser + * + * @package SimplePie + */ +class SimplePie_HTTP_Parser +{ + /** + * HTTP Version + * + * @access public + * @var float + */ + var $http_version = 0.0; + + /** + * Status code + * + * @access public + * @var int + */ + var $status_code = 0; + + /** + * Reason phrase + * + * @access public + * @var string + */ + var $reason = ''; + + /** + * Key/value pairs of the headers + * + * @access public + * @var array + */ + var $headers = array(); + + /** + * Body of the response + * + * @access public + * @var string + */ + var $body = ''; + + /** + * Current state of the state machine + * + * @access private + * @var string + */ + var $state = 'http_version'; + + /** + * Input data + * + * @access private + * @var string + */ + var $data = ''; + + /** + * Input data length (to avoid calling strlen() everytime this is needed) + * + * @access private + * @var int + */ + var $data_length = 0; + + /** + * Current position of the pointer + * + * @var int + * @access private + */ + var $position = 0; + + /** + * Name of the hedaer currently being parsed + * + * @access private + * @var string + */ + var $name = ''; + + /** + * Value of the hedaer currently being parsed + * + * @access private + * @var string + */ + var $value = ''; + + /** + * Create an instance of the class with the input data + * + * @access public + * @param string $data Input data + */ + function SimplePie_HTTP_Parser($data) + { + $this->data = $data; + $this->data_length = strlen($this->data); + } + + /** + * Parse the input data + * + * @access public + * @return bool true on success, false on failure + */ + function parse() + { + while ($this->state && $this->state !== 'emit' && $this->has_data()) + { + $state = $this->state; + $this->$state(); + } + $this->data = ''; + if ($this->state === 'emit' || $this->state === 'body') + { + return true; + } + else + { + $this->http_version = ''; + $this->status_code = ''; + $this->reason = ''; + $this->headers = array(); + $this->body = ''; + return false; + } + } + + /** + * Check whether there is data beyond the pointer + * + * @access private + * @return bool true if there is further data, false if not + */ + function has_data() + { + return (bool) ($this->position < $this->data_length); + } + + /** + * See if the next character is LWS + * + * @access private + * @return bool true if the next character is LWS, false if not + */ + function is_linear_whitespace() + { + return (bool) ($this->data[$this->position] === "\x09" + || $this->data[$this->position] === "\x20" + || ($this->data[$this->position] === "\x0A" + && isset($this->data[$this->position + 1]) + && ($this->data[$this->position + 1] === "\x09" || $this->data[$this->position + 1] === "\x20"))); + } + + /** + * Parse the HTTP version + * + * @access private + */ + function http_version() + { + if (strpos($this->data, "\x0A") !== false && strtoupper(substr($this->data, 0, 5)) === 'HTTP/') + { + $len = strspn($this->data, '0123456789.', 5); + $this->http_version = substr($this->data, 5, $len); + $this->position += 5 + $len; + if (substr_count($this->http_version, '.') <= 1) + { + $this->http_version = (float) $this->http_version; + $this->position += strspn($this->data, "\x09\x20", $this->position); + $this->state = 'status'; + } + else + { + $this->state = false; + } + } + else + { + $this->state = false; + } + } + + /** + * Parse the status code + * + * @access private + */ + function status() + { + if ($len = strspn($this->data, '0123456789', $this->position)) + { + $this->status_code = (int) substr($this->data, $this->position, $len); + $this->position += $len; + $this->state = 'reason'; + } + else + { + $this->state = false; + } + } + + /** + * Parse the reason phrase + * + * @access private + */ + function reason() + { + $len = strcspn($this->data, "\x0A", $this->position); + $this->reason = trim(substr($this->data, $this->position, $len), "\x09\x0D\x20"); + $this->position += $len + 1; + $this->state = 'new_line'; + } + + /** + * Deal with a new line, shifting data around as needed + * + * @access private + */ + function new_line() + { + $this->value = trim($this->value, "\x0D\x20"); + if ($this->name !== '' && $this->value !== '') + { + $this->name = strtolower($this->name); + if (isset($this->headers[$this->name])) + { + $this->headers[$this->name] .= ', ' . $this->value; + } + else + { + $this->headers[$this->name] = $this->value; + } + } + $this->name = ''; + $this->value = ''; + if (substr($this->data[$this->position], 0, 2) === "\x0D\x0A") + { + $this->position += 2; + $this->state = 'body'; + } + elseif ($this->data[$this->position] === "\x0A") + { + $this->position++; + $this->state = 'body'; + } + else + { + $this->state = 'name'; + } + } + + /** + * Parse a header name + * + * @access private + */ + function name() + { + $len = strcspn($this->data, "\x0A:", $this->position); + if (isset($this->data[$this->position + $len])) + { + if ($this->data[$this->position + $len] === "\x0A") + { + $this->position += $len; + $this->state = 'new_line'; + } + else + { + $this->name = substr($this->data, $this->position, $len); + $this->position += $len + 1; + $this->state = 'value'; + } + } + else + { + $this->state = false; + } + } + + /** + * Parse LWS, replacing consecutive LWS characters with a single space + * + * @access private + */ + function linear_whitespace() + { + do + { + if (substr($this->data, $this->position, 2) === "\x0D\x0A") + { + $this->position += 2; + } + elseif ($this->data[$this->position] === "\x0A") + { + $this->position++; + } + $this->position += strspn($this->data, "\x09\x20", $this->position); + } while ($this->has_data() && $this->is_linear_whitespace()); + $this->value .= "\x20"; + } + + /** + * See what state to move to while within non-quoted header values + * + * @access private + */ + function value() + { + if ($this->is_linear_whitespace()) + { + $this->linear_whitespace(); + } + else + { + switch ($this->data[$this->position]) + { + case '"': + $this->position++; + $this->state = 'quote'; + break; + + case "\x0A": + $this->position++; + $this->state = 'new_line'; + break; + + default: + $this->state = 'value_char'; + break; + } + } + } + + /** + * Parse a header value while outside quotes + * + * @access private + */ + function value_char() + { + $len = strcspn($this->data, "\x09\x20\x0A\"", $this->position); + $this->value .= substr($this->data, $this->position, $len); + $this->position += $len; + $this->state = 'value'; + } + + /** + * See what state to move to while within quoted header values + * + * @access private + */ + function quote() + { + if ($this->is_linear_whitespace()) + { + $this->linear_whitespace(); + } + else + { + switch ($this->data[$this->position]) + { + case '"': + $this->position++; + $this->state = 'value'; + break; + + case "\x0A": + $this->position++; + $this->state = 'new_line'; + break; + + case '\\': + $this->position++; + $this->state = 'quote_escaped'; + break; + + default: + $this->state = 'quote_char'; + break; + } + } + } + + /** + * Parse a header value while within quotes + * + * @access private + */ + function quote_char() + { + $len = strcspn($this->data, "\x09\x20\x0A\"\\", $this->position); + $this->value .= substr($this->data, $this->position, $len); + $this->position += $len; + $this->state = 'value'; + } + + /** + * Parse an escaped character within quotes + * + * @access private + */ + function quote_escaped() + { + $this->value .= $this->data[$this->position]; + $this->position++; + $this->state = 'quote'; + } + + /** + * Parse the body + * + * @access private + */ + function body() + { + $this->body = substr($this->data, $this->position); + $this->state = 'emit'; + } +} + +/** + * gzdecode + * + * @package SimplePie + */ +class SimplePie_gzdecode +{ + /** + * Compressed data + * + * @access private + * @see gzdecode::$data + */ + var $compressed_data; + + /** + * Size of compressed data + * + * @access private + */ + var $compressed_size; + + /** + * Minimum size of a valid gzip string + * + * @access private + */ + var $min_compressed_size = 18; + + /** + * Current position of pointer + * + * @access private + */ + var $position = 0; + + /** + * Flags (FLG) + * + * @access private + */ + var $flags; + + /** + * Uncompressed data + * + * @access public + * @see gzdecode::$compressed_data + */ + var $data; + + /** + * Modified time + * + * @access public + */ + var $MTIME; + + /** + * Extra Flags + * + * @access public + */ + var $XFL; + + /** + * Operating System + * + * @access public + */ + var $OS; + + /** + * Subfield ID 1 + * + * @access public + * @see gzdecode::$extra_field + * @see gzdecode::$SI2 + */ + var $SI1; + + /** + * Subfield ID 2 + * + * @access public + * @see gzdecode::$extra_field + * @see gzdecode::$SI1 + */ + var $SI2; + + /** + * Extra field content + * + * @access public + * @see gzdecode::$SI1 + * @see gzdecode::$SI2 + */ + var $extra_field; + + /** + * Original filename + * + * @access public + */ + var $filename; + + /** + * Human readable comment + * + * @access public + */ + var $comment; + + /** + * Don't allow anything to be set + * + * @access public + */ + function __set($name, $value) + { + trigger_error("Cannot write property $name", E_USER_ERROR); + } + + /** + * Set the compressed string and related properties + * + * @access public + */ + function SimplePie_gzdecode($data) + { + $this->compressed_data = $data; + $this->compressed_size = strlen($data); + } + + /** + * Decode the GZIP stream + * + * @access public + */ + function parse() + { + if ($this->compressed_size >= $this->min_compressed_size) + { + // Check ID1, ID2, and CM + if (substr($this->compressed_data, 0, 3) !== "\x1F\x8B\x08") + { + return false; + } + + // Get the FLG (FLaGs) + $this->flags = ord($this->compressed_data[3]); + + // FLG bits above (1 << 4) are reserved + if ($this->flags > 0x1F) + { + return false; + } + + // Advance the pointer after the above + $this->position += 4; + + // MTIME + $mtime = substr($this->compressed_data, $this->position, 4); + // Reverse the string if we're on a big-endian arch because l is the only signed long and is machine endianness + if (current(unpack('S', "\x00\x01")) === 1) + { + $mtime = strrev($mtime); + } + $this->MTIME = current(unpack('l', $mtime)); + $this->position += 4; + + // Get the XFL (eXtra FLags) + $this->XFL = ord($this->compressed_data[$this->position++]); + + // Get the OS (Operating System) + $this->OS = ord($this->compressed_data[$this->position++]); + + // Parse the FEXTRA + if ($this->flags & 4) + { + // Read subfield IDs + $this->SI1 = $this->compressed_data[$this->position++]; + $this->SI2 = $this->compressed_data[$this->position++]; + + // SI2 set to zero is reserved for future use + if ($this->SI2 === "\x00") + { + return false; + } + + // Get the length of the extra field + $len = current(unpack('v', substr($this->compressed_data, $this->position, 2))); + $this->position += 2; + + // Check the length of the string is still valid + $this->min_compressed_size += $len + 4; + if ($this->compressed_size >= $this->min_compressed_size) + { + // Set the extra field to the given data + $this->extra_field = substr($this->compressed_data, $this->position, $len); + $this->position += $len; + } + else + { + return false; + } + } + + // Parse the FNAME + if ($this->flags & 8) + { + // Get the length of the filename + $len = strcspn($this->compressed_data, "\x00", $this->position); + + // Check the length of the string is still valid + $this->min_compressed_size += $len + 1; + if ($this->compressed_size >= $this->min_compressed_size) + { + // Set the original filename to the given string + $this->filename = substr($this->compressed_data, $this->position, $len); + $this->position += $len + 1; + } + else + { + return false; + } + } + + // Parse the FCOMMENT + if ($this->flags & 16) + { + // Get the length of the comment + $len = strcspn($this->compressed_data, "\x00", $this->position); + + // Check the length of the string is still valid + $this->min_compressed_size += $len + 1; + if ($this->compressed_size >= $this->min_compressed_size) + { + // Set the original comment to the given string + $this->comment = substr($this->compressed_data, $this->position, $len); + $this->position += $len + 1; + } + else + { + return false; + } + } + + // Parse the FHCRC + if ($this->flags & 2) + { + // Check the length of the string is still valid + $this->min_compressed_size += $len + 2; + if ($this->compressed_size >= $this->min_compressed_size) + { + // Read the CRC + $crc = current(unpack('v', substr($this->compressed_data, $this->position, 2))); + + // Check the CRC matches + if ((crc32(substr($this->compressed_data, 0, $this->position)) & 0xFFFF) === $crc) + { + $this->position += 2; + } + else + { + return false; + } + } + else + { + return false; + } + } + + // Decompress the actual data + if (($this->data = gzinflate(substr($this->compressed_data, $this->position, -8))) === false) + { + return false; + } + else + { + $this->position = $this->compressed_size - 8; + } + + // Check CRC of data + $crc = current(unpack('V', substr($this->compressed_data, $this->position, 4))); + $this->position += 4; + /*if (extension_loaded('hash') && sprintf('%u', current(unpack('V', hash('crc32b', $this->data)))) !== sprintf('%u', $crc)) + { + return false; + }*/ + + // Check ISIZE of data + $isize = current(unpack('V', substr($this->compressed_data, $this->position, 4))); + $this->position += 4; + if (sprintf('%u', strlen($this->data) & 0xFFFFFFFF) !== sprintf('%u', $isize)) + { + return false; + } + + // Wow, against all odds, we've actually got a valid gzip string + return true; + } + else + { + return false; + } + } +} + +class SimplePie_Cache +{ + /** + * Don't call the constructor. Please. + * + * @access private + */ + function SimplePie_Cache() + { + trigger_error('Please call SimplePie_Cache::create() instead of the constructor', E_USER_ERROR); + } + + /** + * Create a new SimplePie_Cache object + * + * @static + * @access public + */ + function create($location, $filename, $extension) + { + $location_iri = new SimplePie_IRI($location); + switch ($location_iri->get_scheme()) + { + case 'mysql': + if (extension_loaded('mysql')) + { + return new SimplePie_Cache_MySQL($location_iri, $filename, $extension); + } + break; + + default: + return new SimplePie_Cache_File($location, $filename, $extension); + } + } +} + +class SimplePie_Cache_File +{ + var $location; + var $filename; + var $extension; + var $name; + + function SimplePie_Cache_File($location, $filename, $extension) + { + $this->location = $location; + $this->filename = $filename; + $this->extension = $extension; + $this->name = "$this->location/$this->filename.$this->extension"; + } + + function save($data) + { + if (file_exists($this->name) && is_writeable($this->name) || file_exists($this->location) && is_writeable($this->location)) + { + if (is_a($data, 'SimplePie')) + { + $data = $data->data; + } + + $data = serialize($data); + + if (function_exists('file_put_contents')) + { + return (bool) file_put_contents($this->name, $data); + } + else + { + $fp = fopen($this->name, 'wb'); + if ($fp) + { + fwrite($fp, $data); + fclose($fp); + return true; + } + } + } + return false; + } + + function load() + { + if (file_exists($this->name) && is_readable($this->name)) + { + return unserialize(file_get_contents($this->name)); + } + return false; + } + + function mtime() + { + if (file_exists($this->name)) + { + return filemtime($this->name); + } + return false; + } + + function touch() + { + if (file_exists($this->name)) + { + return touch($this->name); + } + return false; + } + + function unlink() + { + if (file_exists($this->name)) + { + return unlink($this->name); + } + return false; + } +} + +class SimplePie_Cache_DB +{ + function prepare_simplepie_object_for_cache($data) + { + $items = $data->get_items(); + $items_by_id = array(); + + if (!empty($items)) + { + foreach ($items as $item) + { + $items_by_id[$item->get_id()] = $item; + } + + if (count($items_by_id) !== count($items)) + { + $items_by_id = array(); + foreach ($items as $item) + { + $items_by_id[$item->get_id(true)] = $item; + } + } + + if (isset($data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0])) + { + $channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]; + } + elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0])) + { + $channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]; + } + elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0])) + { + $channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]; + } + elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['channel'][0])) + { + $channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['channel'][0]; + } + else + { + $channel = null; + } + + if ($channel !== null) + { + if (isset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry'])) + { + unset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry']); + } + if (isset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['entry'])) + { + unset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['entry']); + } + if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item'])) + { + unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item']); + } + if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item'])) + { + unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item']); + } + if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_20]['item'])) + { + unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_20]['item']); + } + } + if (isset($data->data['items'])) + { + unset($data->data['items']); + } + if (isset($data->data['ordered_items'])) + { + unset($data->data['ordered_items']); + } + } + return array(serialize($data->data), $items_by_id); + } +} + +class SimplePie_Cache_MySQL extends SimplePie_Cache_DB +{ + var $mysql; + var $options; + var $id; + + function SimplePie_Cache_MySQL($mysql_location, $name, $extension) + { + $host = $mysql_location->get_host(); + if (SimplePie_Misc::stripos($host, 'unix(') === 0 && substr($host, -1) === ')') + { + $server = ':' . substr($host, 5, -1); + } + else + { + $server = $host; + if ($mysql_location->get_port() !== null) + { + $server .= ':' . $mysql_location->get_port(); + } + } + + if (strpos($mysql_location->get_userinfo(), ':') !== false) + { + list($username, $password) = explode(':', $mysql_location->get_userinfo(), 2); + } + else + { + $username = $mysql_location->get_userinfo(); + $password = null; + } + + if ($this->mysql = mysql_connect($server, $username, $password)) + { + $this->id = $name . $extension; + $this->options = SimplePie_Misc::parse_str($mysql_location->get_query()); + if (!isset($this->options['prefix'][0])) + { + $this->options['prefix'][0] = ''; + } + + if (mysql_select_db(ltrim($mysql_location->get_path(), '/')) + && mysql_query('SET NAMES utf8') + && ($query = mysql_unbuffered_query('SHOW TABLES'))) + { + $db = array(); + while ($row = mysql_fetch_row($query)) + { + $db[] = $row[0]; + } + + if (!in_array($this->options['prefix'][0] . 'cache_data', $db)) + { + if (!mysql_query('CREATE TABLE `' . $this->options['prefix'][0] . 'cache_data` (`id` TEXT CHARACTER SET utf8 NOT NULL, `items` SMALLINT NOT NULL DEFAULT 0, `data` BLOB NOT NULL, `mtime` INT UNSIGNED NOT NULL, UNIQUE (`id`(125)))')) + { + $this->mysql = null; + } + } + + if (!in_array($this->options['prefix'][0] . 'items', $db)) + { + if (!mysql_query('CREATE TABLE `' . $this->options['prefix'][0] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` TEXT CHARACTER SET utf8 NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))')) + { + $this->mysql = null; + } + } + } + else + { + $this->mysql = null; + } + } + } + + function save($data) + { + if ($this->mysql) + { + $feed_id = "'" . mysql_real_escape_string($this->id) . "'"; + + if (is_a($data, 'SimplePie')) + { + if (SIMPLEPIE_PHP5) + { + // This keyword needs to defy coding standards for PHP4 compatibility + $data = clone($data); + } + + $prepared = $this->prepare_simplepie_object_for_cache($data); + + if ($query = mysql_query('SELECT `id` FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = ' . $feed_id, $this->mysql)) + { + if (mysql_num_rows($query)) + { + $items = count($prepared[1]); + if ($items) + { + $sql = 'UPDATE `' . $this->options['prefix'][0] . 'cache_data` SET `items` = ' . $items . ', `data` = \'' . mysql_real_escape_string($prepared[0]) . '\', `mtime` = ' . time() . ' WHERE `id` = ' . $feed_id; + } + else + { + $sql = 'UPDATE `' . $this->options['prefix'][0] . 'cache_data` SET `data` = \'' . mysql_real_escape_string($prepared[0]) . '\', `mtime` = ' . time() . ' WHERE `id` = ' . $feed_id; + } + + if (!mysql_query($sql, $this->mysql)) + { + return false; + } + } + elseif (!mysql_query('INSERT INTO `' . $this->options['prefix'][0] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(' . $feed_id . ', ' . count($prepared[1]) . ', \'' . mysql_real_escape_string($prepared[0]) . '\', ' . time() . ')', $this->mysql)) + { + return false; + } + + $ids = array_keys($prepared[1]); + if (!empty($ids)) + { + foreach ($ids as $id) + { + $database_ids[] = mysql_real_escape_string($id); + } + + if ($query = mysql_unbuffered_query('SELECT `id` FROM `' . $this->options['prefix'][0] . 'items` WHERE `id` = \'' . implode('\' OR `id` = \'', $database_ids) . '\' AND `feed_id` = ' . $feed_id, $this->mysql)) + { + $existing_ids = array(); + while ($row = mysql_fetch_row($query)) + { + $existing_ids[] = $row[0]; + } + + $new_ids = array_diff($ids, $existing_ids); + + foreach ($new_ids as $new_id) + { + if (!($date = $prepared[1][$new_id]->get_date('U'))) + { + $date = time(); + } + + if (!mysql_query('INSERT INTO `' . $this->options['prefix'][0] . 'items` (`feed_id`, `id`, `data`, `posted`) VALUES(' . $feed_id . ', \'' . mysql_real_escape_string($new_id) . '\', \'' . mysql_real_escape_string(serialize($prepared[1][$new_id]->data)) . '\', ' . $date . ')', $this->mysql)) + { + return false; + } + } + return true; + } + } + else + { + return true; + } + } + } + elseif ($query = mysql_query('SELECT `id` FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = ' . $feed_id, $this->mysql)) + { + if (mysql_num_rows($query)) + { + if (mysql_query('UPDATE `' . $this->options['prefix'][0] . 'cache_data` SET `items` = 0, `data` = \'' . mysql_real_escape_string(serialize($data)) . '\', `mtime` = ' . time() . ' WHERE `id` = ' . $feed_id, $this->mysql)) + { + return true; + } + } + elseif (mysql_query('INSERT INTO `' . $this->options['prefix'][0] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(\'' . mysql_real_escape_string($this->id) . '\', 0, \'' . mysql_real_escape_string(serialize($data)) . '\', ' . time() . ')', $this->mysql)) + { + return true; + } + } + } + return false; + } + + function load() + { + if ($this->mysql && ($query = mysql_query('SELECT `items`, `data` FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)) && ($row = mysql_fetch_row($query))) + { + $data = unserialize($row[1]); + + if (isset($this->options['items'][0])) + { + $items = (int) $this->options['items'][0]; + } + else + { + $items = (int) $row[0]; + } + + if ($items !== 0) + { + if (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0])) + { + $feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]; + } + elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0])) + { + $feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]; + } + elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0])) + { + $feed =& $data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]; + } + elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0])) + { + $feed =& $data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]; + } + else + { + $feed = null; + } + + if ($feed !== null) + { + $sql = 'SELECT `data` FROM `' . $this->options['prefix'][0] . 'items` WHERE `feed_id` = \'' . mysql_real_escape_string($this->id) . '\' ORDER BY `posted` DESC'; + if ($items > 0) + { + $sql .= ' LIMIT ' . $items; + } + + if ($query = mysql_unbuffered_query($sql, $this->mysql)) + { + while ($row = mysql_fetch_row($query)) + { + $feed['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry'][] = unserialize($row[0]); + } + } + else + { + return false; + } + } + } + return $data; + } + return false; + } + + function mtime() + { + if ($this->mysql && ($query = mysql_query('SELECT `mtime` FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)) && ($row = mysql_fetch_row($query))) + { + return $row[0]; + } + else + { + return false; + } + } + + function touch() + { + if ($this->mysql && ($query = mysql_query('UPDATE `' . $this->options['prefix'][0] . 'cache_data` SET `mtime` = ' . time() . ' WHERE `id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)) && mysql_affected_rows($this->mysql)) + { + return true; + } + else + { + return false; + } + } + + function unlink() + { + if ($this->mysql && ($query = mysql_query('DELETE FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)) && ($query2 = mysql_query('DELETE FROM `' . $this->options['prefix'][0] . 'items` WHERE `feed_id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql))) + { + return true; + } + else + { + return false; + } + } +} + +class SimplePie_Misc +{ + function time_hms($seconds) + { + $time = ''; + + $hours = floor($seconds / 3600); + $remainder = $seconds % 3600; + if ($hours > 0) + { + $time .= $hours.':'; + } + + $minutes = floor($remainder / 60); + $seconds = $remainder % 60; + if ($minutes < 10 && $hours > 0) + { + $minutes = '0' . $minutes; + } + if ($seconds < 10) + { + $seconds = '0' . $seconds; + } + + $time .= $minutes.':'; + $time .= $seconds; + + return $time; + } + + function absolutize_url($relative, $base) + { + $iri = SimplePie_IRI::absolutize(new SimplePie_IRI($base), $relative); + return $iri->get_iri(); + } + + function remove_dot_segments($input) + { + $output = ''; + while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') + { + // A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise, + if (strpos($input, '../') === 0) + { + $input = substr($input, 3); + } + elseif (strpos($input, './') === 0) + { + $input = substr($input, 2); + } + // B: if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise, + elseif (strpos($input, '/./') === 0) + { + $input = substr_replace($input, '/', 0, 3); + } + elseif ($input === '/.') + { + $input = '/'; + } + // C: if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise, + elseif (strpos($input, '/../') === 0) + { + $input = substr_replace($input, '/', 0, 4); + $output = substr_replace($output, '', strrpos($output, '/')); + } + elseif ($input === '/..') + { + $input = '/'; + $output = substr_replace($output, '', strrpos($output, '/')); + } + // D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise, + elseif ($input === '.' || $input === '..') + { + $input = ''; + } + // E: move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer + elseif (($pos = strpos($input, '/', 1)) !== false) + { + $output .= substr($input, 0, $pos); + $input = substr_replace($input, '', 0, $pos); + } + else + { + $output .= $input; + $input = ''; + } + } + return $output . $input; + } + + function get_element($realname, $string) + { + $return = array(); + $name = preg_quote($realname, '/'); + if (preg_match_all("/<($name)" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . "(>(.*)<\/$name>|(\/)?>)/siU", $string, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) + { + for ($i = 0, $total_matches = count($matches); $i < $total_matches; $i++) + { + $return[$i]['tag'] = $realname; + $return[$i]['full'] = $matches[$i][0][0]; + $return[$i]['offset'] = $matches[$i][0][1]; + if (strlen($matches[$i][3][0]) <= 2) + { + $return[$i]['self_closing'] = true; + } + else + { + $return[$i]['self_closing'] = false; + $return[$i]['content'] = $matches[$i][4][0]; + } + $return[$i]['attribs'] = array(); + if (isset($matches[$i][2][0]) && preg_match_all('/[\x09\x0A\x0B\x0C\x0D\x20]+([^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*)(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"([^"]*)"|\'([^\']*)\'|([^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?/', ' ' . $matches[$i][2][0] . ' ', $attribs, PREG_SET_ORDER)) + { + for ($j = 0, $total_attribs = count($attribs); $j < $total_attribs; $j++) + { + if (count($attribs[$j]) === 2) + { + $attribs[$j][2] = $attribs[$j][1]; + } + $return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8'); + } + } + } + } + return $return; + } + + function element_implode($element) + { + $full = "<$element[tag]"; + foreach ($element['attribs'] as $key => $value) + { + $key = strtolower($key); + $full .= " $key=\"" . htmlspecialchars($value['data']) . '"'; + } + if ($element['self_closing']) + { + $full .= ' />'; + } + else + { + $full .= ">$element[content]"; + } + return $full; + } + + function error($message, $level, $file, $line) + { + if ((ini_get('error_reporting') & $level) > 0) + { + switch ($level) + { + case E_USER_ERROR: + $note = 'PHP Error'; + break; + case E_USER_WARNING: + $note = 'PHP Warning'; + break; + case E_USER_NOTICE: + $note = 'PHP Notice'; + break; + default: + $note = 'Unknown Error'; + break; + } + + $log_error = true; + if (!function_exists('error_log')) + { + $log_error = false; + } + + $log_file = @ini_get('error_log'); + if (!empty($log_file) && ('syslog' != $log_file) && !@is_writable($log_file)) + { + $log_error = false; + } + + if ($log_error) + { + @error_log("$note: $message in $file on line $line", 0); + } + } + + return $message; + } + + /** + * If a file has been cached, retrieve and display it. + * + * This is most useful for caching images (get_favicon(), etc.), + * however it works for all cached files. This WILL NOT display ANY + * file/image/page/whatever, but rather only display what has already + * been cached by SimplePie. + * + * @access public + * @see SimplePie::get_favicon() + * @param str $identifier_url URL that is used to identify the content. + * This may or may not be the actual URL of the live content. + * @param str $cache_location Location of SimplePie's cache. Defaults + * to './cache'. + * @param str $cache_extension The file extension that the file was + * cached with. Defaults to 'spc'. + * @param str $cache_class Name of the cache-handling class being used + * in SimplePie. Defaults to 'SimplePie_Cache', and should be left + * as-is unless you've overloaded the class. + * @param str $cache_name_function Obsolete. Exists for backwards + * compatibility reasons only. + */ + function display_cached_file($identifier_url, $cache_location = './cache', $cache_extension = 'spc', $cache_class = 'SimplePie_Cache', $cache_name_function = 'md5') + { + $cache = call_user_func(array($cache_class, 'create'), $cache_location, $identifier_url, $cache_extension); + + if ($file = $cache->load()) + { + if (isset($file['headers']['content-type'])) + { + header('Content-type:' . $file['headers']['content-type']); + } + else + { + header('Content-type: application/octet-stream'); + } + header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 604800) . ' GMT'); // 7 days + echo $file['body']; + exit; + } + + die('Cached file for ' . $identifier_url . ' cannot be found.'); + } + + function fix_protocol($url, $http = 1) + { + $url = SimplePie_Misc::normalize_url($url); + $parsed = SimplePie_Misc::parse_url($url); + if ($parsed['scheme'] !== '' && $parsed['scheme'] !== 'http' && $parsed['scheme'] !== 'https') + { + return SimplePie_Misc::fix_protocol(SimplePie_Misc::compress_parse_url('http', $parsed['authority'], $parsed['path'], $parsed['query'], $parsed['fragment']), $http); + } + + if ($parsed['scheme'] === '' && $parsed['authority'] === '' && !file_exists($url)) + { + return SimplePie_Misc::fix_protocol(SimplePie_Misc::compress_parse_url('http', $parsed['path'], '', $parsed['query'], $parsed['fragment']), $http); + } + + if ($http === 2 && $parsed['scheme'] !== '') + { + return "feed:$url"; + } + elseif ($http === 3 && strtolower($parsed['scheme']) === 'http') + { + return substr_replace($url, 'podcast', 0, 4); + } + elseif ($http === 4 && strtolower($parsed['scheme']) === 'http') + { + return substr_replace($url, 'itpc', 0, 4); + } + else + { + return $url; + } + } + + function parse_url($url) + { + $iri = new SimplePie_IRI($url); + return array( + 'scheme' => (string) $iri->get_scheme(), + 'authority' => (string) $iri->get_authority(), + 'path' => (string) $iri->get_path(), + 'query' => (string) $iri->get_query(), + 'fragment' => (string) $iri->get_fragment() + ); + } + + function compress_parse_url($scheme = '', $authority = '', $path = '', $query = '', $fragment = '') + { + $iri = new SimplePie_IRI(''); + $iri->set_scheme($scheme); + $iri->set_authority($authority); + $iri->set_path($path); + $iri->set_query($query); + $iri->set_fragment($fragment); + return $iri->get_iri(); + } + + function normalize_url($url) + { + $iri = new SimplePie_IRI($url); + return $iri->get_iri(); + } + + function percent_encoding_normalization($match) + { + $integer = hexdec($match[1]); + if ($integer >= 0x41 && $integer <= 0x5A || $integer >= 0x61 && $integer <= 0x7A || $integer >= 0x30 && $integer <= 0x39 || $integer === 0x2D || $integer === 0x2E || $integer === 0x5F || $integer === 0x7E) + { + return chr($integer); + } + else + { + return strtoupper($match[0]); + } + } + + /** + * Remove bad UTF-8 bytes + * + * PCRE Pattern to locate bad bytes in a UTF-8 string comes from W3C + * FAQ: Multilingual Forms (modified to include full ASCII range) + * + * @author Geoffrey Sneddon + * @see http://www.w3.org/International/questions/qa-forms-utf-8 + * @param string $str String to remove bad UTF-8 bytes from + * @return string UTF-8 string + */ + function utf8_bad_replace($str) + { + if (function_exists('iconv') && ($return = @iconv('UTF-8', 'UTF-8//IGNORE', $str))) + { + return $return; + } + elseif (function_exists('mb_convert_encoding') && ($return = @mb_convert_encoding($str, 'UTF-8', 'UTF-8'))) + { + return $return; + } + elseif (preg_match_all('/(?:[\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})+/', $str, $matches)) + { + return implode("\xEF\xBF\xBD", $matches[0]); + } + elseif ($str !== '') + { + return "\xEF\xBF\xBD"; + } + else + { + return ''; + } + } + + /** + * Converts a Windows-1252 encoded string to a UTF-8 encoded string + * + * @static + * @access public + * @param string $string Windows-1252 encoded string + * @return string UTF-8 encoded string + */ + function windows_1252_to_utf8($string) + { + static $convert_table = array("\x80" => "\xE2\x82\xAC", "\x81" => "\xEF\xBF\xBD", "\x82" => "\xE2\x80\x9A", "\x83" => "\xC6\x92", "\x84" => "\xE2\x80\x9E", "\x85" => "\xE2\x80\xA6", "\x86" => "\xE2\x80\xA0", "\x87" => "\xE2\x80\xA1", "\x88" => "\xCB\x86", "\x89" => "\xE2\x80\xB0", "\x8A" => "\xC5\xA0", "\x8B" => "\xE2\x80\xB9", "\x8C" => "\xC5\x92", "\x8D" => "\xEF\xBF\xBD", "\x8E" => "\xC5\xBD", "\x8F" => "\xEF\xBF\xBD", "\x90" => "\xEF\xBF\xBD", "\x91" => "\xE2\x80\x98", "\x92" => "\xE2\x80\x99", "\x93" => "\xE2\x80\x9C", "\x94" => "\xE2\x80\x9D", "\x95" => "\xE2\x80\xA2", "\x96" => "\xE2\x80\x93", "\x97" => "\xE2\x80\x94", "\x98" => "\xCB\x9C", "\x99" => "\xE2\x84\xA2", "\x9A" => "\xC5\xA1", "\x9B" => "\xE2\x80\xBA", "\x9C" => "\xC5\x93", "\x9D" => "\xEF\xBF\xBD", "\x9E" => "\xC5\xBE", "\x9F" => "\xC5\xB8", "\xA0" => "\xC2\xA0", "\xA1" => "\xC2\xA1", "\xA2" => "\xC2\xA2", "\xA3" => "\xC2\xA3", "\xA4" => "\xC2\xA4", "\xA5" => "\xC2\xA5", "\xA6" => "\xC2\xA6", "\xA7" => "\xC2\xA7", "\xA8" => "\xC2\xA8", "\xA9" => "\xC2\xA9", "\xAA" => "\xC2\xAA", "\xAB" => "\xC2\xAB", "\xAC" => "\xC2\xAC", "\xAD" => "\xC2\xAD", "\xAE" => "\xC2\xAE", "\xAF" => "\xC2\xAF", "\xB0" => "\xC2\xB0", "\xB1" => "\xC2\xB1", "\xB2" => "\xC2\xB2", "\xB3" => "\xC2\xB3", "\xB4" => "\xC2\xB4", "\xB5" => "\xC2\xB5", "\xB6" => "\xC2\xB6", "\xB7" => "\xC2\xB7", "\xB8" => "\xC2\xB8", "\xB9" => "\xC2\xB9", "\xBA" => "\xC2\xBA", "\xBB" => "\xC2\xBB", "\xBC" => "\xC2\xBC", "\xBD" => "\xC2\xBD", "\xBE" => "\xC2\xBE", "\xBF" => "\xC2\xBF", "\xC0" => "\xC3\x80", "\xC1" => "\xC3\x81", "\xC2" => "\xC3\x82", "\xC3" => "\xC3\x83", "\xC4" => "\xC3\x84", "\xC5" => "\xC3\x85", "\xC6" => "\xC3\x86", "\xC7" => "\xC3\x87", "\xC8" => "\xC3\x88", "\xC9" => "\xC3\x89", "\xCA" => "\xC3\x8A", "\xCB" => "\xC3\x8B", "\xCC" => "\xC3\x8C", "\xCD" => "\xC3\x8D", "\xCE" => "\xC3\x8E", "\xCF" => "\xC3\x8F", "\xD0" => "\xC3\x90", "\xD1" => "\xC3\x91", "\xD2" => "\xC3\x92", "\xD3" => "\xC3\x93", "\xD4" => "\xC3\x94", "\xD5" => "\xC3\x95", "\xD6" => "\xC3\x96", "\xD7" => "\xC3\x97", "\xD8" => "\xC3\x98", "\xD9" => "\xC3\x99", "\xDA" => "\xC3\x9A", "\xDB" => "\xC3\x9B", "\xDC" => "\xC3\x9C", "\xDD" => "\xC3\x9D", "\xDE" => "\xC3\x9E", "\xDF" => "\xC3\x9F", "\xE0" => "\xC3\xA0", "\xE1" => "\xC3\xA1", "\xE2" => "\xC3\xA2", "\xE3" => "\xC3\xA3", "\xE4" => "\xC3\xA4", "\xE5" => "\xC3\xA5", "\xE6" => "\xC3\xA6", "\xE7" => "\xC3\xA7", "\xE8" => "\xC3\xA8", "\xE9" => "\xC3\xA9", "\xEA" => "\xC3\xAA", "\xEB" => "\xC3\xAB", "\xEC" => "\xC3\xAC", "\xED" => "\xC3\xAD", "\xEE" => "\xC3\xAE", "\xEF" => "\xC3\xAF", "\xF0" => "\xC3\xB0", "\xF1" => "\xC3\xB1", "\xF2" => "\xC3\xB2", "\xF3" => "\xC3\xB3", "\xF4" => "\xC3\xB4", "\xF5" => "\xC3\xB5", "\xF6" => "\xC3\xB6", "\xF7" => "\xC3\xB7", "\xF8" => "\xC3\xB8", "\xF9" => "\xC3\xB9", "\xFA" => "\xC3\xBA", "\xFB" => "\xC3\xBB", "\xFC" => "\xC3\xBC", "\xFD" => "\xC3\xBD", "\xFE" => "\xC3\xBE", "\xFF" => "\xC3\xBF"); + + return strtr($string, $convert_table); + } + + function change_encoding($data, $input, $output) + { + $input = SimplePie_Misc::encoding($input); + $output = SimplePie_Misc::encoding($output); + + // We fail to fail on non US-ASCII bytes + if ($input === 'US-ASCII') + { + static $non_ascii_octects = ''; + if (!$non_ascii_octects) + { + for ($i = 0x80; $i <= 0xFF; $i++) + { + $non_ascii_octects .= chr($i); + } + } + $data = substr($data, 0, strcspn($data, $non_ascii_octects)); + } + + // This is first, as behaviour of this is completely predictable + if ($input === 'windows-1252' && $output === 'UTF-8') + { + return SimplePie_Misc::windows_1252_to_utf8($data); + } + // This is second, as behaviour of this varies only with PHP version (the middle part of this expression checks the encoding is supported). + elseif (function_exists('mb_convert_encoding') && @mb_convert_encoding("\x80", 'UTF-16BE', $input) !== "\x00\x80" && in_array($input, mb_list_encodings()) && ($return = @mb_convert_encoding($data, $output, $input))) + { + return $return; + } + // This is last, as behaviour of this varies with OS userland and PHP version + elseif (function_exists('iconv') && ($return = @iconv($input, $output, $data))) + { + return $return; + } + // If we can't do anything, just fail + else + { + return false; + } + } + + /** + * Normalize an encoding name + * + * This is automatically generated by create.php + * + * To generate it, run `php create.php` on the command line, and copy the + * output to replace this function. + * + * @param string $charset Character set to standardise + * @return string Standardised name + */ + function encoding($charset) + { + // Normalization from UTS #22 + switch (strtolower(preg_replace('/(?:[^a-zA-Z0-9]+|([^0-9])0+)/', '\1', $charset))) + { + case 'adobestandardencoding': + case 'csadobestandardencoding': + return 'Adobe-Standard-Encoding'; + + case 'adobesymbolencoding': + case 'cshppsmath': + return 'Adobe-Symbol-Encoding'; + + case 'ami1251': + case 'amiga1251': + return 'Amiga-1251'; + + case 'ansix31101983': + case 'csat5001983': + case 'csiso99naplps': + case 'isoir99': + case 'naplps': + return 'ANSI_X3.110-1983'; + + case 'arabic7': + case 'asmo449': + case 'csiso89asmo449': + case 'iso9036': + case 'isoir89': + return 'ASMO_449'; + + case 'big5': + case 'csbig5': + return 'Big5'; + + case 'big5hkscs': + return 'Big5-HKSCS'; + + case 'bocu1': + case 'csbocu1': + return 'BOCU-1'; + + case 'brf': + case 'csbrf': + return 'BRF'; + + case 'bs4730': + case 'csiso4unitedkingdom': + case 'gb': + case 'iso646gb': + case 'isoir4': + case 'uk': + return 'BS_4730'; + + case 'bsviewdata': + case 'csiso47bsviewdata': + case 'isoir47': + return 'BS_viewdata'; + + case 'cesu8': + case 'cscesu8': + return 'CESU-8'; + + case 'ca': + case 'csa71': + case 'csaz243419851': + case 'csiso121canadian1': + case 'iso646ca': + case 'isoir121': + return 'CSA_Z243.4-1985-1'; + + case 'csa72': + case 'csaz243419852': + case 'csiso122canadian2': + case 'iso646ca2': + case 'isoir122': + return 'CSA_Z243.4-1985-2'; + + case 'csaz24341985gr': + case 'csiso123csaz24341985gr': + case 'isoir123': + return 'CSA_Z243.4-1985-gr'; + + case 'csiso139csn369103': + case 'csn369103': + case 'isoir139': + return 'CSN_369103'; + + case 'csdecmcs': + case 'dec': + case 'decmcs': + return 'DEC-MCS'; + + case 'csiso21german': + case 'de': + case 'din66003': + case 'iso646de': + case 'isoir21': + return 'DIN_66003'; + + case 'csdkus': + case 'dkus': + return 'dk-us'; + + case 'csiso646danish': + case 'dk': + case 'ds2089': + case 'iso646dk': + return 'DS_2089'; + + case 'csibmebcdicatde': + case 'ebcdicatde': + return 'EBCDIC-AT-DE'; + + case 'csebcdicatdea': + case 'ebcdicatdea': + return 'EBCDIC-AT-DE-A'; + + case 'csebcdiccafr': + case 'ebcdiccafr': + return 'EBCDIC-CA-FR'; + + case 'csebcdicdkno': + case 'ebcdicdkno': + return 'EBCDIC-DK-NO'; + + case 'csebcdicdknoa': + case 'ebcdicdknoa': + return 'EBCDIC-DK-NO-A'; + + case 'csebcdices': + case 'ebcdices': + return 'EBCDIC-ES'; + + case 'csebcdicesa': + case 'ebcdicesa': + return 'EBCDIC-ES-A'; + + case 'csebcdicess': + case 'ebcdicess': + return 'EBCDIC-ES-S'; + + case 'csebcdicfise': + case 'ebcdicfise': + return 'EBCDIC-FI-SE'; + + case 'csebcdicfisea': + case 'ebcdicfisea': + return 'EBCDIC-FI-SE-A'; + + case 'csebcdicfr': + case 'ebcdicfr': + return 'EBCDIC-FR'; + + case 'csebcdicit': + case 'ebcdicit': + return 'EBCDIC-IT'; + + case 'csebcdicpt': + case 'ebcdicpt': + return 'EBCDIC-PT'; + + case 'csebcdicuk': + case 'ebcdicuk': + return 'EBCDIC-UK'; + + case 'csebcdicus': + case 'ebcdicus': + return 'EBCDIC-US'; + + case 'csiso111ecmacyrillic': + case 'ecmacyrillic': + case 'isoir111': + case 'koi8e': + return 'ECMA-cyrillic'; + + case 'csiso17spanish': + case 'es': + case 'iso646es': + case 'isoir17': + return 'ES'; + + case 'csiso85spanish2': + case 'es2': + case 'iso646es2': + case 'isoir85': + return 'ES2'; + + case 'cseucpkdfmtjapanese': + case 'eucjp': + case 'extendedunixcodepackedformatforjapanese': + return 'EUC-JP'; + + case 'cseucfixwidjapanese': + case 'extendedunixcodefixedwidthforjapanese': + return 'Extended_UNIX_Code_Fixed_Width_for_Japanese'; + + case 'gb18030': + return 'GB18030'; + + case 'chinese': + case 'cp936': + case 'csgb2312': + case 'csiso58gb231280': + case 'gb2312': + case 'gb231280': + case 'gbk': + case 'isoir58': + case 'ms936': + case 'windows936': + return 'GBK'; + + case 'cn': + case 'csiso57gb1988': + case 'gb198880': + case 'iso646cn': + case 'isoir57': + return 'GB_1988-80'; + + case 'csiso153gost1976874': + case 'gost1976874': + case 'isoir153': + case 'stsev35888': + return 'GOST_19768-74'; + + case 'csiso150': + case 'csiso150greekccitt': + case 'greekccitt': + case 'isoir150': + return 'greek-ccitt'; + + case 'csiso88greek7': + case 'greek7': + case 'isoir88': + return 'greek7'; + + case 'csiso18greek7old': + case 'greek7old': + case 'isoir18': + return 'greek7-old'; + + case 'cshpdesktop': + case 'hpdesktop': + return 'HP-DeskTop'; + + case 'cshplegal': + case 'hplegal': + return 'HP-Legal'; + + case 'cshpmath8': + case 'hpmath8': + return 'HP-Math8'; + + case 'cshppifont': + case 'hppifont': + return 'HP-Pi-font'; + + case 'cshproman8': + case 'hproman8': + case 'r8': + case 'roman8': + return 'hp-roman8'; + + case 'hzgb2312': + return 'HZ-GB-2312'; + + case 'csibmsymbols': + case 'ibmsymbols': + return 'IBM-Symbols'; + + case 'csibmthai': + case 'ibmthai': + return 'IBM-Thai'; + + case 'cp37': + case 'csibm37': + case 'ebcdiccpca': + case 'ebcdiccpnl': + case 'ebcdiccpus': + case 'ebcdiccpwt': + case 'ibm37': + return 'IBM037'; + + case 'cp38': + case 'csibm38': + case 'ebcdicint': + case 'ibm38': + return 'IBM038'; + + case 'cp273': + case 'csibm273': + case 'ibm273': + return 'IBM273'; + + case 'cp274': + case 'csibm274': + case 'ebcdicbe': + case 'ibm274': + return 'IBM274'; + + case 'cp275': + case 'csibm275': + case 'ebcdicbr': + case 'ibm275': + return 'IBM275'; + + case 'csibm277': + case 'ebcdiccpdk': + case 'ebcdiccpno': + case 'ibm277': + return 'IBM277'; + + case 'cp278': + case 'csibm278': + case 'ebcdiccpfi': + case 'ebcdiccpse': + case 'ibm278': + return 'IBM278'; + + case 'cp280': + case 'csibm280': + case 'ebcdiccpit': + case 'ibm280': + return 'IBM280'; + + case 'cp281': + case 'csibm281': + case 'ebcdicjpe': + case 'ibm281': + return 'IBM281'; + + case 'cp284': + case 'csibm284': + case 'ebcdiccpes': + case 'ibm284': + return 'IBM284'; + + case 'cp285': + case 'csibm285': + case 'ebcdiccpgb': + case 'ibm285': + return 'IBM285'; + + case 'cp290': + case 'csibm290': + case 'ebcdicjpkana': + case 'ibm290': + return 'IBM290'; + + case 'cp297': + case 'csibm297': + case 'ebcdiccpfr': + case 'ibm297': + return 'IBM297'; + + case 'cp420': + case 'csibm420': + case 'ebcdiccpar1': + case 'ibm420': + return 'IBM420'; + + case 'cp423': + case 'csibm423': + case 'ebcdiccpgr': + case 'ibm423': + return 'IBM423'; + + case 'cp424': + case 'csibm424': + case 'ebcdiccphe': + case 'ibm424': + return 'IBM424'; + + case '437': + case 'cp437': + case 'cspc8codepage437': + case 'ibm437': + return 'IBM437'; + + case 'cp500': + case 'csibm500': + case 'ebcdiccpbe': + case 'ebcdiccpch': + case 'ibm500': + return 'IBM500'; + + case 'cp775': + case 'cspc775baltic': + case 'ibm775': + return 'IBM775'; + + case '850': + case 'cp850': + case 'cspc850multilingual': + case 'ibm850': + return 'IBM850'; + + case '851': + case 'cp851': + case 'csibm851': + case 'ibm851': + return 'IBM851'; + + case '852': + case 'cp852': + case 'cspcp852': + case 'ibm852': + return 'IBM852'; + + case '855': + case 'cp855': + case 'csibm855': + case 'ibm855': + return 'IBM855'; + + case '857': + case 'cp857': + case 'csibm857': + case 'ibm857': + return 'IBM857'; + + case 'ccsid858': + case 'cp858': + case 'ibm858': + case 'pcmultilingual850euro': + return 'IBM00858'; + + case '860': + case 'cp860': + case 'csibm860': + case 'ibm860': + return 'IBM860'; + + case '861': + case 'cp861': + case 'cpis': + case 'csibm861': + case 'ibm861': + return 'IBM861'; + + case '862': + case 'cp862': + case 'cspc862latinhebrew': + case 'ibm862': + return 'IBM862'; + + case '863': + case 'cp863': + case 'csibm863': + case 'ibm863': + return 'IBM863'; + + case 'cp864': + case 'csibm864': + case 'ibm864': + return 'IBM864'; + + case '865': + case 'cp865': + case 'csibm865': + case 'ibm865': + return 'IBM865'; + + case '866': + case 'cp866': + case 'csibm866': + case 'ibm866': + return 'IBM866'; + + case 'cp868': + case 'cpar': + case 'csibm868': + case 'ibm868': + return 'IBM868'; + + case '869': + case 'cp869': + case 'cpgr': + case 'csibm869': + case 'ibm869': + return 'IBM869'; + + case 'cp870': + case 'csibm870': + case 'ebcdiccproece': + case 'ebcdiccpyu': + case 'ibm870': + return 'IBM870'; + + case 'cp871': + case 'csibm871': + case 'ebcdiccpis': + case 'ibm871': + return 'IBM871'; + + case 'cp880': + case 'csibm880': + case 'ebcdiccyrillic': + case 'ibm880': + return 'IBM880'; + + case 'cp891': + case 'csibm891': + case 'ibm891': + return 'IBM891'; + + case 'cp903': + case 'csibm903': + case 'ibm903': + return 'IBM903'; + + case '904': + case 'cp904': + case 'csibbm904': + case 'ibm904': + return 'IBM904'; + + case 'cp905': + case 'csibm905': + case 'ebcdiccptr': + case 'ibm905': + return 'IBM905'; + + case 'cp918': + case 'csibm918': + case 'ebcdiccpar2': + case 'ibm918': + return 'IBM918'; + + case 'ccsid924': + case 'cp924': + case 'ebcdiclatin9euro': + case 'ibm924': + return 'IBM00924'; + + case 'cp1026': + case 'csibm1026': + case 'ibm1026': + return 'IBM1026'; + + case 'ibm1047': + return 'IBM1047'; + + case 'ccsid1140': + case 'cp1140': + case 'ebcdicus37euro': + case 'ibm1140': + return 'IBM01140'; + + case 'ccsid1141': + case 'cp1141': + case 'ebcdicde273euro': + case 'ibm1141': + return 'IBM01141'; + + case 'ccsid1142': + case 'cp1142': + case 'ebcdicdk277euro': + case 'ebcdicno277euro': + case 'ibm1142': + return 'IBM01142'; + + case 'ccsid1143': + case 'cp1143': + case 'ebcdicfi278euro': + case 'ebcdicse278euro': + case 'ibm1143': + return 'IBM01143'; + + case 'ccsid1144': + case 'cp1144': + case 'ebcdicit280euro': + case 'ibm1144': + return 'IBM01144'; + + case 'ccsid1145': + case 'cp1145': + case 'ebcdices284euro': + case 'ibm1145': + return 'IBM01145'; + + case 'ccsid1146': + case 'cp1146': + case 'ebcdicgb285euro': + case 'ibm1146': + return 'IBM01146'; + + case 'ccsid1147': + case 'cp1147': + case 'ebcdicfr297euro': + case 'ibm1147': + return 'IBM01147'; + + case 'ccsid1148': + case 'cp1148': + case 'ebcdicinternational500euro': + case 'ibm1148': + return 'IBM01148'; + + case 'ccsid1149': + case 'cp1149': + case 'ebcdicis871euro': + case 'ibm1149': + return 'IBM01149'; + + case 'csiso143iecp271': + case 'iecp271': + case 'isoir143': + return 'IEC_P27-1'; + + case 'csiso49inis': + case 'inis': + case 'isoir49': + return 'INIS'; + + case 'csiso50inis8': + case 'inis8': + case 'isoir50': + return 'INIS-8'; + + case 'csiso51iniscyrillic': + case 'iniscyrillic': + case 'isoir51': + return 'INIS-cyrillic'; + + case 'csinvariant': + case 'invariant': + return 'INVARIANT'; + + case 'iso2022cn': + return 'ISO-2022-CN'; + + case 'iso2022cnext': + return 'ISO-2022-CN-EXT'; + + case 'csiso2022jp': + case 'iso2022jp': + return 'ISO-2022-JP'; + + case 'csiso2022jp2': + case 'iso2022jp2': + return 'ISO-2022-JP-2'; + + case 'csiso2022kr': + case 'iso2022kr': + return 'ISO-2022-KR'; + + case 'cswindows30latin1': + case 'iso88591windows30latin1': + return 'ISO-8859-1-Windows-3.0-Latin-1'; + + case 'cswindows31latin1': + case 'iso88591windows31latin1': + return 'ISO-8859-1-Windows-3.1-Latin-1'; + + case 'csisolatin2': + case 'iso88592': + case 'iso885921987': + case 'isoir101': + case 'l2': + case 'latin2': + return 'ISO-8859-2'; + + case 'cswindows31latin2': + case 'iso88592windowslatin2': + return 'ISO-8859-2-Windows-Latin-2'; + + case 'csisolatin3': + case 'iso88593': + case 'iso885931988': + case 'isoir109': + case 'l3': + case 'latin3': + return 'ISO-8859-3'; + + case 'csisolatin4': + case 'iso88594': + case 'iso885941988': + case 'isoir110': + case 'l4': + case 'latin4': + return 'ISO-8859-4'; + + case 'csisolatincyrillic': + case 'cyrillic': + case 'iso88595': + case 'iso885951988': + case 'isoir144': + return 'ISO-8859-5'; + + case 'arabic': + case 'asmo708': + case 'csisolatinarabic': + case 'ecma114': + case 'iso88596': + case 'iso885961987': + case 'isoir127': + return 'ISO-8859-6'; + + case 'csiso88596e': + case 'iso88596e': + return 'ISO-8859-6-E'; + + case 'csiso88596i': + case 'iso88596i': + return 'ISO-8859-6-I'; + + case 'csisolatingreek': + case 'ecma118': + case 'elot928': + case 'greek': + case 'greek8': + case 'iso88597': + case 'iso885971987': + case 'isoir126': + return 'ISO-8859-7'; + + case 'csisolatinhebrew': + case 'hebrew': + case 'iso88598': + case 'iso885981988': + case 'isoir138': + return 'ISO-8859-8'; + + case 'csiso88598e': + case 'iso88598e': + return 'ISO-8859-8-E'; + + case 'csiso88598i': + case 'iso88598i': + return 'ISO-8859-8-I'; + + case 'cswindows31latin5': + case 'iso88599windowslatin5': + return 'ISO-8859-9-Windows-Latin-5'; + + case 'csisolatin6': + case 'iso885910': + case 'iso8859101992': + case 'isoir157': + case 'l6': + case 'latin6': + return 'ISO-8859-10'; + + case 'iso885913': + return 'ISO-8859-13'; + + case 'iso885914': + case 'iso8859141998': + case 'isoceltic': + case 'isoir199': + case 'l8': + case 'latin8': + return 'ISO-8859-14'; + + case 'iso885915': + case 'latin9': + return 'ISO-8859-15'; + + case 'iso885916': + case 'iso8859162001': + case 'isoir226': + case 'l10': + case 'latin10': + return 'ISO-8859-16'; + + case 'iso10646j1': + return 'ISO-10646-J-1'; + + case 'csunicode': + case 'iso10646ucs2': + return 'ISO-10646-UCS-2'; + + case 'csucs4': + case 'iso10646ucs4': + return 'ISO-10646-UCS-4'; + + case 'csunicodeascii': + case 'iso10646ucsbasic': + return 'ISO-10646-UCS-Basic'; + + case 'csunicodelatin1': + case 'iso10646': + case 'iso10646unicodelatin1': + return 'ISO-10646-Unicode-Latin1'; + + case 'csiso10646utf1': + case 'iso10646utf1': + return 'ISO-10646-UTF-1'; + + case 'csiso115481': + case 'iso115481': + case 'isotr115481': + return 'ISO-11548-1'; + + case 'csiso90': + case 'isoir90': + return 'iso-ir-90'; + + case 'csunicodeibm1261': + case 'isounicodeibm1261': + return 'ISO-Unicode-IBM-1261'; + + case 'csunicodeibm1264': + case 'isounicodeibm1264': + return 'ISO-Unicode-IBM-1264'; + + case 'csunicodeibm1265': + case 'isounicodeibm1265': + return 'ISO-Unicode-IBM-1265'; + + case 'csunicodeibm1268': + case 'isounicodeibm1268': + return 'ISO-Unicode-IBM-1268'; + + case 'csunicodeibm1276': + case 'isounicodeibm1276': + return 'ISO-Unicode-IBM-1276'; + + case 'csiso646basic1983': + case 'iso646basic1983': + case 'ref': + return 'ISO_646.basic:1983'; + + case 'csiso2intlrefversion': + case 'irv': + case 'iso646irv1983': + case 'isoir2': + return 'ISO_646.irv:1983'; + + case 'csiso2033': + case 'e13b': + case 'iso20331983': + case 'isoir98': + return 'ISO_2033-1983'; + + case 'csiso5427cyrillic': + case 'iso5427': + case 'isoir37': + return 'ISO_5427'; + + case 'iso5427cyrillic1981': + case 'iso54271981': + case 'isoir54': + return 'ISO_5427:1981'; + + case 'csiso5428greek': + case 'iso54281980': + case 'isoir55': + return 'ISO_5428:1980'; + + case 'csiso6937add': + case 'iso6937225': + case 'isoir152': + return 'ISO_6937-2-25'; + + case 'csisotextcomm': + case 'iso69372add': + case 'isoir142': + return 'ISO_6937-2-add'; + + case 'csiso8859supp': + case 'iso8859supp': + case 'isoir154': + case 'latin125': + return 'ISO_8859-supp'; + + case 'csiso10367box': + case 'iso10367box': + case 'isoir155': + return 'ISO_10367-box'; + + case 'csiso15italian': + case 'iso646it': + case 'isoir15': + case 'it': + return 'IT'; + + case 'csiso13jisc6220jp': + case 'isoir13': + case 'jisc62201969': + case 'jisc62201969jp': + case 'katakana': + case 'x2017': + return 'JIS_C6220-1969-jp'; + + case 'csiso14jisc6220ro': + case 'iso646jp': + case 'isoir14': + case 'jisc62201969ro': + case 'jp': + return 'JIS_C6220-1969-ro'; + + case 'csiso42jisc62261978': + case 'isoir42': + case 'jisc62261978': + return 'JIS_C6226-1978'; + + case 'csiso87jisx208': + case 'isoir87': + case 'jisc62261983': + case 'jisx2081983': + case 'x208': + return 'JIS_C6226-1983'; + + case 'csiso91jisc62291984a': + case 'isoir91': + case 'jisc62291984a': + case 'jpocra': + return 'JIS_C6229-1984-a'; + + case 'csiso92jisc62991984b': + case 'iso646jpocrb': + case 'isoir92': + case 'jisc62291984b': + case 'jpocrb': + return 'JIS_C6229-1984-b'; + + case 'csiso93jis62291984badd': + case 'isoir93': + case 'jisc62291984badd': + case 'jpocrbadd': + return 'JIS_C6229-1984-b-add'; + + case 'csiso94jis62291984hand': + case 'isoir94': + case 'jisc62291984hand': + case 'jpocrhand': + return 'JIS_C6229-1984-hand'; + + case 'csiso95jis62291984handadd': + case 'isoir95': + case 'jisc62291984handadd': + case 'jpocrhandadd': + return 'JIS_C6229-1984-hand-add'; + + case 'csiso96jisc62291984kana': + case 'isoir96': + case 'jisc62291984kana': + return 'JIS_C6229-1984-kana'; + + case 'csjisencoding': + case 'jisencoding': + return 'JIS_Encoding'; + + case 'cshalfwidthkatakana': + case 'jisx201': + case 'x201': + return 'JIS_X0201'; + + case 'csiso159jisx2121990': + case 'isoir159': + case 'jisx2121990': + case 'x212': + return 'JIS_X0212-1990'; + + case 'csiso141jusib1002': + case 'iso646yu': + case 'isoir141': + case 'js': + case 'jusib1002': + case 'yu': + return 'JUS_I.B1.002'; + + case 'csiso147macedonian': + case 'isoir147': + case 'jusib1003mac': + case 'macedonian': + return 'JUS_I.B1.003-mac'; + + case 'csiso146serbian': + case 'isoir146': + case 'jusib1003serb': + case 'serbian': + return 'JUS_I.B1.003-serb'; + + case 'koi7switched': + return 'KOI7-switched'; + + case 'cskoi8r': + case 'koi8r': + return 'KOI8-R'; + + case 'koi8u': + return 'KOI8-U'; + + case 'csksc5636': + case 'iso646kr': + case 'ksc5636': + return 'KSC5636'; + + case 'cskz1048': + case 'kz1048': + case 'rk1048': + case 'strk10482002': + return 'KZ-1048'; + + case 'csiso19latingreek': + case 'isoir19': + case 'latingreek': + return 'latin-greek'; + + case 'csiso27latingreek1': + case 'isoir27': + case 'latingreek1': + return 'Latin-greek-1'; + + case 'csiso158lap': + case 'isoir158': + case 'lap': + case 'latinlap': + return 'latin-lap'; + + case 'csmacintosh': + case 'mac': + case 'macintosh': + return 'macintosh'; + + case 'csmicrosoftpublishing': + case 'microsoftpublishing': + return 'Microsoft-Publishing'; + + case 'csmnem': + case 'mnem': + return 'MNEM'; + + case 'csmnemonic': + case 'mnemonic': + return 'MNEMONIC'; + + case 'csiso86hungarian': + case 'hu': + case 'iso646hu': + case 'isoir86': + case 'msz77953': + return 'MSZ_7795.3'; + + case 'csnatsdano': + case 'isoir91': + case 'natsdano': + return 'NATS-DANO'; + + case 'csnatsdanoadd': + case 'isoir92': + case 'natsdanoadd': + return 'NATS-DANO-ADD'; + + case 'csnatssefi': + case 'isoir81': + case 'natssefi': + return 'NATS-SEFI'; + + case 'csnatssefiadd': + case 'isoir82': + case 'natssefiadd': + return 'NATS-SEFI-ADD'; + + case 'csiso151cuba': + case 'cuba': + case 'iso646cu': + case 'isoir151': + case 'ncnc1081': + return 'NC_NC00-10:81'; + + case 'csiso69french': + case 'fr': + case 'iso646fr': + case 'isoir69': + case 'nfz62010': + return 'NF_Z_62-010'; + + case 'csiso25french': + case 'iso646fr1': + case 'isoir25': + case 'nfz620101973': + return 'NF_Z_62-010_(1973)'; + + case 'csiso60danishnorwegian': + case 'csiso60norwegian1': + case 'iso646no': + case 'isoir60': + case 'no': + case 'ns45511': + return 'NS_4551-1'; + + case 'csiso61norwegian2': + case 'iso646no2': + case 'isoir61': + case 'no2': + case 'ns45512': + return 'NS_4551-2'; + + case 'osdebcdicdf3irv': + return 'OSD_EBCDIC_DF03_IRV'; + + case 'osdebcdicdf41': + return 'OSD_EBCDIC_DF04_1'; + + case 'osdebcdicdf415': + return 'OSD_EBCDIC_DF04_15'; + + case 'cspc8danishnorwegian': + case 'pc8danishnorwegian': + return 'PC8-Danish-Norwegian'; + + case 'cspc8turkish': + case 'pc8turkish': + return 'PC8-Turkish'; + + case 'csiso16portuguese': + case 'iso646pt': + case 'isoir16': + case 'pt': + return 'PT'; + + case 'csiso84portuguese2': + case 'iso646pt2': + case 'isoir84': + case 'pt2': + return 'PT2'; + + case 'cp154': + case 'csptcp154': + case 'cyrillicasian': + case 'pt154': + case 'ptcp154': + return 'PTCP154'; + + case 'scsu': + return 'SCSU'; + + case 'csiso10swedish': + case 'fi': + case 'iso646fi': + case 'iso646se': + case 'isoir10': + case 'se': + case 'sen850200b': + return 'SEN_850200_B'; + + case 'csiso11swedishfornames': + case 'iso646se2': + case 'isoir11': + case 'se2': + case 'sen850200c': + return 'SEN_850200_C'; + + case 'csiso102t617bit': + case 'isoir102': + case 't617bit': + return 'T.61-7bit'; + + case 'csiso103t618bit': + case 'isoir103': + case 't61': + case 't618bit': + return 'T.61-8bit'; + + case 'csiso128t101g2': + case 'isoir128': + case 't101g2': + return 'T.101-G2'; + + case 'cstscii': + case 'tscii': + return 'TSCII'; + + case 'csunicode11': + case 'unicode11': + return 'UNICODE-1-1'; + + case 'csunicode11utf7': + case 'unicode11utf7': + return 'UNICODE-1-1-UTF-7'; + + case 'csunknown8bit': + case 'unknown8bit': + return 'UNKNOWN-8BIT'; + + case 'ansix341968': + case 'ansix341986': + case 'ascii': + case 'cp367': + case 'csascii': + case 'ibm367': + case 'iso646irv1991': + case 'iso646us': + case 'isoir6': + case 'us': + case 'usascii': + return 'US-ASCII'; + + case 'csusdk': + case 'usdk': + return 'us-dk'; + + case 'utf7': + return 'UTF-7'; + + case 'utf8': + return 'UTF-8'; + + case 'utf16': + return 'UTF-16'; + + case 'utf16be': + return 'UTF-16BE'; + + case 'utf16le': + return 'UTF-16LE'; + + case 'utf32': + return 'UTF-32'; + + case 'utf32be': + return 'UTF-32BE'; + + case 'utf32le': + return 'UTF-32LE'; + + case 'csventurainternational': + case 'venturainternational': + return 'Ventura-International'; + + case 'csventuramath': + case 'venturamath': + return 'Ventura-Math'; + + case 'csventuraus': + case 'venturaus': + return 'Ventura-US'; + + case 'csiso70videotexsupp1': + case 'isoir70': + case 'videotexsuppl': + return 'videotex-suppl'; + + case 'csviqr': + case 'viqr': + return 'VIQR'; + + case 'csviscii': + case 'viscii': + return 'VISCII'; + + case 'csshiftjis': + case 'cswindows31j': + case 'mskanji': + case 'shiftjis': + case 'windows31j': + return 'Windows-31J'; + + case 'iso885911': + case 'tis620': + return 'windows-874'; + + case 'cseuckr': + case 'csksc56011987': + case 'euckr': + case 'isoir149': + case 'korean': + case 'ksc5601': + case 'ksc56011987': + case 'ksc56011989': + case 'windows949': + return 'windows-949'; + + case 'windows1250': + return 'windows-1250'; + + case 'windows1251': + return 'windows-1251'; + + case 'cp819': + case 'csisolatin1': + case 'ibm819': + case 'iso88591': + case 'iso885911987': + case 'isoir100': + case 'l1': + case 'latin1': + case 'windows1252': + return 'windows-1252'; + + case 'windows1253': + return 'windows-1253'; + + case 'csisolatin5': + case 'iso88599': + case 'iso885991989': + case 'isoir148': + case 'l5': + case 'latin5': + case 'windows1254': + return 'windows-1254'; + + case 'windows1255': + return 'windows-1255'; + + case 'windows1256': + return 'windows-1256'; + + case 'windows1257': + return 'windows-1257'; + + case 'windows1258': + return 'windows-1258'; + + default: + return $charset; + } + } + + function get_curl_version() + { + if (is_array($curl = curl_version())) + { + $curl = $curl['version']; + } + elseif (substr($curl, 0, 5) === 'curl/') + { + $curl = substr($curl, 5, strcspn($curl, "\x09\x0A\x0B\x0C\x0D", 5)); + } + elseif (substr($curl, 0, 8) === 'libcurl/') + { + $curl = substr($curl, 8, strcspn($curl, "\x09\x0A\x0B\x0C\x0D", 8)); + } + else + { + $curl = 0; + } + return $curl; + } + + function is_subclass_of($class1, $class2) + { + if (func_num_args() !== 2) + { + trigger_error('Wrong parameter count for SimplePie_Misc::is_subclass_of()', E_USER_WARNING); + } + elseif (version_compare(PHP_VERSION, '5.0.3', '>=') || is_object($class1)) + { + return is_subclass_of($class1, $class2); + } + elseif (is_string($class1) && is_string($class2)) + { + if (class_exists($class1)) + { + if (class_exists($class2)) + { + $class2 = strtolower($class2); + while ($class1 = strtolower(get_parent_class($class1))) + { + if ($class1 === $class2) + { + return true; + } + } + } + } + else + { + trigger_error('Unknown class passed as parameter', E_USER_WARNNG); + } + } + return false; + } + + /** + * Strip HTML comments + * + * @access public + * @param string $data Data to strip comments from + * @return string Comment stripped string + */ + function strip_comments($data) + { + $output = ''; + while (($start = strpos($data, '', $start)) !== false) + { + $data = substr_replace($data, '', 0, $end + 3); + } + else + { + $data = ''; + } + } + return $output . $data; + } + + function parse_date($dt) + { + $parser = SimplePie_Parse_Date::get(); + return $parser->parse($dt); + } + + /** + * Decode HTML entities + * + * @static + * @access public + * @param string $data Input data + * @return string Output data + */ + function entities_decode($data) + { + $decoder = new SimplePie_Decode_HTML_Entities($data); + return $decoder->parse(); + } + + /** + * Remove RFC822 comments + * + * @access public + * @param string $data Data to strip comments from + * @return string Comment stripped string + */ + function uncomment_rfc822($string) + { + $string = (string) $string; + $position = 0; + $length = strlen($string); + $depth = 0; + + $output = ''; + + while ($position < $length && ($pos = strpos($string, '(', $position)) !== false) + { + $output .= substr($string, $position, $pos - $position); + $position = $pos + 1; + if ($string[$pos - 1] !== '\\') + { + $depth++; + while ($depth && $position < $length) + { + $position += strcspn($string, '()', $position); + if ($string[$position - 1] === '\\') + { + $position++; + continue; + } + elseif (isset($string[$position])) + { + switch ($string[$position]) + { + case '(': + $depth++; + break; + + case ')': + $depth--; + break; + } + $position++; + } + else + { + break; + } + } + } + else + { + $output .= '('; + } + } + $output .= substr($string, $position); + + return $output; + } + + function parse_mime($mime) + { + if (($pos = strpos($mime, ';')) === false) + { + return trim($mime); + } + else + { + return trim(substr($mime, 0, $pos)); + } + } + + function htmlspecialchars_decode($string, $quote_style) + { + if (function_exists('htmlspecialchars_decode')) + { + return htmlspecialchars_decode($string, $quote_style); + } + else + { + return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style))); + } + } + + function atom_03_construct_type($attribs) + { + if (isset($attribs['']['mode']) && strtolower(trim($attribs['']['mode']) === 'base64')) + { + $mode = SIMPLEPIE_CONSTRUCT_BASE64; + } + else + { + $mode = SIMPLEPIE_CONSTRUCT_NONE; + } + if (isset($attribs['']['type'])) + { + switch (strtolower(trim($attribs['']['type']))) + { + case 'text': + case 'text/plain': + return SIMPLEPIE_CONSTRUCT_TEXT | $mode; + + case 'html': + case 'text/html': + return SIMPLEPIE_CONSTRUCT_HTML | $mode; + + case 'xhtml': + case 'application/xhtml+xml': + return SIMPLEPIE_CONSTRUCT_XHTML | $mode; + + default: + return SIMPLEPIE_CONSTRUCT_NONE | $mode; + } + } + else + { + return SIMPLEPIE_CONSTRUCT_TEXT | $mode; + } + } + + function atom_10_construct_type($attribs) + { + if (isset($attribs['']['type'])) + { + switch (strtolower(trim($attribs['']['type']))) + { + case 'text': + return SIMPLEPIE_CONSTRUCT_TEXT; + + case 'html': + return SIMPLEPIE_CONSTRUCT_HTML; + + case 'xhtml': + return SIMPLEPIE_CONSTRUCT_XHTML; + + default: + return SIMPLEPIE_CONSTRUCT_NONE; + } + } + return SIMPLEPIE_CONSTRUCT_TEXT; + } + + function atom_10_content_construct_type($attribs) + { + if (isset($attribs['']['type'])) + { + $type = strtolower(trim($attribs['']['type'])); + switch ($type) + { + case 'text': + return SIMPLEPIE_CONSTRUCT_TEXT; + + case 'html': + return SIMPLEPIE_CONSTRUCT_HTML; + + case 'xhtml': + return SIMPLEPIE_CONSTRUCT_XHTML; + } + if (in_array(substr($type, -4), array('+xml', '/xml')) || substr($type, 0, 5) === 'text/') + { + return SIMPLEPIE_CONSTRUCT_NONE; + } + else + { + return SIMPLEPIE_CONSTRUCT_BASE64; + } + } + else + { + return SIMPLEPIE_CONSTRUCT_TEXT; + } + } + + function is_isegment_nz_nc($string) + { + return (bool) preg_match('/^([A-Za-z0-9\-._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!$&\'()*+,;=@]|(%[0-9ABCDEF]{2}))+$/u', $string); + } + + function space_seperated_tokens($string) + { + $space_characters = "\x20\x09\x0A\x0B\x0C\x0D"; + $string_length = strlen($string); + + $position = strspn($string, $space_characters); + $tokens = array(); + + while ($position < $string_length) + { + $len = strcspn($string, $space_characters, $position); + $tokens[] = substr($string, $position, $len); + $position += $len; + $position += strspn($string, $space_characters, $position); + } + + return $tokens; + } + + function array_unique($array) + { + if (version_compare(PHP_VERSION, '5.2', '>=')) + { + return array_unique($array); + } + else + { + $array = (array) $array; + $new_array = array(); + $new_array_strings = array(); + foreach ($array as $key => $value) + { + if (is_object($value)) + { + if (method_exists($value, '__toString')) + { + $cmp = $value->__toString(); + } + else + { + trigger_error('Object of class ' . get_class($value) . ' could not be converted to string', E_USER_ERROR); + } + } + elseif (is_array($value)) + { + $cmp = (string) reset($value); + } + else + { + $cmp = (string) $value; + } + if (!in_array($cmp, $new_array_strings)) + { + $new_array[$key] = $value; + $new_array_strings[] = $cmp; + } + } + return $new_array; + } + } + + /** + * Converts a unicode codepoint to a UTF-8 character + * + * @static + * @access public + * @param int $codepoint Unicode codepoint + * @return string UTF-8 character + */ + function codepoint_to_utf8($codepoint) + { + $codepoint = (int) $codepoint; + if ($codepoint < 0) + { + return false; + } + else if ($codepoint <= 0x7f) + { + return chr($codepoint); + } + else if ($codepoint <= 0x7ff) + { + return chr(0xc0 | ($codepoint >> 6)) . chr(0x80 | ($codepoint & 0x3f)); + } + else if ($codepoint <= 0xffff) + { + return chr(0xe0 | ($codepoint >> 12)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f)); + } + else if ($codepoint <= 0x10ffff) + { + return chr(0xf0 | ($codepoint >> 18)) . chr(0x80 | (($codepoint >> 12) & 0x3f)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f)); + } + else + { + // U+FFFD REPLACEMENT CHARACTER + return "\xEF\xBF\xBD"; + } + } + + /** + * Re-implementation of PHP 5's stripos() + * + * Returns the numeric position of the first occurrence of needle in the + * haystack string. + * + * @static + * @access string + * @param object $haystack + * @param string $needle Note that the needle may be a string of one or more + * characters. If needle is not a string, it is converted to an integer + * and applied as the ordinal value of a character. + * @param int $offset The optional offset parameter allows you to specify which + * character in haystack to start searching. The position returned is still + * relative to the beginning of haystack. + * @return bool If needle is not found, stripos() will return boolean false. + */ + function stripos($haystack, $needle, $offset = 0) + { + if (function_exists('stripos')) + { + return stripos($haystack, $needle, $offset); + } + else + { + if (is_string($needle)) + { + $needle = strtolower($needle); + } + elseif (is_int($needle) || is_bool($needle) || is_double($needle)) + { + $needle = strtolower(chr($needle)); + } + else + { + trigger_error('needle is not a string or an integer', E_USER_WARNING); + return false; + } + + return strpos(strtolower($haystack), $needle, $offset); + } + } + + /** + * Similar to parse_str() + * + * Returns an associative array of name/value pairs, where the value is an + * array of values that have used the same name + * + * @static + * @access string + * @param string $str The input string. + * @return array + */ + function parse_str($str) + { + $return = array(); + $str = explode('&', $str); + + foreach ($str as $section) + { + if (strpos($section, '=') !== false) + { + list($name, $value) = explode('=', $section, 2); + $return[urldecode($name)][] = urldecode($value); + } + else + { + $return[urldecode($section)][] = null; + } + } + + return $return; + } + + /** + * Detect XML encoding, as per XML 1.0 Appendix F.1 + * + * @todo Add support for EBCDIC + * @param string $data XML data + * @return array Possible encodings + */ + function xml_encoding($data) + { + // UTF-32 Big Endian BOM + if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") + { + $encoding[] = 'UTF-32BE'; + } + // UTF-32 Little Endian BOM + elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") + { + $encoding[] = 'UTF-32LE'; + } + // UTF-16 Big Endian BOM + elseif (substr($data, 0, 2) === "\xFE\xFF") + { + $encoding[] = 'UTF-16BE'; + } + // UTF-16 Little Endian BOM + elseif (substr($data, 0, 2) === "\xFF\xFE") + { + $encoding[] = 'UTF-16LE'; + } + // UTF-8 BOM + elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") + { + $encoding[] = 'UTF-8'; + } + // UTF-32 Big Endian Without BOM + elseif (substr($data, 0, 20) === "\x00\x00\x00\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C") + { + if ($pos = strpos($data, "\x00\x00\x00\x3F\x00\x00\x00\x3E")) + { + $parser = new SimplePie_XML_Declaration_Parser(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32BE', 'UTF-8')); + if ($parser->parse()) + { + $encoding[] = $parser->encoding; + } + } + $encoding[] = 'UTF-32BE'; + } + // UTF-32 Little Endian Without BOM + elseif (substr($data, 0, 20) === "\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C\x00\x00\x00") + { + if ($pos = strpos($data, "\x3F\x00\x00\x00\x3E\x00\x00\x00")) + { + $parser = new SimplePie_XML_Declaration_Parser(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32LE', 'UTF-8')); + if ($parser->parse()) + { + $encoding[] = $parser->encoding; + } + } + $encoding[] = 'UTF-32LE'; + } + // UTF-16 Big Endian Without BOM + elseif (substr($data, 0, 10) === "\x00\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C") + { + if ($pos = strpos($data, "\x00\x3F\x00\x3E")) + { + $parser = new SimplePie_XML_Declaration_Parser(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16BE', 'UTF-8')); + if ($parser->parse()) + { + $encoding[] = $parser->encoding; + } + } + $encoding[] = 'UTF-16BE'; + } + // UTF-16 Little Endian Without BOM + elseif (substr($data, 0, 10) === "\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C\x00") + { + if ($pos = strpos($data, "\x3F\x00\x3E\x00")) + { + $parser = new SimplePie_XML_Declaration_Parser(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16LE', 'UTF-8')); + if ($parser->parse()) + { + $encoding[] = $parser->encoding; + } + } + $encoding[] = 'UTF-16LE'; + } + // US-ASCII (or superset) + elseif (substr($data, 0, 5) === "\x3C\x3F\x78\x6D\x6C") + { + if ($pos = strpos($data, "\x3F\x3E")) + { + $parser = new SimplePie_XML_Declaration_Parser(substr($data, 5, $pos - 5)); + if ($parser->parse()) + { + $encoding[] = $parser->encoding; + } + } + $encoding[] = 'UTF-8'; + } + // Fallback to UTF-8 + else + { + $encoding[] = 'UTF-8'; + } + return $encoding; + } + + function output_javascript() + { + if (function_exists('ob_gzhandler')) + { + ob_start('ob_gzhandler'); + } + header('Content-type: text/javascript; charset: UTF-8'); + header('Cache-Control: must-revalidate'); + header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 604800) . ' GMT'); // 7 days + ?> +function embed_odeo(link) { + document.writeln(''); +} + +function embed_quicktime(type, bgcolor, width, height, link, placeholder, loop) { + if (placeholder != '') { + document.writeln(''); + } + else { + document.writeln(''); + } +} + +function embed_flash(bgcolor, width, height, link, loop, type) { + document.writeln(''); +} + +function embed_flv(width, height, link, placeholder, loop, player) { + document.writeln(''); +} + +function embed_wmedia(width, height, link) { + document.writeln(''); +} + error() !== null) + { + $info .= 'Error occurred: ' . $sp->error() . "\n"; + } + else + { + $info .= "No error found.\n"; + } + $info .= "Extensions:\n"; + $extensions = array('pcre', 'curl', 'zlib', 'mbstring', 'iconv', 'xmlreader', 'xml'); + foreach ($extensions as $ext) + { + if (extension_loaded($ext)) + { + $info .= " $ext loaded\n"; + switch ($ext) + { + case 'pcre': + $info .= ' Version ' . PCRE_VERSION . "\n"; + break; + case 'curl': + $version = curl_version(); + $info .= ' Version ' . $version['version'] . "\n"; + break; + case 'mbstring': + $info .= ' Overloading: ' . mb_get_info('func_overload') . "\n"; + break; + case 'iconv': + $info .= ' Version ' . ICONV_VERSION . "\n"; + break; + case 'xml': + $info .= ' Version ' . LIBXML_DOTTED_VERSION . "\n"; + break; + } + } + else + { + $info .= " $ext not loaded\n"; + } + } + return $info; + } +} + +/** + * Decode HTML Entities + * + * This implements HTML5 as of revision 967 (2007-06-28) + * + * @package SimplePie + */ +class SimplePie_Decode_HTML_Entities +{ + /** + * Data to be parsed + * + * @access private + * @var string + */ + var $data = ''; + + /** + * Currently consumed bytes + * + * @access private + * @var string + */ + var $consumed = ''; + + /** + * Position of the current byte being parsed + * + * @access private + * @var int + */ + var $position = 0; + + /** + * Create an instance of the class with the input data + * + * @access public + * @param string $data Input data + */ + function SimplePie_Decode_HTML_Entities($data) + { + $this->data = $data; + } + + /** + * Parse the input data + * + * @access public + * @return string Output data + */ + function parse() + { + while (($this->position = strpos($this->data, '&', $this->position)) !== false) + { + $this->consume(); + $this->entity(); + $this->consumed = ''; + } + return $this->data; + } + + /** + * Consume the next byte + * + * @access private + * @return mixed The next byte, or false, if there is no more data + */ + function consume() + { + if (isset($this->data[$this->position])) + { + $this->consumed .= $this->data[$this->position]; + return $this->data[$this->position++]; + } + else + { + return false; + } + } + + /** + * Consume a range of characters + * + * @access private + * @param string $chars Characters to consume + * @return mixed A series of characters that match the range, or false + */ + function consume_range($chars) + { + if ($len = strspn($this->data, $chars, $this->position)) + { + $data = substr($this->data, $this->position, $len); + $this->consumed .= $data; + $this->position += $len; + return $data; + } + else + { + return false; + } + } + + /** + * Unconsume one byte + * + * @access private + */ + function unconsume() + { + $this->consumed = substr($this->consumed, 0, -1); + $this->position--; + } + + /** + * Decode an entity + * + * @access private + */ + function entity() + { + switch ($this->consume()) + { + case "\x09": + case "\x0A": + case "\x0B": + case "\x0B": + case "\x0C": + case "\x20": + case "\x3C": + case "\x26": + case false: + break; + + case "\x23": + switch ($this->consume()) + { + case "\x78": + case "\x58": + $range = '0123456789ABCDEFabcdef'; + $hex = true; + break; + + default: + $range = '0123456789'; + $hex = false; + $this->unconsume(); + break; + } + + if ($codepoint = $this->consume_range($range)) + { + static $windows_1252_specials = array(0x0D => "\x0A", 0x80 => "\xE2\x82\xAC", 0x81 => "\xEF\xBF\xBD", 0x82 => "\xE2\x80\x9A", 0x83 => "\xC6\x92", 0x84 => "\xE2\x80\x9E", 0x85 => "\xE2\x80\xA6", 0x86 => "\xE2\x80\xA0", 0x87 => "\xE2\x80\xA1", 0x88 => "\xCB\x86", 0x89 => "\xE2\x80\xB0", 0x8A => "\xC5\xA0", 0x8B => "\xE2\x80\xB9", 0x8C => "\xC5\x92", 0x8D => "\xEF\xBF\xBD", 0x8E => "\xC5\xBD", 0x8F => "\xEF\xBF\xBD", 0x90 => "\xEF\xBF\xBD", 0x91 => "\xE2\x80\x98", 0x92 => "\xE2\x80\x99", 0x93 => "\xE2\x80\x9C", 0x94 => "\xE2\x80\x9D", 0x95 => "\xE2\x80\xA2", 0x96 => "\xE2\x80\x93", 0x97 => "\xE2\x80\x94", 0x98 => "\xCB\x9C", 0x99 => "\xE2\x84\xA2", 0x9A => "\xC5\xA1", 0x9B => "\xE2\x80\xBA", 0x9C => "\xC5\x93", 0x9D => "\xEF\xBF\xBD", 0x9E => "\xC5\xBE", 0x9F => "\xC5\xB8"); + + if ($hex) + { + $codepoint = hexdec($codepoint); + } + else + { + $codepoint = intval($codepoint); + } + + if (isset($windows_1252_specials[$codepoint])) + { + $replacement = $windows_1252_specials[$codepoint]; + } + else + { + $replacement = SimplePie_Misc::codepoint_to_utf8($codepoint); + } + + if (!in_array($this->consume(), array(';', false), true)) + { + $this->unconsume(); + } + + $consumed_length = strlen($this->consumed); + $this->data = substr_replace($this->data, $replacement, $this->position - $consumed_length, $consumed_length); + $this->position += strlen($replacement) - $consumed_length; + } + break; + + default: + static $entities = array('Aacute' => "\xC3\x81", 'aacute' => "\xC3\xA1", 'Aacute;' => "\xC3\x81", 'aacute;' => "\xC3\xA1", 'Acirc' => "\xC3\x82", 'acirc' => "\xC3\xA2", 'Acirc;' => "\xC3\x82", 'acirc;' => "\xC3\xA2", 'acute' => "\xC2\xB4", 'acute;' => "\xC2\xB4", 'AElig' => "\xC3\x86", 'aelig' => "\xC3\xA6", 'AElig;' => "\xC3\x86", 'aelig;' => "\xC3\xA6", 'Agrave' => "\xC3\x80", 'agrave' => "\xC3\xA0", 'Agrave;' => "\xC3\x80", 'agrave;' => "\xC3\xA0", 'alefsym;' => "\xE2\x84\xB5", 'Alpha;' => "\xCE\x91", 'alpha;' => "\xCE\xB1", 'AMP' => "\x26", 'amp' => "\x26", 'AMP;' => "\x26", 'amp;' => "\x26", 'and;' => "\xE2\x88\xA7", 'ang;' => "\xE2\x88\xA0", 'apos;' => "\x27", 'Aring' => "\xC3\x85", 'aring' => "\xC3\xA5", 'Aring;' => "\xC3\x85", 'aring;' => "\xC3\xA5", 'asymp;' => "\xE2\x89\x88", 'Atilde' => "\xC3\x83", 'atilde' => "\xC3\xA3", 'Atilde;' => "\xC3\x83", 'atilde;' => "\xC3\xA3", 'Auml' => "\xC3\x84", 'auml' => "\xC3\xA4", 'Auml;' => "\xC3\x84", 'auml;' => "\xC3\xA4", 'bdquo;' => "\xE2\x80\x9E", 'Beta;' => "\xCE\x92", 'beta;' => "\xCE\xB2", 'brvbar' => "\xC2\xA6", 'brvbar;' => "\xC2\xA6", 'bull;' => "\xE2\x80\xA2", 'cap;' => "\xE2\x88\xA9", 'Ccedil' => "\xC3\x87", 'ccedil' => "\xC3\xA7", 'Ccedil;' => "\xC3\x87", 'ccedil;' => "\xC3\xA7", 'cedil' => "\xC2\xB8", 'cedil;' => "\xC2\xB8", 'cent' => "\xC2\xA2", 'cent;' => "\xC2\xA2", 'Chi;' => "\xCE\xA7", 'chi;' => "\xCF\x87", 'circ;' => "\xCB\x86", 'clubs;' => "\xE2\x99\xA3", 'cong;' => "\xE2\x89\x85", 'COPY' => "\xC2\xA9", 'copy' => "\xC2\xA9", 'COPY;' => "\xC2\xA9", 'copy;' => "\xC2\xA9", 'crarr;' => "\xE2\x86\xB5", 'cup;' => "\xE2\x88\xAA", 'curren' => "\xC2\xA4", 'curren;' => "\xC2\xA4", 'Dagger;' => "\xE2\x80\xA1", 'dagger;' => "\xE2\x80\xA0", 'dArr;' => "\xE2\x87\x93", 'darr;' => "\xE2\x86\x93", 'deg' => "\xC2\xB0", 'deg;' => "\xC2\xB0", 'Delta;' => "\xCE\x94", 'delta;' => "\xCE\xB4", 'diams;' => "\xE2\x99\xA6", 'divide' => "\xC3\xB7", 'divide;' => "\xC3\xB7", 'Eacute' => "\xC3\x89", 'eacute' => "\xC3\xA9", 'Eacute;' => "\xC3\x89", 'eacute;' => "\xC3\xA9", 'Ecirc' => "\xC3\x8A", 'ecirc' => "\xC3\xAA", 'Ecirc;' => "\xC3\x8A", 'ecirc;' => "\xC3\xAA", 'Egrave' => "\xC3\x88", 'egrave' => "\xC3\xA8", 'Egrave;' => "\xC3\x88", 'egrave;' => "\xC3\xA8", 'empty;' => "\xE2\x88\x85", 'emsp;' => "\xE2\x80\x83", 'ensp;' => "\xE2\x80\x82", 'Epsilon;' => "\xCE\x95", 'epsilon;' => "\xCE\xB5", 'equiv;' => "\xE2\x89\xA1", 'Eta;' => "\xCE\x97", 'eta;' => "\xCE\xB7", 'ETH' => "\xC3\x90", 'eth' => "\xC3\xB0", 'ETH;' => "\xC3\x90", 'eth;' => "\xC3\xB0", 'Euml' => "\xC3\x8B", 'euml' => "\xC3\xAB", 'Euml;' => "\xC3\x8B", 'euml;' => "\xC3\xAB", 'euro;' => "\xE2\x82\xAC", 'exist;' => "\xE2\x88\x83", 'fnof;' => "\xC6\x92", 'forall;' => "\xE2\x88\x80", 'frac12' => "\xC2\xBD", 'frac12;' => "\xC2\xBD", 'frac14' => "\xC2\xBC", 'frac14;' => "\xC2\xBC", 'frac34' => "\xC2\xBE", 'frac34;' => "\xC2\xBE", 'frasl;' => "\xE2\x81\x84", 'Gamma;' => "\xCE\x93", 'gamma;' => "\xCE\xB3", 'ge;' => "\xE2\x89\xA5", 'GT' => "\x3E", 'gt' => "\x3E", 'GT;' => "\x3E", 'gt;' => "\x3E", 'hArr;' => "\xE2\x87\x94", 'harr;' => "\xE2\x86\x94", 'hearts;' => "\xE2\x99\xA5", 'hellip;' => "\xE2\x80\xA6", 'Iacute' => "\xC3\x8D", 'iacute' => "\xC3\xAD", 'Iacute;' => "\xC3\x8D", 'iacute;' => "\xC3\xAD", 'Icirc' => "\xC3\x8E", 'icirc' => "\xC3\xAE", 'Icirc;' => "\xC3\x8E", 'icirc;' => "\xC3\xAE", 'iexcl' => "\xC2\xA1", 'iexcl;' => "\xC2\xA1", 'Igrave' => "\xC3\x8C", 'igrave' => "\xC3\xAC", 'Igrave;' => "\xC3\x8C", 'igrave;' => "\xC3\xAC", 'image;' => "\xE2\x84\x91", 'infin;' => "\xE2\x88\x9E", 'int;' => "\xE2\x88\xAB", 'Iota;' => "\xCE\x99", 'iota;' => "\xCE\xB9", 'iquest' => "\xC2\xBF", 'iquest;' => "\xC2\xBF", 'isin;' => "\xE2\x88\x88", 'Iuml' => "\xC3\x8F", 'iuml' => "\xC3\xAF", 'Iuml;' => "\xC3\x8F", 'iuml;' => "\xC3\xAF", 'Kappa;' => "\xCE\x9A", 'kappa;' => "\xCE\xBA", 'Lambda;' => "\xCE\x9B", 'lambda;' => "\xCE\xBB", 'lang;' => "\xE3\x80\x88", 'laquo' => "\xC2\xAB", 'laquo;' => "\xC2\xAB", 'lArr;' => "\xE2\x87\x90", 'larr;' => "\xE2\x86\x90", 'lceil;' => "\xE2\x8C\x88", 'ldquo;' => "\xE2\x80\x9C", 'le;' => "\xE2\x89\xA4", 'lfloor;' => "\xE2\x8C\x8A", 'lowast;' => "\xE2\x88\x97", 'loz;' => "\xE2\x97\x8A", 'lrm;' => "\xE2\x80\x8E", 'lsaquo;' => "\xE2\x80\xB9", 'lsquo;' => "\xE2\x80\x98", 'LT' => "\x3C", 'lt' => "\x3C", 'LT;' => "\x3C", 'lt;' => "\x3C", 'macr' => "\xC2\xAF", 'macr;' => "\xC2\xAF", 'mdash;' => "\xE2\x80\x94", 'micro' => "\xC2\xB5", 'micro;' => "\xC2\xB5", 'middot' => "\xC2\xB7", 'middot;' => "\xC2\xB7", 'minus;' => "\xE2\x88\x92", 'Mu;' => "\xCE\x9C", 'mu;' => "\xCE\xBC", 'nabla;' => "\xE2\x88\x87", 'nbsp' => "\xC2\xA0", 'nbsp;' => "\xC2\xA0", 'ndash;' => "\xE2\x80\x93", 'ne;' => "\xE2\x89\xA0", 'ni;' => "\xE2\x88\x8B", 'not' => "\xC2\xAC", 'not;' => "\xC2\xAC", 'notin;' => "\xE2\x88\x89", 'nsub;' => "\xE2\x8A\x84", 'Ntilde' => "\xC3\x91", 'ntilde' => "\xC3\xB1", 'Ntilde;' => "\xC3\x91", 'ntilde;' => "\xC3\xB1", 'Nu;' => "\xCE\x9D", 'nu;' => "\xCE\xBD", 'Oacute' => "\xC3\x93", 'oacute' => "\xC3\xB3", 'Oacute;' => "\xC3\x93", 'oacute;' => "\xC3\xB3", 'Ocirc' => "\xC3\x94", 'ocirc' => "\xC3\xB4", 'Ocirc;' => "\xC3\x94", 'ocirc;' => "\xC3\xB4", 'OElig;' => "\xC5\x92", 'oelig;' => "\xC5\x93", 'Ograve' => "\xC3\x92", 'ograve' => "\xC3\xB2", 'Ograve;' => "\xC3\x92", 'ograve;' => "\xC3\xB2", 'oline;' => "\xE2\x80\xBE", 'Omega;' => "\xCE\xA9", 'omega;' => "\xCF\x89", 'Omicron;' => "\xCE\x9F", 'omicron;' => "\xCE\xBF", 'oplus;' => "\xE2\x8A\x95", 'or;' => "\xE2\x88\xA8", 'ordf' => "\xC2\xAA", 'ordf;' => "\xC2\xAA", 'ordm' => "\xC2\xBA", 'ordm;' => "\xC2\xBA", 'Oslash' => "\xC3\x98", 'oslash' => "\xC3\xB8", 'Oslash;' => "\xC3\x98", 'oslash;' => "\xC3\xB8", 'Otilde' => "\xC3\x95", 'otilde' => "\xC3\xB5", 'Otilde;' => "\xC3\x95", 'otilde;' => "\xC3\xB5", 'otimes;' => "\xE2\x8A\x97", 'Ouml' => "\xC3\x96", 'ouml' => "\xC3\xB6", 'Ouml;' => "\xC3\x96", 'ouml;' => "\xC3\xB6", 'para' => "\xC2\xB6", 'para;' => "\xC2\xB6", 'part;' => "\xE2\x88\x82", 'permil;' => "\xE2\x80\xB0", 'perp;' => "\xE2\x8A\xA5", 'Phi;' => "\xCE\xA6", 'phi;' => "\xCF\x86", 'Pi;' => "\xCE\xA0", 'pi;' => "\xCF\x80", 'piv;' => "\xCF\x96", 'plusmn' => "\xC2\xB1", 'plusmn;' => "\xC2\xB1", 'pound' => "\xC2\xA3", 'pound;' => "\xC2\xA3", 'Prime;' => "\xE2\x80\xB3", 'prime;' => "\xE2\x80\xB2", 'prod;' => "\xE2\x88\x8F", 'prop;' => "\xE2\x88\x9D", 'Psi;' => "\xCE\xA8", 'psi;' => "\xCF\x88", 'QUOT' => "\x22", 'quot' => "\x22", 'QUOT;' => "\x22", 'quot;' => "\x22", 'radic;' => "\xE2\x88\x9A", 'rang;' => "\xE3\x80\x89", 'raquo' => "\xC2\xBB", 'raquo;' => "\xC2\xBB", 'rArr;' => "\xE2\x87\x92", 'rarr;' => "\xE2\x86\x92", 'rceil;' => "\xE2\x8C\x89", 'rdquo;' => "\xE2\x80\x9D", 'real;' => "\xE2\x84\x9C", 'REG' => "\xC2\xAE", 'reg' => "\xC2\xAE", 'REG;' => "\xC2\xAE", 'reg;' => "\xC2\xAE", 'rfloor;' => "\xE2\x8C\x8B", 'Rho;' => "\xCE\xA1", 'rho;' => "\xCF\x81", 'rlm;' => "\xE2\x80\x8F", 'rsaquo;' => "\xE2\x80\xBA", 'rsquo;' => "\xE2\x80\x99", 'sbquo;' => "\xE2\x80\x9A", 'Scaron;' => "\xC5\xA0", 'scaron;' => "\xC5\xA1", 'sdot;' => "\xE2\x8B\x85", 'sect' => "\xC2\xA7", 'sect;' => "\xC2\xA7", 'shy' => "\xC2\xAD", 'shy;' => "\xC2\xAD", 'Sigma;' => "\xCE\xA3", 'sigma;' => "\xCF\x83", 'sigmaf;' => "\xCF\x82", 'sim;' => "\xE2\x88\xBC", 'spades;' => "\xE2\x99\xA0", 'sub;' => "\xE2\x8A\x82", 'sube;' => "\xE2\x8A\x86", 'sum;' => "\xE2\x88\x91", 'sup;' => "\xE2\x8A\x83", 'sup1' => "\xC2\xB9", 'sup1;' => "\xC2\xB9", 'sup2' => "\xC2\xB2", 'sup2;' => "\xC2\xB2", 'sup3' => "\xC2\xB3", 'sup3;' => "\xC2\xB3", 'supe;' => "\xE2\x8A\x87", 'szlig' => "\xC3\x9F", 'szlig;' => "\xC3\x9F", 'Tau;' => "\xCE\xA4", 'tau;' => "\xCF\x84", 'there4;' => "\xE2\x88\xB4", 'Theta;' => "\xCE\x98", 'theta;' => "\xCE\xB8", 'thetasym;' => "\xCF\x91", 'thinsp;' => "\xE2\x80\x89", 'THORN' => "\xC3\x9E", 'thorn' => "\xC3\xBE", 'THORN;' => "\xC3\x9E", 'thorn;' => "\xC3\xBE", 'tilde;' => "\xCB\x9C", 'times' => "\xC3\x97", 'times;' => "\xC3\x97", 'TRADE;' => "\xE2\x84\xA2", 'trade;' => "\xE2\x84\xA2", 'Uacute' => "\xC3\x9A", 'uacute' => "\xC3\xBA", 'Uacute;' => "\xC3\x9A", 'uacute;' => "\xC3\xBA", 'uArr;' => "\xE2\x87\x91", 'uarr;' => "\xE2\x86\x91", 'Ucirc' => "\xC3\x9B", 'ucirc' => "\xC3\xBB", 'Ucirc;' => "\xC3\x9B", 'ucirc;' => "\xC3\xBB", 'Ugrave' => "\xC3\x99", 'ugrave' => "\xC3\xB9", 'Ugrave;' => "\xC3\x99", 'ugrave;' => "\xC3\xB9", 'uml' => "\xC2\xA8", 'uml;' => "\xC2\xA8", 'upsih;' => "\xCF\x92", 'Upsilon;' => "\xCE\xA5", 'upsilon;' => "\xCF\x85", 'Uuml' => "\xC3\x9C", 'uuml' => "\xC3\xBC", 'Uuml;' => "\xC3\x9C", 'uuml;' => "\xC3\xBC", 'weierp;' => "\xE2\x84\x98", 'Xi;' => "\xCE\x9E", 'xi;' => "\xCE\xBE", 'Yacute' => "\xC3\x9D", 'yacute' => "\xC3\xBD", 'Yacute;' => "\xC3\x9D", 'yacute;' => "\xC3\xBD", 'yen' => "\xC2\xA5", 'yen;' => "\xC2\xA5", 'yuml' => "\xC3\xBF", 'Yuml;' => "\xC5\xB8", 'yuml;' => "\xC3\xBF", 'Zeta;' => "\xCE\x96", 'zeta;' => "\xCE\xB6", 'zwj;' => "\xE2\x80\x8D", 'zwnj;' => "\xE2\x80\x8C"); + + for ($i = 0, $match = null; $i < 9 && $this->consume() !== false; $i++) + { + $consumed = substr($this->consumed, 1); + if (isset($entities[$consumed])) + { + $match = $consumed; + } + } + + if ($match !== null) + { + $this->data = substr_replace($this->data, $entities[$match], $this->position - strlen($consumed) - 1, strlen($match) + 1); + $this->position += strlen($entities[$match]) - strlen($consumed) - 1; + } + break; + } + } +} + +/** + * IRI parser/serialiser + * + * @package SimplePie + */ +class SimplePie_IRI +{ + /** + * Scheme + * + * @access private + * @var string + */ + var $scheme; + + /** + * User Information + * + * @access private + * @var string + */ + var $userinfo; + + /** + * Host + * + * @access private + * @var string + */ + var $host; + + /** + * Port + * + * @access private + * @var string + */ + var $port; + + /** + * Path + * + * @access private + * @var string + */ + var $path; + + /** + * Query + * + * @access private + * @var string + */ + var $query; + + /** + * Fragment + * + * @access private + * @var string + */ + var $fragment; + + /** + * Whether the object represents a valid IRI + * + * @access private + * @var array + */ + var $valid = array(); + + /** + * Return the entire IRI when you try and read the object as a string + * + * @access public + * @return string + */ + function __toString() + { + return $this->get_iri(); + } + + /** + * Create a new IRI object, from a specified string + * + * @access public + * @param string $iri + * @return SimplePie_IRI + */ + function SimplePie_IRI($iri) + { + $iri = (string) $iri; + if ($iri !== '') + { + $parsed = $this->parse_iri($iri); + $this->set_scheme($parsed['scheme']); + $this->set_authority($parsed['authority']); + $this->set_path($parsed['path']); + $this->set_query($parsed['query']); + $this->set_fragment($parsed['fragment']); + } + } + + /** + * Create a new IRI object by resolving a relative IRI + * + * @static + * @access public + * @param SimplePie_IRI $base Base IRI + * @param string $relative Relative IRI + * @return SimplePie_IRI + */ + function absolutize($base, $relative) + { + $relative = (string) $relative; + if ($relative !== '') + { + $relative = new SimplePie_IRI($relative); + if ($relative->get_scheme() !== null) + { + $target = $relative; + } + elseif ($base->get_iri() !== null) + { + if ($relative->get_authority() !== null) + { + $target = $relative; + $target->set_scheme($base->get_scheme()); + } + else + { + $target = new SimplePie_IRI(''); + $target->set_scheme($base->get_scheme()); + $target->set_userinfo($base->get_userinfo()); + $target->set_host($base->get_host()); + $target->set_port($base->get_port()); + if ($relative->get_path() !== null) + { + if (strpos($relative->get_path(), '/') === 0) + { + $target->set_path($relative->get_path()); + } + elseif (($base->get_userinfo() !== null || $base->get_host() !== null || $base->get_port() !== null) && $base->get_path() === null) + { + $target->set_path('/' . $relative->get_path()); + } + elseif (($last_segment = strrpos($base->get_path(), '/')) !== false) + { + $target->set_path(substr($base->get_path(), 0, $last_segment + 1) . $relative->get_path()); + } + else + { + $target->set_path($relative->get_path()); + } + $target->set_query($relative->get_query()); + } + else + { + $target->set_path($base->get_path()); + if ($relative->get_query() !== null) + { + $target->set_query($relative->get_query()); + } + elseif ($base->get_query() !== null) + { + $target->set_query($base->get_query()); + } + } + } + $target->set_fragment($relative->get_fragment()); + } + else + { + // No base URL, just return the relative URL + $target = $relative; + } + } + else + { + $target = $base; + } + return $target; + } + + /** + * Parse an IRI into scheme/authority/path/query/fragment segments + * + * @access private + * @param string $iri + * @return array + */ + function parse_iri($iri) + { + preg_match('/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/', $iri, $match); + for ($i = count($match); $i <= 9; $i++) + { + $match[$i] = ''; + } + return array('scheme' => $match[2], 'authority' => $match[4], 'path' => $match[5], 'query' => $match[7], 'fragment' => $match[9]); + } + + /** + * Remove dot segments from a path + * + * @access private + * @param string $input + * @return string + */ + function remove_dot_segments($input) + { + $output = ''; + while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') + { + // A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise, + if (strpos($input, '../') === 0) + { + $input = substr($input, 3); + } + elseif (strpos($input, './') === 0) + { + $input = substr($input, 2); + } + // B: if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise, + elseif (strpos($input, '/./') === 0) + { + $input = substr_replace($input, '/', 0, 3); + } + elseif ($input === '/.') + { + $input = '/'; + } + // C: if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise, + elseif (strpos($input, '/../') === 0) + { + $input = substr_replace($input, '/', 0, 4); + $output = substr_replace($output, '', strrpos($output, '/')); + } + elseif ($input === '/..') + { + $input = '/'; + $output = substr_replace($output, '', strrpos($output, '/')); + } + // D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise, + elseif ($input === '.' || $input === '..') + { + $input = ''; + } + // E: move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer + elseif (($pos = strpos($input, '/', 1)) !== false) + { + $output .= substr($input, 0, $pos); + $input = substr_replace($input, '', 0, $pos); + } + else + { + $output .= $input; + $input = ''; + } + } + return $output . $input; + } + + /** + * Replace invalid character with percent encoding + * + * @param string $string Input string + * @param string $valid_chars Valid characters + * @param int $case Normalise case + * @return string + */ + function replace_invalid_with_pct_encoding($string, $valid_chars, $case = SIMPLEPIE_SAME_CASE, $iprivate = false) + { + // Normalize as many pct-encoded sections as possible + $string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array(&$this, 'remove_iunreserved_percent_encoded'), $string); + + // Replace invalid percent characters + $string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string); + + // Add unreserved and % to $valid_chars (the latter is safe because all + // pct-encoded sections are now valid). + $valid_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%'; + + // Now replace any bytes that aren't allowed with their pct-encoded versions + $position = 0; + $strlen = strlen($string); + while (($position += strspn($string, $valid_chars, $position)) < $strlen) + { + $value = ord($string[$position]); + + // Start position + $start = $position; + + // By default we are valid + $valid = true; + + // No one byte sequences are valid due to the while. + // Two byte sequence: + if (($value & 0xE0) === 0xC0) + { + $character = ($value & 0x1F) << 6; + $length = 2; + $remaining = 1; + } + // Three byte sequence: + elseif (($value & 0xF0) === 0xE0) + { + $character = ($value & 0x0F) << 12; + $length = 3; + $remaining = 2; + } + // Four byte sequence: + elseif (($value & 0xF8) === 0xF0) + { + $character = ($value & 0x07) << 18; + $length = 4; + $remaining = 3; + } + // Invalid byte: + else + { + $valid = false; + $length = 1; + $remaining = 0; + } + + if ($remaining) + { + if ($position + $length <= $strlen) + { + for ($position++; $remaining; $position++) + { + $value = ord($string[$position]); + + // Check that the byte is valid, then add it to the character: + if (($value & 0xC0) === 0x80) + { + $character |= ($value & 0x3F) << (--$remaining * 6); + } + // If it is invalid, count the sequence as invalid and reprocess the current byte: + else + { + $valid = false; + $position--; + break; + } + } + } + else + { + $position = $strlen - 1; + $valid = false; + } + } + + // Percent encode anything invalid or not in ucschar + if ( + // Invalid sequences + !$valid + // Non-shortest form sequences are invalid + || $length > 1 && $character <= 0x7F + || $length > 2 && $character <= 0x7FF + || $length > 3 && $character <= 0xFFFF + // Outside of range of ucschar codepoints + // Noncharacters + || ($character & 0xFFFE) === 0xFFFE + || $character >= 0xFDD0 && $character <= 0xFDEF + || ( + // Everything else not in ucschar + $character > 0xD7FF && $character < 0xF900 + || $character < 0xA0 + || $character > 0xEFFFD + ) + && ( + // Everything not in iprivate, if it applies + !$iprivate + || $character < 0xE000 + || $character > 0x10FFFD + ) + ) + { + // If we were a character, pretend we weren't, but rather an error. + if ($valid) + $position--; + + for ($j = $start; $j <= $position; $j++) + { + $string = substr_replace($string, sprintf('%%%02X', ord($string[$j])), $j, 1); + $j += 2; + $position += 2; + $strlen += 2; + } + } + } + + // Normalise case + if ($case & SIMPLEPIE_LOWERCASE) + { + $string = strtolower($string); + } + elseif ($case & SIMPLEPIE_UPPERCASE) + { + $string = strtoupper($string); + } + + return $string; + } + + /** + * Callback function for preg_replace_callback. + * + * Removes sequences of percent encoded bytes that represent UTF-8 + * encoded characters in iunreserved + * + * @access private + * @param array $match PCRE match + * @return string Replacement + */ + function remove_iunreserved_percent_encoded($match) + { + // As we just have valid percent encoded sequences we can just explode + // and ignore the first member of the returned array (an empty string). + $bytes = explode('%', $match[0]); + + // Initialize the new string (this is what will be returned) and that + // there are no bytes remaining in the current sequence (unsurprising + // at the first byte!). + $string = ''; + $remaining = 0; + + // Loop over each and every byte, and set $value to its value + for ($i = 1, $len = count($bytes); $i < $len; $i++) + { + $value = hexdec($bytes[$i]); + + // If we're the first byte of sequence: + if (!$remaining) + { + // Start position + $start = $i; + + // By default we are valid + $valid = true; + + // One byte sequence: + if ($value <= 0x7F) + { + $character = $value; + $length = 1; + } + // Two byte sequence: + elseif (($value & 0xE0) === 0xC0) + { + $character = ($value & 0x1F) << 6; + $length = 2; + $remaining = 1; + } + // Three byte sequence: + elseif (($value & 0xF0) === 0xE0) + { + $character = ($value & 0x0F) << 12; + $length = 3; + $remaining = 2; + } + // Four byte sequence: + elseif (($value & 0xF8) === 0xF0) + { + $character = ($value & 0x07) << 18; + $length = 4; + $remaining = 3; + } + // Invalid byte: + else + { + $valid = false; + $remaining = 0; + } + } + // Continuation byte: + else + { + // Check that the byte is valid, then add it to the character: + if (($value & 0xC0) === 0x80) + { + $remaining--; + $character |= ($value & 0x3F) << ($remaining * 6); + } + // If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence: + else + { + $valid = false; + $remaining = 0; + $i--; + } + } + + // If we've reached the end of the current byte sequence, append it to Unicode::$data + if (!$remaining) + { + // Percent encode anything invalid or not in iunreserved + if ( + // Invalid sequences + !$valid + // Non-shortest form sequences are invalid + || $length > 1 && $character <= 0x7F + || $length > 2 && $character <= 0x7FF + || $length > 3 && $character <= 0xFFFF + // Outside of range of iunreserved codepoints + || $character < 0x2D + || $character > 0xEFFFD + // Noncharacters + || ($character & 0xFFFE) === 0xFFFE + || $character >= 0xFDD0 && $character <= 0xFDEF + // Everything else not in iunreserved (this is all BMP) + || $character === 0x2F + || $character > 0x39 && $character < 0x41 + || $character > 0x5A && $character < 0x61 + || $character > 0x7A && $character < 0x7E + || $character > 0x7E && $character < 0xA0 + || $character > 0xD7FF && $character < 0xF900 + ) + { + for ($j = $start; $j <= $i; $j++) + { + $string .= '%' . strtoupper($bytes[$j]); + } + } + else + { + for ($j = $start; $j <= $i; $j++) + { + $string .= chr(hexdec($bytes[$j])); + } + } + } + } + + // If we have any bytes left over they are invalid (i.e., we are + // mid-way through a multi-byte sequence) + if ($remaining) + { + for ($j = $start; $j < $len; $j++) + { + $string .= '%' . strtoupper($bytes[$j]); + } + } + + return $string; + } + + /** + * Check if the object represents a valid IRI + * + * @access public + * @return bool + */ + function is_valid() + { + return array_sum($this->valid) === count($this->valid); + } + + /** + * Set the scheme. Returns true on success, false on failure (if there are + * any invalid characters). + * + * @access public + * @param string $scheme + * @return bool + */ + function set_scheme($scheme) + { + if ($scheme === null || $scheme === '') + { + $this->scheme = null; + } + else + { + $len = strlen($scheme); + switch (true) + { + case $len > 1: + if (!strspn($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-.', 1)) + { + $this->scheme = null; + $this->valid[__FUNCTION__] = false; + return false; + } + + case $len > 0: + if (!strspn($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 0, 1)) + { + $this->scheme = null; + $this->valid[__FUNCTION__] = false; + return false; + } + } + $this->scheme = strtolower($scheme); + } + $this->valid[__FUNCTION__] = true; + return true; + } + + /** + * Set the authority. Returns true on success, false on failure (if there are + * any invalid characters). + * + * @access public + * @param string $authority + * @return bool + */ + function set_authority($authority) + { + if (($userinfo_end = strrpos($authority, '@')) !== false) + { + $userinfo = substr($authority, 0, $userinfo_end); + $authority = substr($authority, $userinfo_end + 1); + } + else + { + $userinfo = null; + } + + if (($port_start = strpos($authority, ':')) !== false) + { + $port = substr($authority, $port_start + 1); + $authority = substr($authority, 0, $port_start); + } + else + { + $port = null; + } + + return $this->set_userinfo($userinfo) && $this->set_host($authority) && $this->set_port($port); + } + + /** + * Set the userinfo. + * + * @access public + * @param string $userinfo + * @return bool + */ + function set_userinfo($userinfo) + { + if ($userinfo === null || $userinfo === '') + { + $this->userinfo = null; + } + else + { + $this->userinfo = $this->replace_invalid_with_pct_encoding($userinfo, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=:'); + } + $this->valid[__FUNCTION__] = true; + return true; + } + + /** + * Set the host. Returns true on success, false on failure (if there are + * any invalid characters). + * + * @access public + * @param string $host + * @return bool + */ + function set_host($host) + { + if ($host === null || $host === '') + { + $this->host = null; + $this->valid[__FUNCTION__] = true; + return true; + } + elseif ($host[0] === '[' && substr($host, -1) === ']') + { + if (Net_IPv6::checkIPv6(substr($host, 1, -1))) + { + $this->host = $host; + $this->valid[__FUNCTION__] = true; + return true; + } + else + { + $this->host = null; + $this->valid[__FUNCTION__] = false; + return false; + } + } + else + { + $this->host = $this->replace_invalid_with_pct_encoding($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=', SIMPLEPIE_LOWERCASE); + $this->valid[__FUNCTION__] = true; + return true; + } + } + + /** + * Set the port. Returns true on success, false on failure (if there are + * any invalid characters). + * + * @access public + * @param string $port + * @return bool + */ + function set_port($port) + { + if ($port === null || $port === '') + { + $this->port = null; + $this->valid[__FUNCTION__] = true; + return true; + } + elseif (strspn($port, '0123456789') === strlen($port)) + { + $this->port = (int) $port; + $this->valid[__FUNCTION__] = true; + return true; + } + else + { + $this->port = null; + $this->valid[__FUNCTION__] = false; + return false; + } + } + + /** + * Set the path. + * + * @access public + * @param string $path + * @return bool + */ + function set_path($path) + { + if ($path === null || $path === '') + { + $this->path = null; + $this->valid[__FUNCTION__] = true; + return true; + } + elseif (substr($path, 0, 2) === '//' && $this->userinfo === null && $this->host === null && $this->port === null) + { + $this->path = null; + $this->valid[__FUNCTION__] = false; + return false; + } + else + { + $this->path = $this->replace_invalid_with_pct_encoding($path, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=@/'); + if ($this->scheme !== null) + { + $this->path = $this->remove_dot_segments($this->path); + } + $this->valid[__FUNCTION__] = true; + return true; + } + } + + /** + * Set the query. + * + * @access public + * @param string $query + * @return bool + */ + function set_query($query) + { + if ($query === null || $query === '') + { + $this->query = null; + } + else + { + $this->query = $this->replace_invalid_with_pct_encoding($query, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$\'()*+,;:@/?&='); + } + $this->valid[__FUNCTION__] = true; + return true; + } + + /** + * Set the fragment. + * + * @access public + * @param string $fragment + * @return bool + */ + function set_fragment($fragment) + { + if ($fragment === null || $fragment === '') + { + $this->fragment = null; + } + else + { + $this->fragment = $this->replace_invalid_with_pct_encoding($fragment, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=:@/?'); + } + $this->valid[__FUNCTION__] = true; + return true; + } + + /** + * Get the complete IRI + * + * @access public + * @return string + */ + function get_iri() + { + $iri = ''; + if ($this->scheme !== null) + { + $iri .= $this->scheme . ':'; + } + if (($authority = $this->get_authority()) !== null) + { + $iri .= '//' . $authority; + } + if ($this->path !== null) + { + $iri .= $this->path; + } + if ($this->query !== null) + { + $iri .= '?' . $this->query; + } + if ($this->fragment !== null) + { + $iri .= '#' . $this->fragment; + } + + if ($iri !== '') + { + return $iri; + } + else + { + return null; + } + } + + /** + * Get the scheme + * + * @access public + * @return string + */ + function get_scheme() + { + return $this->scheme; + } + + /** + * Get the complete authority + * + * @access public + * @return string + */ + function get_authority() + { + $authority = ''; + if ($this->userinfo !== null) + { + $authority .= $this->userinfo . '@'; + } + if ($this->host !== null) + { + $authority .= $this->host; + } + if ($this->port !== null) + { + $authority .= ':' . $this->port; + } + + if ($authority !== '') + { + return $authority; + } + else + { + return null; + } + } + + /** + * Get the user information + * + * @access public + * @return string + */ + function get_userinfo() + { + return $this->userinfo; + } + + /** + * Get the host + * + * @access public + * @return string + */ + function get_host() + { + return $this->host; + } + + /** + * Get the port + * + * @access public + * @return string + */ + function get_port() + { + return $this->port; + } + + /** + * Get the path + * + * @access public + * @return string + */ + function get_path() + { + return $this->path; + } + + /** + * Get the query + * + * @access public + * @return string + */ + function get_query() + { + return $this->query; + } + + /** + * Get the fragment + * + * @access public + * @return string + */ + function get_fragment() + { + return $this->fragment; + } +} + +/** + * Class to validate and to work with IPv6 addresses. + * + * @package SimplePie + * @copyright 2003-2005 The PHP Group + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/package/Net_IPv6 + * @author Alexander Merz + * @author elfrink at introweb dot nl + * @author Josh Peck + * @author Geoffrey Sneddon + */ +class SimplePie_Net_IPv6 +{ + /** + * Removes a possible existing netmask specification of an IP address. + * + * @param string $ip the (compressed) IP as Hex representation + * @return string the IP the without netmask + * @since 1.1.0 + * @access public + * @static + */ + function removeNetmaskSpec($ip) + { + if (strpos($ip, '/') !== false) + { + list($addr, $nm) = explode('/', $ip); + } + else + { + $addr = $ip; + } + return $addr; + } + + /** + * Uncompresses an IPv6 address + * + * RFC 2373 allows you to compress zeros in an address to '::'. This + * function expects an valid IPv6 address and expands the '::' to + * the required zeros. + * + * Example: FF01::101 -> FF01:0:0:0:0:0:0:101 + * ::1 -> 0:0:0:0:0:0:0:1 + * + * @access public + * @static + * @param string $ip a valid IPv6-address (hex format) + * @return string the uncompressed IPv6-address (hex format) + */ + function Uncompress($ip) + { + $uip = SimplePie_Net_IPv6::removeNetmaskSpec($ip); + $c1 = -1; + $c2 = -1; + if (strpos($ip, '::') !== false) + { + list($ip1, $ip2) = explode('::', $ip); + if ($ip1 === '') + { + $c1 = -1; + } + else + { + $pos = 0; + if (($pos = substr_count($ip1, ':')) > 0) + { + $c1 = $pos; + } + else + { + $c1 = 0; + } + } + if ($ip2 === '') + { + $c2 = -1; + } + else + { + $pos = 0; + if (($pos = substr_count($ip2, ':')) > 0) + { + $c2 = $pos; + } + else + { + $c2 = 0; + } + } + if (strstr($ip2, '.')) + { + $c2++; + } + // :: + if ($c1 === -1 && $c2 === -1) + { + $uip = '0:0:0:0:0:0:0:0'; + } + // ::xxx + else if ($c1 === -1) + { + $fill = str_repeat('0:', 7 - $c2); + $uip = str_replace('::', $fill, $uip); + } + // xxx:: + else if ($c2 === -1) + { + $fill = str_repeat(':0', 7 - $c1); + $uip = str_replace('::', $fill, $uip); + } + // xxx::xxx + else + { + $fill = str_repeat(':0:', 6 - $c2 - $c1); + $uip = str_replace('::', $fill, $uip); + $uip = str_replace('::', ':', $uip); + } + } + return $uip; + } + + /** + * Splits an IPv6 address into the IPv6 and a possible IPv4 part + * + * RFC 2373 allows you to note the last two parts of an IPv6 address as + * an IPv4 compatible address + * + * Example: 0:0:0:0:0:0:13.1.68.3 + * 0:0:0:0:0:FFFF:129.144.52.38 + * + * @access public + * @static + * @param string $ip a valid IPv6-address (hex format) + * @return array [0] contains the IPv6 part, [1] the IPv4 part (hex format) + */ + function SplitV64($ip) + { + $ip = SimplePie_Net_IPv6::Uncompress($ip); + if (strstr($ip, '.')) + { + $pos = strrpos($ip, ':'); + $ip[$pos] = '_'; + $ipPart = explode('_', $ip); + return $ipPart; + } + else + { + return array($ip, ''); + } + } + + /** + * Checks an IPv6 address + * + * Checks if the given IP is IPv6-compatible + * + * @access public + * @static + * @param string $ip a valid IPv6-address + * @return bool true if $ip is an IPv6 address + */ + function checkIPv6($ip) + { + $ipPart = SimplePie_Net_IPv6::SplitV64($ip); + $count = 0; + if (!empty($ipPart[0])) + { + $ipv6 = explode(':', $ipPart[0]); + for ($i = 0; $i < count($ipv6); $i++) + { + $dec = hexdec($ipv6[$i]); + $hex = strtoupper(preg_replace('/^[0]{1,3}(.*[0-9a-fA-F])$/', '\\1', $ipv6[$i])); + if ($ipv6[$i] >= 0 && $dec <= 65535 && $hex === strtoupper(dechex($dec))) + { + $count++; + } + } + if ($count === 8) + { + return true; + } + elseif ($count === 6 && !empty($ipPart[1])) + { + $ipv4 = explode('.', $ipPart[1]); + $count = 0; + foreach ($ipv4 as $ipv4_part) + { + if ($ipv4_part >= 0 && $ipv4_part <= 255 && preg_match('/^\d{1,3}$/', $ipv4_part)) + { + $count++; + } + } + if ($count === 4) + { + return true; + } + } + else + { + return false; + } + + } + else + { + return false; + } + } +} + +/** + * Date Parser + * + * @package SimplePie + */ +class SimplePie_Parse_Date +{ + /** + * Input data + * + * @access protected + * @var string + */ + var $date; + + /** + * List of days, calendar day name => ordinal day number in the week + * + * @access protected + * @var array + */ + var $day = array( + // English + 'mon' => 1, + 'monday' => 1, + 'tue' => 2, + 'tuesday' => 2, + 'wed' => 3, + 'wednesday' => 3, + 'thu' => 4, + 'thursday' => 4, + 'fri' => 5, + 'friday' => 5, + 'sat' => 6, + 'saturday' => 6, + 'sun' => 7, + 'sunday' => 7, + // Dutch + 'maandag' => 1, + 'dinsdag' => 2, + 'woensdag' => 3, + 'donderdag' => 4, + 'vrijdag' => 5, + 'zaterdag' => 6, + 'zondag' => 7, + // French + 'lundi' => 1, + 'mardi' => 2, + 'mercredi' => 3, + 'jeudi' => 4, + 'vendredi' => 5, + 'samedi' => 6, + 'dimanche' => 7, + // German + 'montag' => 1, + 'dienstag' => 2, + 'mittwoch' => 3, + 'donnerstag' => 4, + 'freitag' => 5, + 'samstag' => 6, + 'sonnabend' => 6, + 'sonntag' => 7, + // Italian + 'lunedì' => 1, + 'martedì' => 2, + 'mercoledì' => 3, + 'giovedì' => 4, + 'venerdì' => 5, + 'sabato' => 6, + 'domenica' => 7, + // Spanish + 'lunes' => 1, + 'martes' => 2, + 'miércoles' => 3, + 'jueves' => 4, + 'viernes' => 5, + 'sábado' => 6, + 'domingo' => 7, + // Finnish + 'maanantai' => 1, + 'tiistai' => 2, + 'keskiviikko' => 3, + 'torstai' => 4, + 'perjantai' => 5, + 'lauantai' => 6, + 'sunnuntai' => 7, + // Hungarian + 'hétfő' => 1, + 'kedd' => 2, + 'szerda' => 3, + 'csütörtok' => 4, + 'péntek' => 5, + 'szombat' => 6, + 'vasárnap' => 7, + // Greek + 'Δευ' => 1, + 'Τρι' => 2, + 'Τετ' => 3, + 'Πεμ' => 4, + 'Παρ' => 5, + 'Σαβ' => 6, + 'Κυρ' => 7, + ); + + /** + * List of months, calendar month name => calendar month number + * + * @access protected + * @var array + */ + var $month = array( + // English + 'jan' => 1, + 'january' => 1, + 'feb' => 2, + 'february' => 2, + 'mar' => 3, + 'march' => 3, + 'apr' => 4, + 'april' => 4, + 'may' => 5, + // No long form of May + 'jun' => 6, + 'june' => 6, + 'jul' => 7, + 'july' => 7, + 'aug' => 8, + 'august' => 8, + 'sep' => 9, + 'september' => 8, + 'oct' => 10, + 'october' => 10, + 'nov' => 11, + 'november' => 11, + 'dec' => 12, + 'december' => 12, + // Dutch + 'januari' => 1, + 'februari' => 2, + 'maart' => 3, + 'april' => 4, + 'mei' => 5, + 'juni' => 6, + 'juli' => 7, + 'augustus' => 8, + 'september' => 9, + 'oktober' => 10, + 'november' => 11, + 'december' => 12, + // French + 'janvier' => 1, + 'février' => 2, + 'mars' => 3, + 'avril' => 4, + 'mai' => 5, + 'juin' => 6, + 'juillet' => 7, + 'août' => 8, + 'septembre' => 9, + 'octobre' => 10, + 'novembre' => 11, + 'décembre' => 12, + // German + 'januar' => 1, + 'februar' => 2, + 'märz' => 3, + 'april' => 4, + 'mai' => 5, + 'juni' => 6, + 'juli' => 7, + 'august' => 8, + 'september' => 9, + 'oktober' => 10, + 'november' => 11, + 'dezember' => 12, + // Italian + 'gennaio' => 1, + 'febbraio' => 2, + 'marzo' => 3, + 'aprile' => 4, + 'maggio' => 5, + 'giugno' => 6, + 'luglio' => 7, + 'agosto' => 8, + 'settembre' => 9, + 'ottobre' => 10, + 'novembre' => 11, + 'dicembre' => 12, + // Spanish + 'enero' => 1, + 'febrero' => 2, + 'marzo' => 3, + 'abril' => 4, + 'mayo' => 5, + 'junio' => 6, + 'julio' => 7, + 'agosto' => 8, + 'septiembre' => 9, + 'setiembre' => 9, + 'octubre' => 10, + 'noviembre' => 11, + 'diciembre' => 12, + // Finnish + 'tammikuu' => 1, + 'helmikuu' => 2, + 'maaliskuu' => 3, + 'huhtikuu' => 4, + 'toukokuu' => 5, + 'kesäkuu' => 6, + 'heinäkuu' => 7, + 'elokuu' => 8, + 'suuskuu' => 9, + 'lokakuu' => 10, + 'marras' => 11, + 'joulukuu' => 12, + // Hungarian + 'január' => 1, + 'február' => 2, + 'március' => 3, + 'április' => 4, + 'május' => 5, + 'június' => 6, + 'július' => 7, + 'augusztus' => 8, + 'szeptember' => 9, + 'október' => 10, + 'november' => 11, + 'december' => 12, + // Greek + 'Ιαν' => 1, + 'Φεβ' => 2, + 'Μάώ' => 3, + 'Μαώ' => 3, + 'Απρ' => 4, + 'Μάι' => 5, + 'Μαϊ' => 5, + 'Μαι' => 5, + 'Ιούν' => 6, + 'Ιον' => 6, + 'Ιούλ' => 7, + 'Ιολ' => 7, + 'Αύγ' => 8, + 'Αυγ' => 8, + 'Σεπ' => 9, + 'Οκτ' => 10, + 'Νοέ' => 11, + 'Δεκ' => 12, + ); + + /** + * List of timezones, abbreviation => offset from UTC + * + * @access protected + * @var array + */ + var $timezone = array( + 'ACDT' => 37800, + 'ACIT' => 28800, + 'ACST' => 34200, + 'ACT' => -18000, + 'ACWDT' => 35100, + 'ACWST' => 31500, + 'AEDT' => 39600, + 'AEST' => 36000, + 'AFT' => 16200, + 'AKDT' => -28800, + 'AKST' => -32400, + 'AMDT' => 18000, + 'AMT' => -14400, + 'ANAST' => 46800, + 'ANAT' => 43200, + 'ART' => -10800, + 'AZOST' => -3600, + 'AZST' => 18000, + 'AZT' => 14400, + 'BIOT' => 21600, + 'BIT' => -43200, + 'BOT' => -14400, + 'BRST' => -7200, + 'BRT' => -10800, + 'BST' => 3600, + 'BTT' => 21600, + 'CAST' => 18000, + 'CAT' => 7200, + 'CCT' => 23400, + 'CDT' => -18000, + 'CEDT' => 7200, + 'CET' => 3600, + 'CGST' => -7200, + 'CGT' => -10800, + 'CHADT' => 49500, + 'CHAST' => 45900, + 'CIST' => -28800, + 'CKT' => -36000, + 'CLDT' => -10800, + 'CLST' => -14400, + 'COT' => -18000, + 'CST' => -21600, + 'CVT' => -3600, + 'CXT' => 25200, + 'DAVT' => 25200, + 'DTAT' => 36000, + 'EADT' => -18000, + 'EAST' => -21600, + 'EAT' => 10800, + 'ECT' => -18000, + 'EDT' => -14400, + 'EEST' => 10800, + 'EET' => 7200, + 'EGT' => -3600, + 'EKST' => 21600, + 'EST' => -18000, + 'FJT' => 43200, + 'FKDT' => -10800, + 'FKST' => -14400, + 'FNT' => -7200, + 'GALT' => -21600, + 'GEDT' => 14400, + 'GEST' => 10800, + 'GFT' => -10800, + 'GILT' => 43200, + 'GIT' => -32400, + 'GST' => 14400, + 'GST' => -7200, + 'GYT' => -14400, + 'HAA' => -10800, + 'HAC' => -18000, + 'HADT' => -32400, + 'HAE' => -14400, + 'HAP' => -25200, + 'HAR' => -21600, + 'HAST' => -36000, + 'HAT' => -9000, + 'HAY' => -28800, + 'HKST' => 28800, + 'HMT' => 18000, + 'HNA' => -14400, + 'HNC' => -21600, + 'HNE' => -18000, + 'HNP' => -28800, + 'HNR' => -25200, + 'HNT' => -12600, + 'HNY' => -32400, + 'IRDT' => 16200, + 'IRKST' => 32400, + 'IRKT' => 28800, + 'IRST' => 12600, + 'JFDT' => -10800, + 'JFST' => -14400, + 'JST' => 32400, + 'KGST' => 21600, + 'KGT' => 18000, + 'KOST' => 39600, + 'KOVST' => 28800, + 'KOVT' => 25200, + 'KRAST' => 28800, + 'KRAT' => 25200, + 'KST' => 32400, + 'LHDT' => 39600, + 'LHST' => 37800, + 'LINT' => 50400, + 'LKT' => 21600, + 'MAGST' => 43200, + 'MAGT' => 39600, + 'MAWT' => 21600, + 'MDT' => -21600, + 'MESZ' => 7200, + 'MEZ' => 3600, + 'MHT' => 43200, + 'MIT' => -34200, + 'MNST' => 32400, + 'MSDT' => 14400, + 'MSST' => 10800, + 'MST' => -25200, + 'MUT' => 14400, + 'MVT' => 18000, + 'MYT' => 28800, + 'NCT' => 39600, + 'NDT' => -9000, + 'NFT' => 41400, + 'NMIT' => 36000, + 'NOVST' => 25200, + 'NOVT' => 21600, + 'NPT' => 20700, + 'NRT' => 43200, + 'NST' => -12600, + 'NUT' => -39600, + 'NZDT' => 46800, + 'NZST' => 43200, + 'OMSST' => 25200, + 'OMST' => 21600, + 'PDT' => -25200, + 'PET' => -18000, + 'PETST' => 46800, + 'PETT' => 43200, + 'PGT' => 36000, + 'PHOT' => 46800, + 'PHT' => 28800, + 'PKT' => 18000, + 'PMDT' => -7200, + 'PMST' => -10800, + 'PONT' => 39600, + 'PST' => -28800, + 'PWT' => 32400, + 'PYST' => -10800, + 'PYT' => -14400, + 'RET' => 14400, + 'ROTT' => -10800, + 'SAMST' => 18000, + 'SAMT' => 14400, + 'SAST' => 7200, + 'SBT' => 39600, + 'SCDT' => 46800, + 'SCST' => 43200, + 'SCT' => 14400, + 'SEST' => 3600, + 'SGT' => 28800, + 'SIT' => 28800, + 'SRT' => -10800, + 'SST' => -39600, + 'SYST' => 10800, + 'SYT' => 7200, + 'TFT' => 18000, + 'THAT' => -36000, + 'TJT' => 18000, + 'TKT' => -36000, + 'TMT' => 18000, + 'TOT' => 46800, + 'TPT' => 32400, + 'TRUT' => 36000, + 'TVT' => 43200, + 'TWT' => 28800, + 'UYST' => -7200, + 'UYT' => -10800, + 'UZT' => 18000, + 'VET' => -14400, + 'VLAST' => 39600, + 'VLAT' => 36000, + 'VOST' => 21600, + 'VUT' => 39600, + 'WAST' => 7200, + 'WAT' => 3600, + 'WDT' => 32400, + 'WEST' => 3600, + 'WFT' => 43200, + 'WIB' => 25200, + 'WIT' => 32400, + 'WITA' => 28800, + 'WKST' => 18000, + 'WST' => 28800, + 'YAKST' => 36000, + 'YAKT' => 32400, + 'YAPT' => 36000, + 'YEKST' => 21600, + 'YEKT' => 18000, + ); + + /** + * Cached PCRE for SimplePie_Parse_Date::$day + * + * @access protected + * @var string + */ + var $day_pcre; + + /** + * Cached PCRE for SimplePie_Parse_Date::$month + * + * @access protected + * @var string + */ + var $month_pcre; + + /** + * Array of user-added callback methods + * + * @access private + * @var array + */ + var $built_in = array(); + + /** + * Array of user-added callback methods + * + * @access private + * @var array + */ + var $user = array(); + + /** + * Create new SimplePie_Parse_Date object, and set self::day_pcre, + * self::month_pcre, and self::built_in + * + * @access private + */ + function SimplePie_Parse_Date() + { + $this->day_pcre = '(' . implode(array_keys($this->day), '|') . ')'; + $this->month_pcre = '(' . implode(array_keys($this->month), '|') . ')'; + + static $cache; + if (!isset($cache[get_class($this)])) + { + $all_methods = get_class_methods($this); + + foreach ($all_methods as $method) + { + if (strtolower(substr($method, 0, 5)) === 'date_') + { + $cache[get_class($this)][] = $method; + } + } + } + + foreach ($cache[get_class($this)] as $method) + { + $this->built_in[] = $method; + } + } + + /** + * Get the object + * + * @access public + */ + function get() + { + static $object; + if (!$object) + { + $object = new SimplePie_Parse_Date; + } + return $object; + } + + /** + * Parse a date + * + * @final + * @access public + * @param string $date Date to parse + * @return int Timestamp corresponding to date string, or false on failure + */ + function parse($date) + { + foreach ($this->user as $method) + { + if (($returned = call_user_func($method, $date)) !== false) + { + return $returned; + } + } + + foreach ($this->built_in as $method) + { + if (($returned = call_user_func(array(&$this, $method), $date)) !== false) + { + return $returned; + } + } + + return false; + } + + /** + * Add a callback method to parse a date + * + * @final + * @access public + * @param callback $callback + */ + function add_callback($callback) + { + if (is_callable($callback)) + { + $this->user[] = $callback; + } + else + { + trigger_error('User-supplied function must be a valid callback', E_USER_WARNING); + } + } + + /** + * Parse a superset of W3C-DTF (allows hyphens and colons to be omitted, as + * well as allowing any of upper or lower case "T", horizontal tabs, or + * spaces to be used as the time seperator (including more than one)) + * + * @access protected + * @return int Timestamp + */ + function date_w3cdtf($date) + { + static $pcre; + if (!$pcre) + { + $year = '([0-9]{4})'; + $month = $day = $hour = $minute = $second = '([0-9]{2})'; + $decimal = '([0-9]*)'; + $zone = '(?:(Z)|([+\-])([0-9]{1,2}):?([0-9]{1,2}))'; + $pcre = '/^' . $year . '(?:-?' . $month . '(?:-?' . $day . '(?:[Tt\x09\x20]+' . $hour . '(?::?' . $minute . '(?::?' . $second . '(?:.' . $decimal . ')?)?)?' . $zone . ')?)?)?$/'; + } + if (preg_match($pcre, $date, $match)) + { + /* + Capturing subpatterns: + 1: Year + 2: Month + 3: Day + 4: Hour + 5: Minute + 6: Second + 7: Decimal fraction of a second + 8: Zulu + 9: Timezone ± + 10: Timezone hours + 11: Timezone minutes + */ + + // Fill in empty matches + for ($i = count($match); $i <= 3; $i++) + { + $match[$i] = '1'; + } + + for ($i = count($match); $i <= 7; $i++) + { + $match[$i] = '0'; + } + + // Numeric timezone + if (isset($match[9]) && $match[9] !== '') + { + $timezone = $match[10] * 3600; + $timezone += $match[11] * 60; + if ($match[9] === '-') + { + $timezone = 0 - $timezone; + } + } + else + { + $timezone = 0; + } + + // Convert the number of seconds to an integer, taking decimals into account + $second = round($match[6] + $match[7] / pow(10, strlen($match[7]))); + + return gmmktime($match[4], $match[5], $second, $match[2], $match[3], $match[1]) - $timezone; + } + else + { + return false; + } + } + + /** + * Remove RFC822 comments + * + * @access protected + * @param string $data Data to strip comments from + * @return string Comment stripped string + */ + function remove_rfc2822_comments($string) + { + $string = (string) $string; + $position = 0; + $length = strlen($string); + $depth = 0; + + $output = ''; + + while ($position < $length && ($pos = strpos($string, '(', $position)) !== false) + { + $output .= substr($string, $position, $pos - $position); + $position = $pos + 1; + if ($string[$pos - 1] !== '\\') + { + $depth++; + while ($depth && $position < $length) + { + $position += strcspn($string, '()', $position); + if ($string[$position - 1] === '\\') + { + $position++; + continue; + } + elseif (isset($string[$position])) + { + switch ($string[$position]) + { + case '(': + $depth++; + break; + + case ')': + $depth--; + break; + } + $position++; + } + else + { + break; + } + } + } + else + { + $output .= '('; + } + } + $output .= substr($string, $position); + + return $output; + } + + /** + * Parse RFC2822's date format + * + * @access protected + * @return int Timestamp + */ + function date_rfc2822($date) + { + static $pcre; + if (!$pcre) + { + $wsp = '[\x09\x20]'; + $fws = '(?:' . $wsp . '+|' . $wsp . '*(?:\x0D\x0A' . $wsp . '+)+)'; + $optional_fws = $fws . '?'; + $day_name = $this->day_pcre; + $month = $this->month_pcre; + $day = '([0-9]{1,2})'; + $hour = $minute = $second = '([0-9]{2})'; + $year = '([0-9]{2,4})'; + $num_zone = '([+\-])([0-9]{2})([0-9]{2})'; + $character_zone = '([A-Z]{1,5})'; + $zone = '(?:' . $num_zone . '|' . $character_zone . ')'; + $pcre = '/(?:' . $optional_fws . $day_name . $optional_fws . ',)?' . $optional_fws . $day . $fws . $month . $fws . $year . $fws . $hour . $optional_fws . ':' . $optional_fws . $minute . '(?:' . $optional_fws . ':' . $optional_fws . $second . ')?' . $fws . $zone . '/i'; + } + if (preg_match($pcre, $this->remove_rfc2822_comments($date), $match)) + { + /* + Capturing subpatterns: + 1: Day name + 2: Day + 3: Month + 4: Year + 5: Hour + 6: Minute + 7: Second + 8: Timezone ± + 9: Timezone hours + 10: Timezone minutes + 11: Alphabetic timezone + */ + + // Find the month number + $month = $this->month[strtolower($match[3])]; + + // Numeric timezone + if ($match[8] !== '') + { + $timezone = $match[9] * 3600; + $timezone += $match[10] * 60; + if ($match[8] === '-') + { + $timezone = 0 - $timezone; + } + } + // Character timezone + elseif (isset($this->timezone[strtoupper($match[11])])) + { + $timezone = $this->timezone[strtoupper($match[11])]; + } + // Assume everything else to be -0000 + else + { + $timezone = 0; + } + + // Deal with 2/3 digit years + if ($match[4] < 50) + { + $match[4] += 2000; + } + elseif ($match[4] < 1000) + { + $match[4] += 1900; + } + + // Second is optional, if it is empty set it to zero + if ($match[7] !== '') + { + $second = $match[7]; + } + else + { + $second = 0; + } + + return gmmktime($match[5], $match[6], $second, $month, $match[2], $match[4]) - $timezone; + } + else + { + return false; + } + } + + /** + * Parse RFC850's date format + * + * @access protected + * @return int Timestamp + */ + function date_rfc850($date) + { + static $pcre; + if (!$pcre) + { + $space = '[\x09\x20]+'; + $day_name = $this->day_pcre; + $month = $this->month_pcre; + $day = '([0-9]{1,2})'; + $year = $hour = $minute = $second = '([0-9]{2})'; + $zone = '([A-Z]{1,5})'; + $pcre = '/^' . $day_name . ',' . $space . $day . '-' . $month . '-' . $year . $space . $hour . ':' . $minute . ':' . $second . $space . $zone . '$/i'; + } + if (preg_match($pcre, $date, $match)) + { + /* + Capturing subpatterns: + 1: Day name + 2: Day + 3: Month + 4: Year + 5: Hour + 6: Minute + 7: Second + 8: Timezone + */ + + // Month + $month = $this->month[strtolower($match[3])]; + + // Character timezone + if (isset($this->timezone[strtoupper($match[8])])) + { + $timezone = $this->timezone[strtoupper($match[8])]; + } + // Assume everything else to be -0000 + else + { + $timezone = 0; + } + + // Deal with 2 digit year + if ($match[4] < 50) + { + $match[4] += 2000; + } + else + { + $match[4] += 1900; + } + + return gmmktime($match[5], $match[6], $match[7], $month, $match[2], $match[4]) - $timezone; + } + else + { + return false; + } + } + + /** + * Parse C99's asctime()'s date format + * + * @access protected + * @return int Timestamp + */ + function date_asctime($date) + { + static $pcre; + if (!$pcre) + { + $space = '[\x09\x20]+'; + $wday_name = $this->day_pcre; + $mon_name = $this->month_pcre; + $day = '([0-9]{1,2})'; + $hour = $sec = $min = '([0-9]{2})'; + $year = '([0-9]{4})'; + $terminator = '\x0A?\x00?'; + $pcre = '/^' . $wday_name . $space . $mon_name . $space . $day . $space . $hour . ':' . $min . ':' . $sec . $space . $year . $terminator . '$/i'; + } + if (preg_match($pcre, $date, $match)) + { + /* + Capturing subpatterns: + 1: Day name + 2: Month + 3: Day + 4: Hour + 5: Minute + 6: Second + 7: Year + */ + + $month = $this->month[strtolower($match[2])]; + return gmmktime($match[4], $match[5], $match[6], $month, $match[3], $match[7]); + } + else + { + return false; + } + } + + /** + * Parse dates using strtotime() + * + * @access protected + * @return int Timestamp + */ + function date_strtotime($date) + { + $strtotime = strtotime($date); + if ($strtotime === -1 || $strtotime === false) + { + return false; + } + else + { + return $strtotime; + } + } +} + +/** + * Content-type sniffing + * + * @package SimplePie + */ +class SimplePie_Content_Type_Sniffer +{ + /** + * File object + * + * @var SimplePie_File + * @access private + */ + var $file; + + /** + * Create an instance of the class with the input file + * + * @access public + * @param SimplePie_Content_Type_Sniffer $file Input file + */ + function SimplePie_Content_Type_Sniffer($file) + { + $this->file = $file; + } + + /** + * Get the Content-Type of the specified file + * + * @access public + * @return string Actual Content-Type + */ + function get_type() + { + if (isset($this->file->headers['content-type'])) + { + if (!isset($this->file->headers['content-encoding']) + && ($this->file->headers['content-type'] === 'text/plain' + || $this->file->headers['content-type'] === 'text/plain; charset=ISO-8859-1' + || $this->file->headers['content-type'] === 'text/plain; charset=iso-8859-1')) + { + return $this->text_or_binary(); + } + + if (($pos = strpos($this->file->headers['content-type'], ';')) !== false) + { + $official = substr($this->file->headers['content-type'], 0, $pos); + } + else + { + $official = $this->file->headers['content-type']; + } + $official = strtolower($official); + + if ($official === 'unknown/unknown' + || $official === 'application/unknown') + { + return $this->unknown(); + } + elseif (substr($official, -4) === '+xml' + || $official === 'text/xml' + || $official === 'application/xml') + { + return $official; + } + elseif (substr($official, 0, 6) === 'image/') + { + if ($return = $this->image()) + { + return $return; + } + else + { + return $official; + } + } + elseif ($official === 'text/html') + { + return $this->feed_or_html(); + } + else + { + return $official; + } + } + else + { + return $this->unknown(); + } + } + + /** + * Sniff text or binary + * + * @access private + * @return string Actual Content-Type + */ + function text_or_binary() + { + if (substr($this->file->body, 0, 2) === "\xFE\xFF" + || substr($this->file->body, 0, 2) === "\xFF\xFE" + || substr($this->file->body, 0, 4) === "\x00\x00\xFE\xFF" + || substr($this->file->body, 0, 3) === "\xEF\xBB\xBF") + { + return 'text/plain'; + } + elseif (preg_match('/[\x00-\x08\x0E-\x1A\x1C-\x1F]/', $this->file->body)) + { + return 'application/octect-stream'; + } + else + { + return 'text/plain'; + } + } + + /** + * Sniff unknown + * + * @access private + * @return string Actual Content-Type + */ + function unknown() + { + $ws = strspn($this->file->body, "\x09\x0A\x0B\x0C\x0D\x20"); + if (strtolower(substr($this->file->body, $ws, 14)) === 'file->body, $ws, 5)) === 'file->body, $ws, 7)) === 'file->body, 0, 5) === '%PDF-') + { + return 'application/pdf'; + } + elseif (substr($this->file->body, 0, 11) === '%!PS-Adobe-') + { + return 'application/postscript'; + } + elseif (substr($this->file->body, 0, 6) === 'GIF87a' + || substr($this->file->body, 0, 6) === 'GIF89a') + { + return 'image/gif'; + } + elseif (substr($this->file->body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") + { + return 'image/png'; + } + elseif (substr($this->file->body, 0, 3) === "\xFF\xD8\xFF") + { + return 'image/jpeg'; + } + elseif (substr($this->file->body, 0, 2) === "\x42\x4D") + { + return 'image/bmp'; + } + else + { + return $this->text_or_binary(); + } + } + + /** + * Sniff images + * + * @access private + * @return string Actual Content-Type + */ + function image() + { + if (substr($this->file->body, 0, 6) === 'GIF87a' + || substr($this->file->body, 0, 6) === 'GIF89a') + { + return 'image/gif'; + } + elseif (substr($this->file->body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") + { + return 'image/png'; + } + elseif (substr($this->file->body, 0, 3) === "\xFF\xD8\xFF") + { + return 'image/jpeg'; + } + elseif (substr($this->file->body, 0, 2) === "\x42\x4D") + { + return 'image/bmp'; + } + else + { + return false; + } + } + + /** + * Sniff HTML + * + * @access private + * @return string Actual Content-Type + */ + function feed_or_html() + { + $len = strlen($this->file->body); + $pos = strspn($this->file->body, "\x09\x0A\x0D\x20"); + + while ($pos < $len) + { + switch ($this->file->body[$pos]) + { + case "\x09": + case "\x0A": + case "\x0D": + case "\x20": + $pos += strspn($this->file->body, "\x09\x0A\x0D\x20", $pos); + continue 2; + + case '<': + $pos++; + break; + + default: + return 'text/html'; + } + + if (substr($this->file->body, $pos, 3) === '!--') + { + $pos += 3; + if ($pos < $len && ($pos = strpos($this->file->body, '-->', $pos)) !== false) + { + $pos += 3; + } + else + { + return 'text/html'; + } + } + elseif (substr($this->file->body, $pos, 1) === '!') + { + if ($pos < $len && ($pos = strpos($this->file->body, '>', $pos)) !== false) + { + $pos++; + } + else + { + return 'text/html'; + } + } + elseif (substr($this->file->body, $pos, 1) === '?') + { + if ($pos < $len && ($pos = strpos($this->file->body, '?>', $pos)) !== false) + { + $pos += 2; + } + else + { + return 'text/html'; + } + } + elseif (substr($this->file->body, $pos, 3) === 'rss' + || substr($this->file->body, $pos, 7) === 'rdf:RDF') + { + return 'application/rss+xml'; + } + elseif (substr($this->file->body, $pos, 4) === 'feed') + { + return 'application/atom+xml'; + } + else + { + return 'text/html'; + } + } + + return 'text/html'; + } +} + +/** + * Parses the XML Declaration + * + * @package SimplePie + */ +class SimplePie_XML_Declaration_Parser +{ + /** + * XML Version + * + * @access public + * @var string + */ + var $version = '1.0'; + + /** + * Encoding + * + * @access public + * @var string + */ + var $encoding = 'UTF-8'; + + /** + * Standalone + * + * @access public + * @var bool + */ + var $standalone = false; + + /** + * Current state of the state machine + * + * @access private + * @var string + */ + var $state = 'before_version_name'; + + /** + * Input data + * + * @access private + * @var string + */ + var $data = ''; + + /** + * Input data length (to avoid calling strlen() everytime this is needed) + * + * @access private + * @var int + */ + var $data_length = 0; + + /** + * Current position of the pointer + * + * @var int + * @access private + */ + var $position = 0; + + /** + * Create an instance of the class with the input data + * + * @access public + * @param string $data Input data + */ + function SimplePie_XML_Declaration_Parser($data) + { + $this->data = $data; + $this->data_length = strlen($this->data); + } + + /** + * Parse the input data + * + * @access public + * @return bool true on success, false on failure + */ + function parse() + { + while ($this->state && $this->state !== 'emit' && $this->has_data()) + { + $state = $this->state; + $this->$state(); + } + $this->data = ''; + if ($this->state === 'emit') + { + return true; + } + else + { + $this->version = ''; + $this->encoding = ''; + $this->standalone = ''; + return false; + } + } + + /** + * Check whether there is data beyond the pointer + * + * @access private + * @return bool true if there is further data, false if not + */ + function has_data() + { + return (bool) ($this->position < $this->data_length); + } + + /** + * Advance past any whitespace + * + * @return int Number of whitespace characters passed + */ + function skip_whitespace() + { + $whitespace = strspn($this->data, "\x09\x0A\x0D\x20", $this->position); + $this->position += $whitespace; + return $whitespace; + } + + /** + * Read value + */ + function get_value() + { + $quote = substr($this->data, $this->position, 1); + if ($quote === '"' || $quote === "'") + { + $this->position++; + $len = strcspn($this->data, $quote, $this->position); + if ($this->has_data()) + { + $value = substr($this->data, $this->position, $len); + $this->position += $len + 1; + return $value; + } + } + return false; + } + + function before_version_name() + { + if ($this->skip_whitespace()) + { + $this->state = 'version_name'; + } + else + { + $this->state = false; + } + } + + function version_name() + { + if (substr($this->data, $this->position, 7) === 'version') + { + $this->position += 7; + $this->skip_whitespace(); + $this->state = 'version_equals'; + } + else + { + $this->state = false; + } + } + + function version_equals() + { + if (substr($this->data, $this->position, 1) === '=') + { + $this->position++; + $this->skip_whitespace(); + $this->state = 'version_value'; + } + else + { + $this->state = false; + } + } + + function version_value() + { + if ($this->version = $this->get_value()) + { + $this->skip_whitespace(); + if ($this->has_data()) + { + $this->state = 'encoding_name'; + } + else + { + $this->state = 'emit'; + } + } + else + { + $this->state = false; + } + } + + function encoding_name() + { + if (substr($this->data, $this->position, 8) === 'encoding') + { + $this->position += 8; + $this->skip_whitespace(); + $this->state = 'encoding_equals'; + } + else + { + $this->state = 'standalone_name'; + } + } + + function encoding_equals() + { + if (substr($this->data, $this->position, 1) === '=') + { + $this->position++; + $this->skip_whitespace(); + $this->state = 'encoding_value'; + } + else + { + $this->state = false; + } + } + + function encoding_value() + { + if ($this->encoding = $this->get_value()) + { + $this->skip_whitespace(); + if ($this->has_data()) + { + $this->state = 'standalone_name'; + } + else + { + $this->state = 'emit'; + } + } + else + { + $this->state = false; + } + } + + function standalone_name() + { + if (substr($this->data, $this->position, 10) === 'standalone') + { + $this->position += 10; + $this->skip_whitespace(); + $this->state = 'standalone_equals'; + } + else + { + $this->state = false; + } + } + + function standalone_equals() + { + if (substr($this->data, $this->position, 1) === '=') + { + $this->position++; + $this->skip_whitespace(); + $this->state = 'standalone_value'; + } + else + { + $this->state = false; + } + } + + function standalone_value() + { + if ($standalone = $this->get_value()) + { + switch ($standalone) + { + case 'yes': + $this->standalone = true; + break; + + case 'no': + $this->standalone = false; + break; + + default: + $this->state = false; + return; + } + + $this->skip_whitespace(); + if ($this->has_data()) + { + $this->state = false; + } + else + { + $this->state = 'emit'; + } + } + else + { + $this->state = false; + } + } +} + +class SimplePie_Locator +{ + var $useragent; + var $timeout; + var $file; + var $local = array(); + var $elsewhere = array(); + var $file_class = 'SimplePie_File'; + var $cached_entities = array(); + var $http_base; + var $base; + var $base_location = 0; + var $checked_feeds = 0; + var $max_checked_feeds = 10; + var $content_type_sniffer_class = 'SimplePie_Content_Type_Sniffer'; + + function SimplePie_Locator(&$file, $timeout = 10, $useragent = null, $file_class = 'SimplePie_File', $max_checked_feeds = 10, $content_type_sniffer_class = 'SimplePie_Content_Type_Sniffer') + { + $this->file =& $file; + $this->file_class = $file_class; + $this->useragent = $useragent; + $this->timeout = $timeout; + $this->max_checked_feeds = $max_checked_feeds; + $this->content_type_sniffer_class = $content_type_sniffer_class; + } + + function find($type = SIMPLEPIE_LOCATOR_ALL, &$working) + { + if ($this->is_feed($this->file)) + { + return $this->file; + } + + if ($this->file->method & SIMPLEPIE_FILE_SOURCE_REMOTE) + { + $sniffer = new $this->content_type_sniffer_class($this->file); + if ($sniffer->get_type() !== 'text/html') + { + return null; + } + } + + if ($type & ~SIMPLEPIE_LOCATOR_NONE) + { + $this->get_base(); + } + + if ($type & SIMPLEPIE_LOCATOR_AUTODISCOVERY && $working = $this->autodiscovery()) + { + return $working[0]; + } + + if ($type & (SIMPLEPIE_LOCATOR_LOCAL_EXTENSION | SIMPLEPIE_LOCATOR_LOCAL_BODY | SIMPLEPIE_LOCATOR_REMOTE_EXTENSION | SIMPLEPIE_LOCATOR_REMOTE_BODY) && $this->get_links()) + { + if ($type & SIMPLEPIE_LOCATOR_LOCAL_EXTENSION && $working = $this->extension($this->local)) + { + return $working; + } + + if ($type & SIMPLEPIE_LOCATOR_LOCAL_BODY && $working = $this->body($this->local)) + { + return $working; + } + + if ($type & SIMPLEPIE_LOCATOR_REMOTE_EXTENSION && $working = $this->extension($this->elsewhere)) + { + return $working; + } + + if ($type & SIMPLEPIE_LOCATOR_REMOTE_BODY && $working = $this->body($this->elsewhere)) + { + return $working; + } + } + return null; + } + + function is_feed(&$file) + { + if ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE) + { + $sniffer = new $this->content_type_sniffer_class($file); + $sniffed = $sniffer->get_type(); + if (in_array($sniffed, array('application/rss+xml', 'application/rdf+xml', 'text/rdf', 'application/atom+xml', 'text/xml', 'application/xml'))) + { + return true; + } + else + { + return false; + } + } + elseif ($file->method & SIMPLEPIE_FILE_SOURCE_LOCAL) + { + return true; + } + else + { + return false; + } + } + + function get_base() + { + $this->http_base = $this->file->url; + $this->base = $this->http_base; + $elements = SimplePie_Misc::get_element('base', $this->file->body); + foreach ($elements as $element) + { + if ($element['attribs']['href']['data'] !== '') + { + $this->base = SimplePie_Misc::absolutize_url(trim($element['attribs']['href']['data']), $this->http_base); + $this->base_location = $element['offset']; + break; + } + } + } + + function autodiscovery() + { + $links = array_merge(SimplePie_Misc::get_element('link', $this->file->body), SimplePie_Misc::get_element('a', $this->file->body), SimplePie_Misc::get_element('area', $this->file->body)); + $done = array(); + $feeds = array(); + foreach ($links as $link) + { + if ($this->checked_feeds === $this->max_checked_feeds) + { + break; + } + if (isset($link['attribs']['href']['data']) && isset($link['attribs']['rel']['data'])) + { + $rel = array_unique(SimplePie_Misc::space_seperated_tokens(strtolower($link['attribs']['rel']['data']))); + + if ($this->base_location < $link['offset']) + { + $href = SimplePie_Misc::absolutize_url(trim($link['attribs']['href']['data']), $this->base); + } + else + { + $href = SimplePie_Misc::absolutize_url(trim($link['attribs']['href']['data']), $this->http_base); + } + + if (!in_array($href, $done) && in_array('feed', $rel) || (in_array('alternate', $rel) && !empty($link['attribs']['type']['data']) && in_array(strtolower(SimplePie_Misc::parse_mime($link['attribs']['type']['data'])), array('application/rss+xml', 'application/atom+xml'))) && !isset($feeds[$href])) + { + $this->checked_feeds++; + $feed = new $this->file_class($href, $this->timeout, 5, null, $this->useragent); + if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed)) + { + $feeds[$href] = $feed; + } + } + $done[] = $href; + } + } + + if (!empty($feeds)) + { + return array_values($feeds); + } + else { + return null; + } + } + + function get_links() + { + $links = SimplePie_Misc::get_element('a', $this->file->body); + foreach ($links as $link) + { + if (isset($link['attribs']['href']['data'])) + { + $href = trim($link['attribs']['href']['data']); + $parsed = SimplePie_Misc::parse_url($href); + if ($parsed['scheme'] === '' || preg_match('/^(http(s)|feed)?$/i', $parsed['scheme'])) + { + if ($this->base_location < $link['offset']) + { + $href = SimplePie_Misc::absolutize_url(trim($link['attribs']['href']['data']), $this->base); + } + else + { + $href = SimplePie_Misc::absolutize_url(trim($link['attribs']['href']['data']), $this->http_base); + } + + $current = SimplePie_Misc::parse_url($this->file->url); + + if ($parsed['authority'] === '' || $parsed['authority'] === $current['authority']) + { + $this->local[] = $href; + } + else + { + $this->elsewhere[] = $href; + } + } + } + } + $this->local = array_unique($this->local); + $this->elsewhere = array_unique($this->elsewhere); + if (!empty($this->local) || !empty($this->elsewhere)) + { + return true; + } + return null; + } + + function extension(&$array) + { + foreach ($array as $key => $value) + { + if ($this->checked_feeds === $this->max_checked_feeds) + { + break; + } + if (in_array(strtolower(strrchr($value, '.')), array('.rss', '.rdf', '.atom', '.xml'))) + { + $this->checked_feeds++; + $feed = new $this->file_class($value, $this->timeout, 5, null, $this->useragent); + if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed)) + { + return $feed; + } + else + { + unset($array[$key]); + } + } + } + return null; + } + + function body(&$array) + { + foreach ($array as $key => $value) + { + if ($this->checked_feeds === $this->max_checked_feeds) + { + break; + } + if (preg_match('/(rss|rdf|atom|xml)/i', $value)) + { + $this->checked_feeds++; + $feed = new $this->file_class($value, $this->timeout, 5, null, $this->useragent); + if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed)) + { + return $feed; + } + else + { + unset($array[$key]); + } + } + } + return null; + } +} + +class SimplePie_Parser +{ + var $error_code; + var $error_string; + var $current_line; + var $current_column; + var $current_byte; + var $separator = ' '; + var $namespace = array(''); + var $element = array(''); + var $xml_base = array(''); + var $xml_base_explicit = array(false); + var $xml_lang = array(''); + var $data = array(); + var $datas = array(array()); + var $current_xhtml_construct = -1; + var $encoding; + + function parse(&$data, $encoding) + { + // Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character + if (strtoupper($encoding) === 'US-ASCII') + { + $this->encoding = 'UTF-8'; + } + else + { + $this->encoding = $encoding; + } + + // Strip BOM: + // UTF-32 Big Endian BOM + if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") + { + $data = substr($data, 4); + } + // UTF-32 Little Endian BOM + elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") + { + $data = substr($data, 4); + } + // UTF-16 Big Endian BOM + elseif (substr($data, 0, 2) === "\xFE\xFF") + { + $data = substr($data, 2); + } + // UTF-16 Little Endian BOM + elseif (substr($data, 0, 2) === "\xFF\xFE") + { + $data = substr($data, 2); + } + // UTF-8 BOM + elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") + { + $data = substr($data, 3); + } + + if (substr($data, 0, 5) === '')) !== false) + { + $declaration = new SimplePie_XML_Declaration_Parser(substr($data, 5, $pos - 5)); + if ($declaration->parse()) + { + $data = substr($data, $pos + 2); + $data = 'version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' . $data; + } + else + { + $this->error_string = 'SimplePie bug! Please report this!'; + return false; + } + } + + $return = true; + + static $xml_is_sane = null; + if ($xml_is_sane === null) + { + $parser_check = xml_parser_create(); + xml_parse_into_struct($parser_check, '&', $values); + xml_parser_free($parser_check); + $xml_is_sane = isset($values[0]['value']); + } + + // Create the parser + if ($xml_is_sane) + { + $xml = xml_parser_create_ns($this->encoding, $this->separator); + xml_parser_set_option($xml, XML_OPTION_SKIP_WHITE, 1); + xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, 0); + xml_set_object($xml, $this); + xml_set_character_data_handler($xml, 'cdata'); + xml_set_element_handler($xml, 'tag_open', 'tag_close'); + + // Parse! + if (!xml_parse($xml, $data, true)) + { + $this->error_code = xml_get_error_code($xml); + $this->error_string = xml_error_string($this->error_code); + $return = false; + } + $this->current_line = xml_get_current_line_number($xml); + $this->current_column = xml_get_current_column_number($xml); + $this->current_byte = xml_get_current_byte_index($xml); + xml_parser_free($xml); + return $return; + } + else + { + libxml_clear_errors(); + $xml = new XMLReader(); + $xml->xml($data); + while (@$xml->read()) + { + switch ($xml->nodeType) + { + + case constant('XMLReader::END_ELEMENT'): + if ($xml->namespaceURI !== '') + { + $tagName = $xml->namespaceURI . $this->separator . $xml->localName; + } + else + { + $tagName = $xml->localName; + } + $this->tag_close(null, $tagName); + break; + case constant('XMLReader::ELEMENT'): + $empty = $xml->isEmptyElement; + if ($xml->namespaceURI !== '') + { + $tagName = $xml->namespaceURI . $this->separator . $xml->localName; + } + else + { + $tagName = $xml->localName; + } + $attributes = array(); + while ($xml->moveToNextAttribute()) + { + if ($xml->namespaceURI !== '') + { + $attrName = $xml->namespaceURI . $this->separator . $xml->localName; + } + else + { + $attrName = $xml->localName; + } + $attributes[$attrName] = $xml->value; + } + $this->tag_open(null, $tagName, $attributes); + if ($empty) + { + $this->tag_close(null, $tagName); + } + break; + case constant('XMLReader::TEXT'): + + case constant('XMLReader::CDATA'): + $this->cdata(null, $xml->value); + break; + } + } + if ($error = libxml_get_last_error()) + { + $this->error_code = $error->code; + $this->error_string = $error->message; + $this->current_line = $error->line; + $this->current_column = $error->column; + return false; + } + else + { + return true; + } + } + } + + function get_error_code() + { + return $this->error_code; + } + + function get_error_string() + { + return $this->error_string; + } + + function get_current_line() + { + return $this->current_line; + } + + function get_current_column() + { + return $this->current_column; + } + + function get_current_byte() + { + return $this->current_byte; + } + + function get_data() + { + return $this->data; + } + + function tag_open($parser, $tag, $attributes) + { + list($this->namespace[], $this->element[]) = $this->split_ns($tag); + + $attribs = array(); + foreach ($attributes as $name => $value) + { + list($attrib_namespace, $attribute) = $this->split_ns($name); + $attribs[$attrib_namespace][$attribute] = $value; + } + + if (isset($attribs[SIMPLEPIE_NAMESPACE_XML]['base'])) + { + $this->xml_base[] = SimplePie_Misc::absolutize_url($attribs[SIMPLEPIE_NAMESPACE_XML]['base'], end($this->xml_base)); + $this->xml_base_explicit[] = true; + } + else + { + $this->xml_base[] = end($this->xml_base); + $this->xml_base_explicit[] = end($this->xml_base_explicit); + } + + if (isset($attribs[SIMPLEPIE_NAMESPACE_XML]['lang'])) + { + $this->xml_lang[] = $attribs[SIMPLEPIE_NAMESPACE_XML]['lang']; + } + else + { + $this->xml_lang[] = end($this->xml_lang); + } + + if ($this->current_xhtml_construct >= 0) + { + $this->current_xhtml_construct++; + if (end($this->namespace) === SIMPLEPIE_NAMESPACE_XHTML) + { + $this->data['data'] .= '<' . end($this->element); + if (isset($attribs[''])) + { + foreach ($attribs[''] as $name => $value) + { + $this->data['data'] .= ' ' . $name . '="' . htmlspecialchars($value, ENT_COMPAT, $this->encoding) . '"'; + } + } + $this->data['data'] .= '>'; + } + } + else + { + $this->datas[] =& $this->data; + $this->data =& $this->data['child'][end($this->namespace)][end($this->element)][]; + $this->data = array('data' => '', 'attribs' => $attribs, 'xml_base' => end($this->xml_base), 'xml_base_explicit' => end($this->xml_base_explicit), 'xml_lang' => end($this->xml_lang)); + if ((end($this->namespace) === SIMPLEPIE_NAMESPACE_ATOM_03 && in_array(end($this->element), array('title', 'tagline', 'copyright', 'info', 'summary', 'content')) && isset($attribs['']['mode']) && $attribs['']['mode'] === 'xml') + || (end($this->namespace) === SIMPLEPIE_NAMESPACE_ATOM_10 && in_array(end($this->element), array('rights', 'subtitle', 'summary', 'info', 'title', 'content')) && isset($attribs['']['type']) && $attribs['']['type'] === 'xhtml')) + { + $this->current_xhtml_construct = 0; + } + } + } + + function cdata($parser, $cdata) + { + if ($this->current_xhtml_construct >= 0) + { + $this->data['data'] .= htmlspecialchars($cdata, ENT_QUOTES, $this->encoding); + } + else + { + $this->data['data'] .= $cdata; + } + } + + function tag_close($parser, $tag) + { + if ($this->current_xhtml_construct >= 0) + { + $this->current_xhtml_construct--; + if (end($this->namespace) === SIMPLEPIE_NAMESPACE_XHTML && !in_array(end($this->element), array('area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param'))) + { + $this->data['data'] .= 'element) . '>'; + } + } + if ($this->current_xhtml_construct === -1) + { + $this->data =& $this->datas[count($this->datas) - 1]; + array_pop($this->datas); + } + + array_pop($this->element); + array_pop($this->namespace); + array_pop($this->xml_base); + array_pop($this->xml_base_explicit); + array_pop($this->xml_lang); + } + + function split_ns($string) + { + static $cache = array(); + if (!isset($cache[$string])) + { + if ($pos = strpos($string, $this->separator)) + { + static $separator_length; + if (!$separator_length) + { + $separator_length = strlen($this->separator); + } + $namespace = substr($string, 0, $pos); + $local_name = substr($string, $pos + $separator_length); + if (strtolower($namespace) === SIMPLEPIE_NAMESPACE_ITUNES) + { + $namespace = SIMPLEPIE_NAMESPACE_ITUNES; + } + + // Normalize the Media RSS namespaces + if ($namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG) + { + $namespace = SIMPLEPIE_NAMESPACE_MEDIARSS; + } + $cache[$string] = array($namespace, $local_name); + } + else + { + $cache[$string] = array('', $string); + } + } + return $cache[$string]; + } +} + +/** + * @todo Move to using an actual HTML parser (this will allow tags to be properly stripped, and to switch between HTML and XHTML), this will also make it easier to shorten a string while preserving HTML tags + */ +class SimplePie_Sanitize +{ + // Private vars + var $base; + + // Options + var $remove_div = true; + var $image_handler = ''; + var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'); + var $encode_instead_of_strip = false; + var $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'); + var $strip_comments = false; + var $output_encoding = 'UTF-8'; + var $enable_cache = true; + var $cache_location = './cache'; + var $cache_name_function = 'md5'; + var $cache_class = 'SimplePie_Cache'; + var $file_class = 'SimplePie_File'; + var $timeout = 10; + var $useragent = ''; + var $force_fsockopen = false; + + var $replace_url_attributes = array( + 'a' => 'href', + 'area' => 'href', + 'blockquote' => 'cite', + 'del' => 'cite', + 'form' => 'action', + 'img' => array('longdesc', 'src'), + 'input' => 'src', + 'ins' => 'cite', + 'q' => 'cite' + ); + + function remove_div($enable = true) + { + $this->remove_div = (bool) $enable; + } + + function set_image_handler($page = false) + { + if ($page) + { + $this->image_handler = (string) $page; + } + else + { + $this->image_handler = false; + } + } + + function pass_cache_data($enable_cache = true, $cache_location = './cache', $cache_name_function = 'md5', $cache_class = 'SimplePie_Cache') + { + if (isset($enable_cache)) + { + $this->enable_cache = (bool) $enable_cache; + } + + if ($cache_location) + { + $this->cache_location = (string) $cache_location; + } + + if ($cache_name_function) + { + $this->cache_name_function = (string) $cache_name_function; + } + + if ($cache_class) + { + $this->cache_class = (string) $cache_class; + } + } + + function pass_file_data($file_class = 'SimplePie_File', $timeout = 10, $useragent = '', $force_fsockopen = false) + { + if ($file_class) + { + $this->file_class = (string) $file_class; + } + + if ($timeout) + { + $this->timeout = (string) $timeout; + } + + if ($useragent) + { + $this->useragent = (string) $useragent; + } + + if ($force_fsockopen) + { + $this->force_fsockopen = (string) $force_fsockopen; + } + } + + function strip_htmltags($tags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style')) + { + if ($tags) + { + if (is_array($tags)) + { + $this->strip_htmltags = $tags; + } + else + { + $this->strip_htmltags = explode(',', $tags); + } + } + else + { + $this->strip_htmltags = false; + } + } + + function encode_instead_of_strip($encode = false) + { + $this->encode_instead_of_strip = (bool) $encode; + } + + function strip_attributes($attribs = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc')) + { + if ($attribs) + { + if (is_array($attribs)) + { + $this->strip_attributes = $attribs; + } + else + { + $this->strip_attributes = explode(',', $attribs); + } + } + else + { + $this->strip_attributes = false; + } + } + + function strip_comments($strip = false) + { + $this->strip_comments = (bool) $strip; + } + + function set_output_encoding($encoding = 'UTF-8') + { + $this->output_encoding = (string) $encoding; + } + + /** + * Set element/attribute key/value pairs of HTML attributes + * containing URLs that need to be resolved relative to the feed + * + * @access public + * @since 1.0 + * @param array $element_attribute Element/attribute key/value pairs + */ + function set_url_replacements($element_attribute = array('a' => 'href', 'area' => 'href', 'blockquote' => 'cite', 'del' => 'cite', 'form' => 'action', 'img' => array('longdesc', 'src'), 'input' => 'src', 'ins' => 'cite', 'q' => 'cite')) + { + $this->replace_url_attributes = (array) $element_attribute; + } + + function sanitize($data, $type, $base = '') + { + $data = trim($data); + if ($data !== '' || $type & SIMPLEPIE_CONSTRUCT_IRI) + { + if ($type & SIMPLEPIE_CONSTRUCT_MAYBE_HTML) + { + if (preg_match('/(&(#(x[0-9a-fA-F]+|[0-9]+)|[a-zA-Z0-9]+)|<\/[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*' . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>)/', $data)) + { + $type |= SIMPLEPIE_CONSTRUCT_HTML; + } + else + { + $type |= SIMPLEPIE_CONSTRUCT_TEXT; + } + } + + if ($type & SIMPLEPIE_CONSTRUCT_BASE64) + { + $data = base64_decode($data); + } + + if ($type & SIMPLEPIE_CONSTRUCT_XHTML) + { + if ($this->remove_div) + { + $data = preg_replace('/^/', '', $data); + $data = preg_replace('/<\/div>$/', '', $data); + } + else + { + $data = preg_replace('/^/', '
', $data); + } + } + + if ($type & (SIMPLEPIE_CONSTRUCT_HTML | SIMPLEPIE_CONSTRUCT_XHTML)) + { + // Strip comments + if ($this->strip_comments) + { + $data = SimplePie_Misc::strip_comments($data); + } + + // Strip out HTML tags and attributes that might cause various security problems. + // Based on recommendations by Mark Pilgrim at: + // http://diveintomark.org/archives/2003/06/12/how_to_consume_rss_safely + if ($this->strip_htmltags) + { + foreach ($this->strip_htmltags as $tag) + { + $pcre = "/<($tag)" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . "(>(.*)<\/$tag" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>|(\/)?>)/siU'; + while (preg_match($pcre, $data)) + { + $data = preg_replace_callback($pcre, array(&$this, 'do_strip_htmltags'), $data); + } + } + } + + if ($this->strip_attributes) + { + foreach ($this->strip_attributes as $attrib) + { + $data = preg_replace('/(<[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*)' . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . trim($attrib) . '(?:\s*=\s*(?:"(?:[^"]*)"|\'(?:[^\']*)\'|(?:[^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?' . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>/', '\1\2\3>', $data); + } + } + + // Replace relative URLs + $this->base = $base; + foreach ($this->replace_url_attributes as $element => $attributes) + { + $data = $this->replace_urls($data, $element, $attributes); + } + + // If image handling (caching, etc.) is enabled, cache and rewrite all the image tags. + if (isset($this->image_handler) && ((string) $this->image_handler) !== '' && $this->enable_cache) + { + $images = SimplePie_Misc::get_element('img', $data); + foreach ($images as $img) + { + if (isset($img['attribs']['src']['data'])) + { + $image_url = call_user_func($this->cache_name_function, $img['attribs']['src']['data']); + $cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, $image_url, 'spi'); + + if ($cache->load()) + { + $img['attribs']['src']['data'] = $this->image_handler . $image_url; + $data = str_replace($img['full'], SimplePie_Misc::element_implode($img), $data); + } + else + { + $file = new $this->file_class($img['attribs']['src']['data'], $this->timeout, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen); + $headers = $file->headers; + + if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300))) + { + if ($cache->save(array('headers' => $file->headers, 'body' => $file->body))) + { + $img['attribs']['src']['data'] = $this->image_handler . $image_url; + $data = str_replace($img['full'], SimplePie_Misc::element_implode($img), $data); + } + else + { + trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); + } + } + } + } + } + } + + // Having (possibly) taken stuff out, there may now be whitespace at the beginning/end of the data + $data = trim($data); + } + + if ($type & SIMPLEPIE_CONSTRUCT_IRI) + { + $data = SimplePie_Misc::absolutize_url($data, $base); + } + + if ($type & (SIMPLEPIE_CONSTRUCT_TEXT | SIMPLEPIE_CONSTRUCT_IRI)) + { + $data = htmlspecialchars($data, ENT_COMPAT, 'UTF-8'); + } + + if ($this->output_encoding !== 'UTF-8') + { + $data = SimplePie_Misc::change_encoding($data, 'UTF-8', $this->output_encoding); + } + } + return $data; + } + + function replace_urls($data, $tag, $attributes) + { + if (!is_array($this->strip_htmltags) || !in_array($tag, $this->strip_htmltags)) + { + $elements = SimplePie_Misc::get_element($tag, $data); + foreach ($elements as $element) + { + if (is_array($attributes)) + { + foreach ($attributes as $attribute) + { + if (isset($element['attribs'][$attribute]['data'])) + { + $element['attribs'][$attribute]['data'] = SimplePie_Misc::absolutize_url($element['attribs'][$attribute]['data'], $this->base); + $new_element = SimplePie_Misc::element_implode($element); + $data = str_replace($element['full'], $new_element, $data); + $element['full'] = $new_element; + } + } + } + elseif (isset($element['attribs'][$attributes]['data'])) + { + $element['attribs'][$attributes]['data'] = SimplePie_Misc::absolutize_url($element['attribs'][$attributes]['data'], $this->base); + $data = str_replace($element['full'], SimplePie_Misc::element_implode($element), $data); + } + } + } + return $data; + } + + function do_strip_htmltags($match) + { + if ($this->encode_instead_of_strip) + { + if (isset($match[4]) && !in_array(strtolower($match[1]), array('script', 'style'))) + { + $match[1] = htmlspecialchars($match[1], ENT_COMPAT, 'UTF-8'); + $match[2] = htmlspecialchars($match[2], ENT_COMPAT, 'UTF-8'); + return "<$match[1]$match[2]>$match[3]</$match[1]>"; + } + else + { + return htmlspecialchars($match[0], ENT_COMPAT, 'UTF-8'); + } + } + elseif (isset($match[4]) && !in_array(strtolower($match[1]), array('script', 'style'))) + { + return $match[4]; + } + else + { + return ''; + } + } +} + +?> diff --git a/inc/Sitemapper.php b/inc/Sitemapper.php new file mode 100644 index 0000000..4689b04 --- /dev/null +++ b/inc/Sitemapper.php @@ -0,0 +1,203 @@ + + */ + +if(!defined('DOKU_INC')) die('meh.'); + +/** + * A class for building sitemaps and pinging search engines with the sitemap URL. + * + * @author Michael Hamann + */ +class Sitemapper { + /** + * Builds a Google Sitemap of all public pages known to the indexer + * + * The map is placed in the cache directory named sitemap.xml.gz - This + * file needs to be writable! + * + * @author Michael Hamann + * @author Andreas Gohr + * @link https://www.google.com/webmasters/sitemaps/docs/en/about.html + * @link http://www.sitemaps.org/ + */ + public static function generate(){ + global $conf; + if($conf['sitemap'] < 1 || !is_numeric($conf['sitemap'])) return false; + + $sitemap = Sitemapper::getFilePath(); + + if(@file_exists($sitemap)){ + if(!is_writable($sitemap)) return false; + }else{ + if(!is_writable(dirname($sitemap))) return false; + } + + if(@filesize($sitemap) && + @filemtime($sitemap) > (time()-($conf['sitemap']*86400))){ // 60*60*24=86400 + dbglog('Sitemapper::generate(): Sitemap up to date'); + return false; + } + + dbglog("Sitemapper::generate(): using $sitemap"); + + $pages = idx_get_indexer()->getPages(); + dbglog('Sitemapper::generate(): creating sitemap using '.count($pages).' pages'); + $items = array(); + + // build the sitemap items + foreach($pages as $id){ + //skip hidden, non existing and restricted files + if(isHiddenPage($id)) continue; + if(auth_aclcheck($id,'','') < AUTH_READ) continue; + $item = SitemapItem::createFromID($id); + if ($item !== NULL) + $items[] = $item; + } + + $eventData = array('items' => &$items, 'sitemap' => &$sitemap); + $event = new Doku_Event('SITEMAP_GENERATE', $eventData); + if ($event->advise_before(true)) { + //save the new sitemap + $result = io_saveFile($sitemap, Sitemapper::getXML($items)); + } + $event->advise_after(); + + return $result; + } + + /** + * Builds the sitemap XML string from the given array auf SitemapItems. + * + * @param $items array The SitemapItems that shall be included in the sitemap. + * @return string The sitemap XML. + * @author Michael Hamann + */ + private static function getXML($items) { + ob_start(); + echo ''.NL; + echo ''.NL; + foreach ($items as $item) { + echo $item->toXML(); + } + echo ''.NL; + $result = ob_get_contents(); + ob_end_clean(); + return $result; + } + + /** + * Helper function for getting the path to the sitemap file. + * + * @return The path to the sitemap file. + * @author Michael Hamann + */ + public static function getFilePath() { + global $conf; + + $sitemap = $conf['cachedir'].'/sitemap.xml'; + if($conf['compression'] === 'bz2' || $conf['compression'] === 'gz'){ + $sitemap .= '.gz'; + } + + return $sitemap; + } + + /** + * Pings search engines with the sitemap url. Plugins can add or remove + * urls to ping using the SITEMAP_PING event. + * + * @author Michael Hamann + */ + public static function pingSearchEngines() { + //ping search engines... + $http = new DokuHTTPClient(); + $http->timeout = 8; + + $encoded_sitemap_url = urlencode(wl('', array('do' => 'sitemap'), true, '&')); + $ping_urls = array( + 'google' => 'http://www.google.com/webmasters/sitemaps/ping?sitemap='.$encoded_sitemap_url, + 'yahoo' => 'http://search.yahooapis.com/SiteExplorerService/V1/updateNotification?appid=dokuwiki&url='.$encoded_sitemap_url, + 'microsoft' => 'http://www.bing.com/webmaster/ping.aspx?siteMap='.$encoded_sitemap_url, + ); + + $data = array('ping_urls' => $ping_urls, + 'encoded_sitemap_url' => $encoded_sitemap_url + ); + $event = new Doku_Event('SITEMAP_PING', $data); + if ($event->advise_before(true)) { + foreach ($data['ping_urls'] as $name => $url) { + dbglog("Sitemapper::PingSearchEngines(): pinging $name"); + $resp = $http->get($url); + if($http->error) dbglog("Sitemapper:pingSearchengines(): $http->error"); + dbglog('Sitemapper:pingSearchengines(): '.preg_replace('/[\n\r]/',' ',strip_tags($resp))); + } + } + $event->advise_after(); + + return true; + } +} + +/** + * An item of a sitemap. + * + * @author Michael Hamann + */ +class SitemapItem { + public $url; + public $lastmod; + public $changefreq; + public $priority; + + /** + * Create a new item. + * + * @param $url string The url of the item + * @param $lastmod int Timestamp of the last modification + * @param $changefreq string How frequently the item is likely to change. Valid values: always, hourly, daily, weekly, monthly, yearly, never. + * @param $priority float|string The priority of the item relative to other URLs on your site. Valid values range from 0.0 to 1.0. + */ + public function __construct($url, $lastmod, $changefreq = null, $priority = null) { + $this->url = $url; + $this->lastmod = $lastmod; + $this->changefreq = $changefreq; + $this->priority = $priority; + } + + /** + * Helper function for creating an item for a wikipage id. + * + * @param $id string A wikipage id. + * @param $changefreq string How frequently the item is likely to change. Valid values: always, hourly, daily, weekly, monthly, yearly, never. + * @param $priority float|string The priority of the item relative to other URLs on your site. Valid values range from 0.0 to 1.0. + * @return The sitemap item. + */ + public static function createFromID($id, $changefreq = null, $priority = null) { + $id = trim($id); + $date = @filemtime(wikiFN($id)); + if(!$date) return NULL; + return new SitemapItem(wl($id, '', true), $date, $changefreq, $priority); + } + + /** + * Get the XML representation of the sitemap item. + * + * @return The XML representation. + */ + public function toXML() { + $result = ' '.NL + .' '.hsc($this->url).''.NL + .' '.date_iso8601($this->lastmod).''.NL; + if ($this->changefreq !== NULL) + $result .= ' '.hsc($this->changefreq).''.NL; + if ($this->priority !== NULL) + $result .= ' '.hsc($this->priority).''.NL; + $result .= ' '.NL; + return $result; + } +} diff --git a/inc/TarLib.class.php b/inc/TarLib.class.php new file mode 100644 index 0000000..12418c4 --- /dev/null +++ b/inc/TarLib.class.php @@ -0,0 +1,795 @@ + (Maxg) + * @author Christopher Smith + */ + + +/** + * Those constants represent the compression method to use. + * COMPRESS_GZIP is used for the GZIP compression; COMPRESS_BZIP for + * BZIP2 and COMPRESS_NONE for no compression. + * + * On the other hand, COMPRESS_AUTO is a bit harder. It will first check + * if the zlib extensions are loaded. + * If it is, GZIP will be used. Else it will check if the bz2 extensions + * are loaded. If it is, BZIP2 will be used. Else no compression will be + * performed. + * + * You can then use getCompression() to know the compression chosen. + * + * If you selected a compression which can't be used (i.e extension not + * present), it will be just disabled, and won't produce any error ! + * As a consequence, getCompression() will return COMPRESS_NONE + * + * ARCHIVE_DYNAMIC can be passed as the first argument of the constructor, to + * create an archive in memory instead of a file. See also: MaxgTar(), + * getDynamicArchive() and writeArchive() + * + * ARCHIVE_RENAMECOMP is a flag that can be multiplied by the compression method + * (i.e COMPRESS_AUTO * ARCHIVE_RENAMECOMP). This will add the correct extension + * to the archive name, which is useful with COMPRESS_AUTO, ie .bz2 if you gave + * COMPRESS_BZIP. See also getCompression(TRUE) which does exactly the + * same + * + * COMPRESS_DETECT does exactly the opposite and try to detect the + * compression to use to read the archive depending on its extension. (i.e if + * the archive ends with .tar.gz TarLib will try to decompress it with + * GZIP). See also setCompression() + * + * FULL_ARCHIVE is a -1 constant that means "the complete archive" when + * extracting. This is explained in Extract() + */ +#define('COMPRESS_GZIP',1); +#define('COMPRESS_BZIP',2); +#define('COMPRESS_AUTO',3); +#define('COMPRESS_NONE',0); +#define('TARLIB_VERSION','1.2'); +#define('FULL_ARCHIVE',-1); +#define('ARCHIVE_DYNAMIC',0); +#define('ARCHIVE_RENAMECOMP',5); +#define('COMPRESS_DETECT',-1); + +class TarLib { + var $_comptype; + var $_compzlevel; + var $_fp; + var $_memdat; + var $_nomf; + var $_result; + var $_initerror; + + const COMPRESS_GZIP = 1; + const COMPRESS_BZIP = 2; + const COMPRESS_AUTO = 3; + const COMPRESS_NONE = 0; + const TARLIB_VERSION = '1.2'; + const FULL_ARCHIVE = -1; + const ARCHIVE_DYNAMIC = 0; + const ARCHIVE_RENAMECOMP = 5; + const COMPRESS_DETECT = -1; + + /** + * constructor, initialize the class + * + * The constructor initialize the variables and prepare the class for the + * archive, and return the object created. Note that you can use multiple + * instances of the MaxgTar class, if you call this function another time and + * store the object in an other variable. + * + * In fact, MaxgTar accepts the following arguments (all are optional) : + * + * filename can be either a file name (absolute or relative). In this + * case, it can be used both for reading and writing. You can also open + * remote archive if you add a protocole name at the beginning of the file + * (ie https://host.dom/archive.tar.gz), but for reading only and if the + * directive allow_url_fopen is enabled in PHP.INI (this can be checked with + * TarInfo()). If you pass a file that doesn't exist, the script + * will try to create it. If the archive already exists and contains files, + * you can use Add() to append files.But by default this parameter + * is ARCHIVE_DYNAMIC (write only) so the archive is created in memory and + * can be sent to a file [writeArchive()] or to the client + * [sendClient()] + * + * compression_type should be a constant that represents a type of + * compression, or its integer value. The different values are described in + * the constants. + * + * compression_level is an integer between 1 and 9 (by default) an + * represent the GZIP or BZIP compression level. 1 produce fast compression, + * and 9 produce smaller files. See the RFC 1952 for more infos. + */ + function tarlib($p_filen = TarLib::ARCHIVE_DYNAMIC , $p_comptype = TarLib::COMPRESS_AUTO, $p_complevel = 9) { + $this->_initerror = 0; + $this->_nomf = $p_filen; + $flag=0; + if($p_comptype && $p_comptype % 5 == 0){ + $p_comptype /= TarLib::ARCHIVE_RENAMECOMP; + $flag=1; + } + + if($p_complevel > 0 && $p_complevel <= 9) $this->_compzlevel = $p_complevel; + else $p_complevel = 9; + + if($p_comptype == TarLib::COMPRESS_DETECT) { + if(strtolower(substr($p_filen,-3)) == '.gz') $p_comptype = TarLib::COMPRESS_GZIP; + elseif(strtolower(substr($p_filen,-4)) == '.bz2') $p_comptype = TarLib::COMPRESS_BZIP; + else $p_comptype = TarLib::COMPRESS_NONE; + } + + switch($p_comptype) { + case TarLib::COMPRESS_GZIP: + if(!extension_loaded('zlib')) $this->_initerror = -1; + $this->_comptype = TarLib::COMPRESS_GZIP; + break; + + case TarLib::COMPRESS_BZIP: + if(!extension_loaded('bz2')) $this->_initerror = -2; + $this->_comptype = TarLib::COMPRESS_BZIP; + break; + + case TarLib::COMPRESS_AUTO: + if(extension_loaded('zlib')) + $this->_comptype = TarLib::COMPRESS_GZIP; + elseif(extension_loaded('bz2')) + $this->_comptype = TarLib::COMPRESS_BZIP; + else + $this->_comptype = TarLib::COMPRESS_NONE; + break; + + default: + $this->_comptype = TarLib::COMPRESS_NONE; + } + + if($this->_initerror < 0) $this->_comptype = TarLib::COMPRESS_NONE; + + if($flag) $this->_nomf.= '.'.$this->getCompression(1); + $this->_result = true; + } + + /** + * Recycle a TAR object. + * + * This function does exactly the same as TarLib (constructor), except it + * returns a status code. + */ + function setArchive($p_name='', $p_comp = TarLib::COMPRESS_AUTO, $p_level=9) { + $this->_CompTar(); + $this->TarLib($p_name, $p_comp, $p_level); + return $this->_result; + } + + /** + * Get the compression used to generate the archive + * + * This is a very useful function when you're using dynamical archives. + * Besides, if you let the script chose which compression to use, you'll have + * a problem when you'll want to send it to the client if you don't know + * which compression was used. + * + * There are two ways to call this function : if you call it without argument + * or with FALSE, it will return the compression constants, explained on the + * MaxgTar Constants. If you call it with GetExtension on TRUE it will + * return the extension without starting dot (ie "tar" or "tar.bz2" or + * "tar.gz") + * + * NOTE: This can be done with the flag ARCHIVE_RENAMECOMP, see the + * MaxgTar Constants + */ + function getCompression($ext = false) { + $exts = Array('tar','tar.gz','tar.bz2'); + if($ext) return $exts[$this->_comptype]; + return $this->_comptype; + } + + /** + * Change the compression mode. + * + * This function will change the compression methode to read or write + * the archive. See the MaxgTar Constants to see which constants you can use. + * It may look strange, but it returns the GZIP compression level. + */ + function setCompression($p_comp = TarLib::COMPRESS_AUTO) { + $this->setArchive($this->_nomf, $p_comp, $this->_compzlevel); + return $this->_compzlevel; + } + + /** + * Returns the compressed dynamic archive. + * + * When you're working with dynamic archives, use this function to grab + * the final compressed archive in a string ready to be put in a SQL table or + * in a file. + */ + function getDynamicArchive() { + return $this->_encode($this->_memdat); + } + + /** + * Write a dynamical archive into a file + * + * This function attempts to write a dynamicaly-genrated archive into + * TargetFile on the webserver. It returns a TarErrorStr() status + * code. + * + * To know the extension to add to the file if you're using AUTO_DETECT + * compression, you can use getCompression(). + */ + function writeArchive($p_archive) { + if(!$this->_memdat) return -7; + $fp = @fopen($p_archive, 'wb'); + if(!$fp) return -6; + + fwrite($fp, $this->_memdat); + fclose($fp); + + return true; + } + + /** + * Send a TAR archive to the client browser. + * + * This function will send an archive to the client, and return a status + * code, but can behave differently depending on the arguments you give. All + * arguments are optional. + * + * ClientName is used to specify the archive name to give to the browser. If + * you don't give one, it will send the constructor filename or return an + * error code in case of dynamical archive. + * + * FileName is optional and used to send a specific archive. Leave it blank + * to send dynamical archives or the current working archive. + * + * If SendHeaders is enabled (by default), the library will send the HTTP + * headers itself before it sends the contents. This headers are : + * Content-Type, Content-Disposition, Content-Length and Accept-Range. + * + * Please note that this function DOES NOT stops the script so don't forget + * to exit() to avoid your script sending other data and corrupt the archive. + * Another note : for AUTO_DETECT dynamical archives you can know the + * extension to add to the name with getCompression() + */ + function sendClient($name = '', $archive = '', $headers = true) { + if(!$name && !$this->_nomf) return -9; + if(!$archive && !$this->_memdat) return -10; + if(!$name) $name = basename($this->_nomf); + + if($archive){ if(!file_exists($archive)) return -11; } + else $decoded = $this->getDynamicArchive(); + + if($headers) { + header('Content-Type: application/x-gtar'); + header('Content-Disposition: attachment; filename='.basename($name)); + header('Accept-Ranges: bytes'); + header('Content-Length: '.($archive ? filesize($archive) : strlen($decoded))); + } + + if($archive) { + $fp = @fopen($archive,'rb'); + if(!$fp) return -4; + + while(!feof($fp)) echo fread($fp,2048); + } else { + echo $decoded; + } + + return true; + } + + /** + * Extract part or totality of the archive. + * + * This function can extract files from an archive, and returns then a + * status codes that can be converted with TarErrorStr() into a + * human readable message. + * + * Only the first argument is required, What and it can be either the + * constant FULL_ARCHIVE or an indexed array containing each file you want to + * extract. + * + * To contains the target folder to extract the archive. It is optional and + * the default value is '.' which means the current folder. If the target + * folder doesn't exist, the script attempts to create it and give it + * permissions 0777 by default. + * + * RemovePath is very usefull when you want to extract files from a subfoler + * in the archive to a root folder. For instance, if you have a file in the + * archive called some/sub/folder/test.txt and you want to extract it to the + * script folder, you can call Extract with To = '.' and RemovePath = + * 'some/sub/folder/' + * + * FileMode is optional and its default value is 0755. It is in fact the UNIX + * permission in octal mode (prefixed with a 0) that will be given on each + * extracted file. + */ + function Extract($p_what = TarLib::FULL_ARCHIVE, $p_to = '.', $p_remdir='', $p_mode = 0755) { + if(!$this->_OpenRead()) return -4; + // if(!@is_dir($p_to)) if(!@mkdir($p_to, 0777)) return -8; --CS + if(!@is_dir($p_to)) if(!$this->_dirApp($p_to)) return -8; //--CS (route through correct dir fn) + + $ok = $this->_extractList($p_to, $p_what, $p_remdir, $p_mode); + $this->_CompTar(); + + return $ok; + } + + /** + * Create a new package with the given files + * + * This function will attempt to create a new archive with global headers + * then add the given files into. If the archive is a real file, the + * contents are written directly into the file, if it is a dynamic archive + * contents are only stored in memory. This function should not be used to + * add files to an existing archive, you should use Add() instead. + * + * The FileList actually supports three different modes : + * + * - You can pass a string containing filenames separated by pipes '|'. + * In this case the file are read from the webserver filesystem and the + * root folder is the folder where the script using the MaxgTar is called. + * + * - You can also give a unidimensional indexed array containing the + * filenames. The behaviour for the content reading is the same that a + * '|'ed string. + * + * - The more useful usage is to pass bidimensional arrays, where the + * first element contains the filename and the second contains the file + * contents. You can even add empty folders to the package if the filename + * has a leading '/'. Once again, have a look at the exemples to understand + * better. + * + * Note you can also give arrays with both dynamic contents and static files. + * + * The optional parameter RemovePath can be used to delete a part of the tree + * of the filename you're adding, for instance if you're adding in the root + * of a package a file that is stored somewhere in the server tree. + * + * On the contrary the parameter AddPath can be used to add a prefix folder + * to the file you store. Note also that the RemovePath is applied before the + * AddPath is added, so it HAS a sense to use both parameters together. + */ + function Create($p_filelist,$p_add='',$p_rem='') { + if(!$fl = $this->_fetchFilelist($p_filelist)) return -5; + if(!$this->_OpenWrite()) return -6; + + $ok = $this->_addFileList($fl,$p_add,$p_rem); + + if($ok){ + $this->_writeFooter(); + }else{ + $this->_CompTar(); + @unlink($this->_nomf); + } + + return $ok; + } + + /** + * Add files to an existing package. + * + * This function does exactly the same than Create() exept it + * will append the given files at the end of the archive. Please not the is + * safe to call Add() on a newly created archive whereas the + * contrary will fail ! + * + * This function returns a status code, you can use TarErrorStr() on + * it to get the human-readable description of the error. + */ + function Add($p_filelist, $p_add = '', $p_rem = '') { + if (($this->_nomf != TarLib::ARCHIVE_DYNAMIC && @is_file($this->_nomf)) || + ($this->_nomf == TarLib::ARCHIVE_DYNAMIC && !$this->_memdat)){ + return $this->Create($p_filelist, $p_add, $p_rem); + } + + if(!$fl = $this->_fetchFilelist($p_filelist)) return -5; + return $this->_append($fl, $p_add, $p_rem); + } + + /** + * Read the contents of a TAR archive + * + * This function attempts to get the list of the files stored in the + * archive, and return either an error code or an indexed array of + * associative array containing for each file the following information : + * + * checksum Tar Checksum of the file + * filename The full name of the stored file (up to 100 c.) + * mode UNIX permissions in DECIMAL, not octal + * uid The Owner ID + * gid The Group ID + * size Uncompressed filesize + * mtime Timestamp of last modification + * typeflag Empty for files, set for folders + * link For the links, did you guess it ? + * uname Owner name + * gname Group name + */ + function ListContents() { + if(!$this->_nomf) return -3; + if(!$this->_OpenRead()) return -4; + + $result = Array(); + + while ($dat = $this->_read(512)) { + $dat = $this->_readHeader($dat); + if(!is_array($dat)) continue; + + $this->_seek(ceil($dat['size']/512)*512,1); + $result[] = $dat; + } + + return $result; + } + + /** + * Convert a status code into a human readable message + * + * Some MaxgTar functions like Create(), Add() ... return numerical + * status code. You can pass them to this function to grab their english + * equivalent. + */ + function TarErrorStr($i) { + $ecodes = Array( + 1 => true, + 0 => "Undocumented error", + -1 => "Can't use COMPRESS_GZIP compression : ZLIB extensions are not loaded !", + -2 => "Can't use COMPRESS_BZIP compression : BZ2 extensions are not loaded !", + -3 => "You must set a archive file to read the contents !", + -4 => "Can't open the archive file for read !", + -5 => "Invalide file list !", + -6 => "Can't open the archive in write mode !", + -7 => "There is no ARCHIVE_DYNAMIC to write !", + -8 => "Can't create the directory to extract files !", + -9 => "Please pass a archive name to send if you made created an ARCHIVE_DYNAMIC !", + -10 => "You didn't pass an archive filename and there is no stored ARCHIVE_DYNAMIC !", + -11 => "Given archive doesn't exist !" + ); + + return isset($ecodes[$i]) ? $ecodes[$i] : $ecodes[0]; + } + + function _seek($p_flen, $tell=0) { + if($this->_nomf === TarLib::ARCHIVE_DYNAMIC) + $this->_memdat=substr($this->_memdat,0,($tell ? strlen($this->_memdat) : 0) + $p_flen); + elseif($this->_comptype == TarLib::COMPRESS_GZIP) + @gzseek($this->_fp, ($tell ? @gztell($this->_fp) : 0)+$p_flen); + elseif($this->_comptype == TarLib::COMPRESS_BZIP) + @fseek($this->_fp, ($tell ? @ftell($this->_fp) : 0)+$p_flen); + else + @fseek($this->_fp, ($tell ? @ftell($this->_fp) : 0)+$p_flen); + } + + function _OpenRead() { + if($this->_comptype == TarLib::COMPRESS_GZIP) + $this->_fp = @gzopen($this->_nomf, 'rb'); + elseif($this->_comptype == TarLib::COMPRESS_BZIP) + $this->_fp = @bzopen($this->_nomf, 'rb'); + else + $this->_fp = @fopen($this->_nomf, 'rb'); + + return ($this->_fp ? true : false); + } + + function _OpenWrite($add = 'w') { + if($this->_nomf === TarLib::ARCHIVE_DYNAMIC) return true; + + if($this->_comptype == TarLib::COMPRESS_GZIP) + $this->_fp = @gzopen($this->_nomf, $add.'b'.$this->_compzlevel); + elseif($this->_comptype == TarLib::COMPRESS_BZIP) + $this->_fp = @bzopen($this->_nomf, $add.'b'); + else + $this->_fp = @fopen($this->_nomf, $add.'b'); + + return ($this->_fp ? true : false); + } + + function _CompTar() { + if($this->_nomf === TarLib::ARCHIVE_DYNAMIC || !$this->_fp) return; + + if($this->_comptype == TarLib::COMPRESS_GZIP) @gzclose($this->_fp); + elseif($this->_comptype == TarLib::COMPRESS_BZIP) @bzclose($this->_fp); + else @fclose($this->_fp); + } + + function _read($p_len) { + if($this->_comptype == TarLib::COMPRESS_GZIP) + return @gzread($this->_fp,$p_len); + elseif($this->_comptype == TarLib::COMPRESS_BZIP) + return @bzread($this->_fp,$p_len); + else + return @fread($this->_fp,$p_len); + } + + function _write($p_data) { + if($this->_nomf === TarLib::ARCHIVE_DYNAMIC) $this->_memdat .= $p_data; + elseif($this->_comptype == TarLib::COMPRESS_GZIP) + return @gzwrite($this->_fp,$p_data); + + elseif($this->_comptype == TarLib::COMPRESS_BZIP) + return @bzwrite($this->_fp,$p_data); + + else + return @fwrite($this->_fp,$p_data); + } + + function _encode($p_dat) { + if($this->_comptype == TarLib::COMPRESS_GZIP) + return gzencode($p_dat, $this->_compzlevel); + elseif($this->_comptype == TarLib::COMPRESS_BZIP) + return bzcompress($p_dat, $this->_compzlevel); + else return $p_dat; + } + + function _readHeader($p_dat) { + if (!$p_dat || strlen($p_dat) != 512) return false; + + for ($i=0, $chks=0; $i<148; $i++) + $chks += ord($p_dat[$i]); + + for ($i=156,$chks+=256; $i<512; $i++) + $chks += ord($p_dat[$i]); + + $headers = @unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor", $p_dat); + if(!$headers) return false; + + $return['checksum'] = OctDec(trim($headers['checksum'])); + if ($return['checksum'] != $chks) return false; + + $return['filename'] = trim($headers['filename']); + $return['mode'] = OctDec(trim($headers['mode'])); + $return['uid'] = OctDec(trim($headers['uid'])); + $return['gid'] = OctDec(trim($headers['gid'])); + $return['size'] = OctDec(trim($headers['size'])); + $return['mtime'] = OctDec(trim($headers['mtime'])); + $return['typeflag'] = $headers['typeflag']; + $return['link'] = trim($headers['link']); + $return['uname'] = trim($headers['uname']); + $return['gname'] = trim($headers['gname']); + + return $return; + } + + function _fetchFilelist($p_filelist) { + if(!$p_filelist || (is_array($p_filelist) && !@count($p_filelist))) return false; + + if(is_string($p_filelist)) { + $p_filelist = explode('|',$p_filelist); + if(!is_array($p_filelist)) $p_filelist = Array($p_filelist); + } + + return $p_filelist; + } + + function _addFileList($p_fl, $p_addir, $p_remdir) { + foreach($p_fl as $file) { + if(($file == $this->_nomf && $this->_nomf != TarLib::ARCHIVE_DYNAMIC) || !$file || (!file_exists($file) && !is_array($file))) + continue; + + if (!$this->_addFile($file, $p_addir, $p_remdir)) + continue; + + if (@is_dir($file)) { + $d = @opendir($file); + + if(!$d) continue; + readdir($d); + readdir($d); + + while($f = readdir($d)) { + if($file != ".") $tmplist[0] = "$file/$f"; + else $tmplist[0] = $d; + + $this->_addFileList($tmplist, $p_addir, $p_remdir); + } + + closedir($d); + unset($tmplist,$f); + } + } + return true; + } + + function _addFile($p_fn, $p_addir = '', $p_remdir = '') { + if(is_array($p_fn)) list($p_fn, $data) = $p_fn; + $sname = $p_fn; + + if($p_remdir) { + if(substr($p_remdir,-1) != '/') $p_remdir .= "/"; + + if(substr($sname, 0, strlen($p_remdir)) == $p_remdir) + $sname = substr($sname, strlen($p_remdir)); + } + + if($p_addir) $sname = $p_addir.(substr($p_addir,-1) == '/' ? '' : "/").$sname; + + if(strlen($sname) > 99) return; + + if(@is_dir($p_fn)) { + if(!$this->_writeFileHeader($p_fn, $sname)) return false; + } else { + if(!$data) { + $fp = fopen($p_fn, 'rb'); + if(!$fp) return false; + } + + if(!$this->_writeFileHeader($p_fn, $sname, ($data ? strlen($data) : false))) return false; + + if(!$data) { + while(!feof($fp)) { + $packed = pack("a512", fread($fp,512)); + $this->_write($packed); + } + fclose($fp); + } else { + $len = strlen($data); + for($s = 0; $s < $len; $s += 512){ + $this->_write(pack("a512",substr($data,$s,512))); + } + } + } + + return true; + } + + function _writeFileHeader($p_file, $p_sname, $p_data=false) { + if(!$p_data) { + if (!$p_sname) $p_sname = $p_file; + $p_sname = $this->_pathTrans($p_sname); + + $h_info = stat($p_file); + $h[0] = sprintf("%6s ", DecOct($h_info[4])); + $h[] = sprintf("%6s ", DecOct($h_info[5])); + $h[] = sprintf("%6s ", DecOct(fileperms($p_file))); + clearstatcache(); + $h[] = sprintf("%11s ", DecOct(filesize($p_file))); + $h[] = sprintf("%11s", DecOct(filemtime($p_file))); + + $dir = @is_dir($p_file) ? '5' : ''; + } else { + $dir = ''; + $p_data = sprintf("%11s ", DecOct($p_data)); + $time = sprintf("%11s ", DecOct(time())); + $h = Array(" 0 "," 0 "," 40777 ",$p_data,$time); + } + + $data_first = pack("a100a8a8a8a12A12", $p_sname, $h[2], $h[0], $h[1], $h[3], $h[4]); + $data_last = pack("a1a100a6a2a32a32a8a8a155a12", $dir, '', '', '', '', '', '', '', '', ""); + + for ($i=0,$chks=0; $i<148; $i++) + $chks += ord($data_first[$i]); + + for ($i=156, $chks+=256, $j=0; $i<512; $i++, $j++) + $chks += ord($data_last[$j]); + + $this->_write($data_first); + + $chks = pack("a8",sprintf("%6s ", DecOct($chks))); + $this->_write($chks.$data_last); + + return true; + } + + function _append($p_filelist, $p_addir="", $p_remdir="") { + if(!$this->_fp) if(!$this->_OpenWrite('a')) return -6; + + if($this->_nomf == TarLib::ARCHIVE_DYNAMIC) { + $s = strlen($this->_memdat); + $this->_memdat = substr($this->_memdat,0,-512); + } else { + $s = filesize($this->_nomf); + $this->_seek($s-512); + } + + $ok = $this->_addFileList($p_filelist, $p_addir, $p_remdir); + $this->_writeFooter(); + + return $ok; + } + + function _pathTrans($p_dir) { + if ($p_dir) { + $subf = explode("/", $p_dir); + $r=''; + + for ($i=count($subf)-1; $i>=0; $i--) { + if ($subf[$i] == ".") { + # do nothing + } elseif ($subf[$i] == "..") { + $i--; + } elseif (!$subf[$i] && $i!=count($subf)-1 && $i) { + # do nothing + } else { + $r = $subf[$i].($i!=(count($subf)-1) ? "/".$r : ""); + } + } + } + return $r; + } + + function _writeFooter() { + $this->_write(pack("a512", "")); + } + + function _extractList($p_to, $p_files, $p_remdir, $p_mode = 0755) { + if (!$p_to || ($p_to[0]!="/"&&substr($p_to,0,3)!="../"&&substr($p_to,1,3)!=":\\"&&substr($p_to,1,2)!=":/")) /*" // <- PHP Coder bug */ + $p_to = "./$p_to"; + + if ($p_remdir && substr($p_remdir,-1)!='/') $p_remdir .= '/'; + $p_remdirs = strlen($p_remdir); + while($dat = $this->_read(512)) { + $headers = $this->_readHeader($dat); + if(!$headers['filename']) continue; + + if($p_files == -1 || $p_files[0] == -1){ + $extract = true; + } else { + $extract = false; + + foreach($p_files as $f) { + if(substr($f,-1) == "/") { + if((strlen($headers['filename']) > strlen($f)) && (substr($headers['filename'],0,strlen($f))==$f)) { + $extract = true; + break; + } + } elseif($f == $headers['filename']) { + $extract = true; + break; + } + } + } + + if ($extract) { + $det[] = $headers; + if ($p_remdir && substr($headers['filename'],0,$p_remdirs)==$p_remdir) + $headers['filename'] = substr($headers['filename'],$p_remdirs); + + if($headers['filename'].'/' == $p_remdir && $headers['typeflag']=='5') continue; + + if ($p_to != "./" && $p_to != "/") { + while($p_to{-1}=="/") $p_to = substr($p_to,0,-1); + + if($headers['filename']{0} == "/") + $headers['filename'] = $p_to.$headers['filename']; + else + $headers['filename'] = $p_to."/".$headers['filename']; + } + + $ok = $this->_dirApp($headers['typeflag']=="5" ? $headers['filename'] : dirname($headers['filename'])); + if($ok < 0) return $ok; + + if (!$headers['typeflag']) { + if (!$fp = @fopen($headers['filename'], "wb")) return -6; + $n = floor($headers['size']/512); + + for ($i=0; $i<$n; $i++){ + fwrite($fp, $this->_read(512),512); + } + if (($headers['size'] % 512) != 0) fwrite($fp, $this->_read(512), $headers['size'] % 512); + + fclose($fp); + touch($headers['filename'], $headers['mtime']); + chmod($headers['filename'], $p_mode); + } else { + $this->_seek(ceil($headers['size']/512)*512,1); + } + }else $this->_seek(ceil($headers['size']/512)*512,1); + } + return $det; + } + + function _dirApp($d) { + // map to dokuwiki function (its more robust) + return io_mkdir_p($d); + } + +} + diff --git a/inc/ZipLib.class.php b/inc/ZipLib.class.php new file mode 100644 index 0000000..cf89a40 --- /dev/null +++ b/inc/ZipLib.class.php @@ -0,0 +1,504 @@ + + */ +class ZipLib { + + var $datasec; + var $ctrl_dir = array(); + var $eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00"; + var $old_offset = 0; + var $dirs = Array("."); + + function get_List($zip_name) { + $zip = @fopen($zip_name, 'rb'); + if(!$zip) return(0); + $centd = $this->ReadCentralDir($zip,$zip_name); + + @rewind($zip); + @fseek($zip, $centd['offset']); + + for ($i=0; $i<$centd['entries']; $i++) { + $header = $this->ReadCentralFileHeaders($zip); + $header['index'] = $i; + + $info['filename'] = $header['filename']; + $info['stored_filename'] = $header['stored_filename']; + $info['size'] = $header['size']; + $info['compressed_size'] = $header['compressed_size']; + $info['crc'] = strtoupper(dechex( $header['crc'] )); + $info['mtime'] = $header['mtime']; + $info['comment'] = $header['comment']; + $info['folder'] = ($header['external']==0x41FF0010||$header['external']==16)?1:0; + $info['index'] = $header['index']; + $info['status'] = $header['status']; + $ret[]=$info; + + unset($header); + } + return $ret; + } + + function Add($files,$compact) { + if(!is_array($files[0])) $files=Array($files); + + for($i=0;$files[$i];$i++){ + $fn = $files[$i]; + if(!in_Array(dirname($fn[0]),$this->dirs)) + $this->add_Dir(dirname($fn[0])); + if(basename($fn[0])) + $ret[basename($fn[0])]=$this->add_File($fn[1],$fn[0],$compact); + } + return $ret; + } + + /** + * Zips recursively the $folder directory, from the $basedir directory + */ + function Compress($folder, $basedir=null, $parent=null) { + $full_path = $basedir."/".$parent.$folder; + $zip_path = $parent.$folder; + if ($zip_path) { + $zip_path .= "/"; + $this->add_dir($zip_path); + } + $dir = new DirectoryIterator($full_path); + foreach($dir as $file) { + if(!$file->isDot()) { + $filename = $file->getFilename(); + if($file->isDir()) { + $this->Compress($filename, $basedir, $zip_path); + } else { + $content = join('', file($full_path.'/'.$filename)); + $this->add_File($content, $zip_path.$filename); + } + } + } + } + + /** + * Returns the Zip file + */ + function get_file() { + $data = implode('', $this -> datasec); + $ctrldir = implode('', $this -> ctrl_dir); + + return $data . $ctrldir . $this -> eof_ctrl_dir . + pack('v', count($this->ctrl_dir)).pack('v', count($this->ctrl_dir)). + pack('V', strlen($ctrldir)) . pack('V', strlen($data)) . "\x00\x00"; + } + + function add_dir($name) { + $name = str_replace("\\", "/", $name); + $fr = "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + + $fr .= pack("V",0).pack("V",0).pack("V",0).pack("v", strlen($name) ); + $fr .= pack("v", 0 ).$name.pack("V", 0).pack("V", 0).pack("V", 0); + $this -> datasec[] = $fr; + + $new_offset = strlen(implode("", $this->datasec)); + + $cdrec = "\x50\x4b\x01\x02\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + $cdrec .= pack("V",0).pack("V",0).pack("V",0).pack("v", strlen($name) ); + $cdrec .= pack("v", 0 ).pack("v", 0 ).pack("v", 0 ).pack("v", 0 ); + $ext = "\xff\xff\xff\xff"; + $cdrec .= pack("V", 16 ).pack("V", $this -> old_offset ).$name; + + $this -> ctrl_dir[] = $cdrec; + $this -> old_offset = $new_offset; + $this -> dirs[] = $name; + } + + /** + * Add a file named $name from a string $data + */ + function add_File($data, $name, $compact = 1) { + $name = str_replace('\\', '/', $name); + $dtime = dechex($this->DosTime()); + + $hexdtime = pack('H*',$dtime[6].$dtime[7]. + $dtime[4].$dtime[5]. + $dtime[2].$dtime[3]. + $dtime[0].$dtime[1]); + + if($compact){ + $fr = "\x50\x4b\x03\x04\x14\x00\x00\x00\x08\x00".$hexdtime; + }else{ + $fr = "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00".$hexdtime; + } + $unc_len = strlen($data); + $crc = crc32($data); + + if($compact){ + $zdata = gzcompress($data); + $c_len = strlen($zdata); + $zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); + }else{ + $zdata = $data; + } + $c_len=strlen($zdata); + $fr .= pack('V', $crc).pack('V', $c_len).pack('V', $unc_len); + $fr .= pack('v', strlen($name)).pack('v', 0).$name.$zdata; + + $fr .= pack('V', $crc).pack('V', $c_len).pack('V', $unc_len); + + $this -> datasec[] = $fr; + $new_offset = strlen(implode('', $this->datasec)); + if($compact) { + $cdrec = "\x50\x4b\x01\x02\x00\x00\x14\x00\x00\x00\x08\x00"; + } else { + $cdrec = "\x50\x4b\x01\x02\x14\x00\x0a\x00\x00\x00\x00\x00"; + } + $cdrec .= $hexdtime.pack('V', $crc).pack('V', $c_len).pack('V', $unc_len); + $cdrec .= pack('v', strlen($name) ).pack('v', 0 ).pack('v', 0 ); + $cdrec .= pack('v', 0 ).pack('v', 0 ).pack('V', 32 ); + $cdrec .= pack('V', $this -> old_offset ); + + $this -> old_offset = $new_offset; + $cdrec .= $name; + $this -> ctrl_dir[] = $cdrec; + return true; + } + + function DosTime() { + $timearray = getdate(); + if ($timearray['year'] < 1980) { + $timearray['year'] = 1980; + $timearray['mon'] = 1; + $timearray['mday'] = 1; + $timearray['hours'] = 0; + $timearray['minutes'] = 0; + $timearray['seconds'] = 0; + } + return (($timearray['year'] - 1980) << 25) | + ($timearray['mon'] << 21) | + ($timearray['mday'] << 16) | + ($timearray['hours'] << 11) | + ($timearray['minutes'] << 5) | + ($timearray['seconds'] >> 1); + } + + /** + * Extract a zip file $zn to the $to directory + */ + function Extract ( $zn, $to, $index = Array(-1) ) { + if(!@is_dir($to)) $this->_mkdir($to); + $ok = 0; + $zip = @fopen($zn,'rb'); + if(!$zip) return(-1); + $cdir = $this->ReadCentralDir($zip,$zn); + $pos_entry = $cdir['offset']; + + if(!is_array($index)){ + $index = array($index); + } + for($i=0; isset($index[$i]);$i++){ + if(intval($index[$i])!=$index[$i]||$index[$i]>$cdir['entries']) + return(-1); + } + + for ($i=0; $i<$cdir['entries']; $i++) { + @fseek($zip, $pos_entry); + $header = $this->ReadCentralFileHeaders($zip); + $header['index'] = $i; + $pos_entry = ftell($zip); + @rewind($zip); + fseek($zip, $header['offset']); + if(in_array("-1",$index)||in_array($i,$index)){ + $stat[$header['filename']]=$this->ExtractFile($header, $to, $zip); + } + } + fclose($zip); + return $stat; + } + + function ReadFileHeader($zip, $header) { + $binary_data = fread($zip, 30); + $data = unpack('vchk/vid/vversion/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len', $binary_data); + + $header['filename'] = fread($zip, $data['filename_len']); + if ($data['extra_len'] != 0) { + $header['extra'] = fread($zip, $data['extra_len']); + } else { + $header['extra'] = ''; + } + + $header['compression'] = $data['compression']; + foreach (array('size','compressed_size','crc') as $hd) { // On ODT files, these headers are 0. Keep the previous value. + if ($data[$hd] != 0) $header[$hd] = $data[$hd]; + } + $header['flag'] = $data['flag']; + $header['mdate'] = $data['mdate']; + $header['mtime'] = $data['mtime']; + + if ($header['mdate'] && $header['mtime']){ + $hour = ($header['mtime']&0xF800)>>11; + $minute = ($header['mtime']&0x07E0)>>5; + $seconde = ($header['mtime']&0x001F)*2; + $year = (($header['mdate']&0xFE00)>>9)+1980; + $month = ($header['mdate']&0x01E0)>>5; + $day = $header['mdate']&0x001F; + $header['mtime'] = mktime($hour, $minute, $seconde, $month, $day, $year); + } else { + $header['mtime'] = time(); + } + + $header['stored_filename'] = $header['filename']; + $header['status'] = "ok"; + return $header; + } + + function ReadCentralFileHeaders($zip){ + $binary_data = fread($zip, 46); + $header = unpack('vchkid/vid/vversion/vversion_extracted/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset', $binary_data); + + if ($header['filename_len'] != 0){ + $header['filename'] = fread($zip,$header['filename_len']); + }else{ + $header['filename'] = ''; + } + + if ($header['extra_len'] != 0){ + $header['extra'] = fread($zip, $header['extra_len']); + }else{ + $header['extra'] = ''; + } + + if ($header['comment_len'] != 0){ + $header['comment'] = fread($zip, $header['comment_len']); + }else{ + $header['comment'] = ''; + } + + if ($header['mdate'] && $header['mtime']) { + $hour = ($header['mtime'] & 0xF800) >> 11; + $minute = ($header['mtime'] & 0x07E0) >> 5; + $seconde = ($header['mtime'] & 0x001F)*2; + $year = (($header['mdate'] & 0xFE00) >> 9) + 1980; + $month = ($header['mdate'] & 0x01E0) >> 5; + $day = $header['mdate'] & 0x001F; + $header['mtime'] = mktime($hour, $minute, $seconde, $month, $day, $year); + } else { + $header['mtime'] = time(); + } + + $header['stored_filename'] = $header['filename']; + $header['status'] = 'ok'; + if (substr($header['filename'], -1) == '/') $header['external'] = 0x41FF0010; + + return $header; + } + + function ReadCentralDir($zip,$zip_name) { + $size = filesize($zip_name); + if ($size < 277){ + $maximum_size = $size; + } else { + $maximum_size=277; + } + + @fseek($zip, $size-$maximum_size); + $pos = ftell($zip); + $bytes = 0x00000000; + + while ($pos < $size) { + $byte = @fread($zip, 1); + $bytes=(($bytes << 8) & 0xFFFFFFFF) | Ord($byte); + if ($bytes == 0x504b0506){ + $pos++; + break; + } + $pos++; + } + + $data=unpack('vdisk/vdisk_start/vdisk_entries/ventries/Vsize/Voffset/vcomment_size', + fread($zip, 18)); + + if ($data['comment_size'] != 0){ + $centd['comment'] = fread($zip, $data['comment_size']); + } else { + $centd['comment'] = ''; + } + $centd['entries'] = $data['entries']; + $centd['disk_entries'] = $data['disk_entries']; + $centd['offset'] = $data['offset']; + $centd['disk_start'] = $data['disk_start']; + $centd['size'] = $data['size']; + $centd['disk'] = $data['disk']; + return $centd; + } + + function ExtractFile($header,$to,$zip) { + $header = $this->readfileheader($zip, $header); + + if(substr($to,-1)!="/") $to.="/"; + if(substr($header['filename'],-1)=="/") { + $this->_mkdir($to.$header['filename']); + return +2; + } + + if (!$this->_mkdir($to.dirname($header['filename']))) return (-1); + + if (!array_key_exists("external", $header) || (!($header['external']==0x41FF0010)&&!($header['external']==16))) { + if ($header['compression']==0) { + $fp = @fopen($to.$header['filename'], 'wb'); + if(!$fp) return(-1); + $size = $header['compressed_size']; + + while ($size != 0) { + $read_size = ($size < 2048 ? $size : 2048); + $buffer = fread($zip, $read_size); + $binary_data = pack('a'.$read_size, $buffer); + @fwrite($fp, $binary_data, $read_size); + $size -= $read_size; + } + fclose($fp); + touch($to.$header['filename'], $header['mtime']); + + }else{ + if (!is_dir(dirname($to.$header['filename']))) $this->_mkdir(dirname($to.$header['filename'])); + $fp = fopen($to.$header['filename'].'.gz','wb'); + if(!$fp) return(-1); + $binary_data = pack('va1a1Va1a1', 0x8b1f, Chr($header['compression']), + Chr(0x00), time(), Chr(0x00), Chr(3)); + + fwrite($fp, $binary_data, 10); + $size = $header['compressed_size']; + + while ($size != 0) { + $read_size = ($size < 1024 ? $size : 1024); + $buffer = fread($zip, $read_size); + $binary_data = pack('a'.$read_size, $buffer); + @fwrite($fp, $binary_data, $read_size); + $size -= $read_size; + } + + $binary_data = pack('VV', $header['crc'], $header['size']); + fwrite($fp, $binary_data,8); + fclose($fp); + + $gzp = @gzopen($to.$header['filename'].'.gz','rb'); + if(!$gzp){ + @gzclose($gzp); + @unlink($to.$header['filename']); + die("Archive is compressed whereas ZLIB is not enabled."); + } + $fp = @fopen($to.$header['filename'],'wb'); + if(!$fp) return(-1); + $size = $header['size']; + + while ($size != 0) { + $read_size = ($size < 2048 ? $size : 2048); + $buffer = gzread($gzp, $read_size); + $binary_data = pack('a'.$read_size, $buffer); + @fwrite($fp, $binary_data, $read_size); + $size -= $read_size; + } + fclose($fp); + gzclose($gzp); + + touch($to.$header['filename'], $header['mtime']); + @unlink($to.$header['filename'].'.gz'); + } + } + return true; + } + + /** + * centralize mkdir calls and use dokuwiki io functions + * + * @author Christopher Smith + */ + function _mkdir($d) { + return io_mkdir_p($d); + } + + + function ExtractStr($zn, $name) { + $ok = 0; + $zip = @fopen($zn,'rb'); + if(!$zip) return(null); + $cdir = $this->ReadCentralDir($zip,$zn); + $pos_entry = $cdir['offset']; + + for ($i=0; $i<$cdir['entries']; $i++) { + @fseek($zip, $pos_entry); + $header = $this->ReadCentralFileHeaders($zip); + $header['index'] = $i; + $pos_entry = ftell($zip); + @rewind($zip); + fseek($zip, $header['offset']); + if ($name == $header['stored_filename'] || $name == $header['filename']) { + $str = $this->ExtractStrFile($header, $zip); + fclose($zip); + return $str; + } + + } + fclose($zip); + return null; + } + + function ExtractStrFile($header,$zip) { + $hdr = $this->readfileheader($zip); + $binary_data = ''; + if (!($header['external']==0x41FF0010) && !($header['external']==16)) { + if ($header['compression']==0) { + while ($size != 0) { + $read_size = ($size < 2048 ? $size : 2048); + $buffer = fread($zip, $read_size); + $binary_data .= pack('a'.$read_size, $buffer); + $size -= $read_size; + } + return $binary_data; + } else { + $size = $header['compressed_size']; + if ($size == 0) { + return ''; + } + //Just in case + if ($size > ($this->_ret_bytes(ini_get('memory_limit'))/2)) { + die("Compressed file is to huge to be uncompress in memory."); + } + while ($size != 0) + { + $read_size = ($size < 2048 ? $size : 2048); + $buffer = fread($zip, $read_size); + $binary_data .= pack('a'.$read_size, $buffer); + $size -= $read_size; + } + $str = gzinflate($binary_data, $header['size']); + if ($header['crc'] == crc32($str)) { + return $str; + } else { + die("Crc Error"); + } + } + } + return null; + } + + function _ret_bytes($val) { + $val = trim($val); + $last = $val{strlen($val)-1}; + switch($last) { + case 'k': + case 'K': + return (int) $val * 1024; + break; + case 'm': + case 'M': + return (int) $val * 1048576; + break; + default: + return $val; + } + } +} + diff --git a/inc/actions.php b/inc/actions.php new file mode 100644 index 0000000..4a2e200 --- /dev/null +++ b/inc/actions.php @@ -0,0 +1,762 @@ + + */ + +if(!defined('DOKU_INC')) die('meh.'); + +/** + * Call the needed action handlers + * + * @author Andreas Gohr + * @triggers ACTION_ACT_PREPROCESS + * @triggers ACTION_HEADERS_SEND + */ +function act_dispatch(){ + global $ACT; + global $ID; + global $INFO; + global $QUERY; + global $lang; + global $conf; + + $preact = $ACT; + + // give plugins an opportunity to process the action + $evt = new Doku_Event('ACTION_ACT_PREPROCESS',$ACT); + if ($evt->advise_before()) { + + //sanitize $ACT + $ACT = act_clean($ACT); + + //check if searchword was given - else just show + $s = cleanID($QUERY); + if($ACT == 'search' && empty($s)){ + $ACT = 'show'; + } + + //login stuff + if(in_array($ACT,array('login','logout'))){ + $ACT = act_auth($ACT); + } + + //check if user is asking to (un)subscribe a page + if($ACT == 'subscribe') { + try { + $ACT = act_subscription($ACT); + } catch (Exception $e) { + msg($e->getMessage(), -1); + } + } + + //display some infos + if($ACT == 'check'){ + check(); + $ACT = 'show'; + } + + //check permissions + $ACT = act_permcheck($ACT); + + //sitemap + if ($ACT == 'sitemap'){ + $ACT = act_sitemap($ACT); + } + + //register + if($ACT == 'register' && $_POST['save'] && register()){ + $ACT = 'login'; + } + + if ($ACT == 'resendpwd' && act_resendpwd()) { + $ACT = 'login'; + } + + //update user profile + if ($ACT == 'profile') { + if(!$_SERVER['REMOTE_USER']) { + $ACT = 'login'; + } else { + if(updateprofile()) { + msg($lang['profchanged'],1); + $ACT = 'show'; + } + } + } + + //revert + if($ACT == 'revert'){ + if(checkSecurityToken()){ + $ACT = act_revert($ACT); + }else{ + $ACT = 'show'; + } + } + + //save + if($ACT == 'save'){ + if(checkSecurityToken()){ + $ACT = act_save($ACT); + }else{ + $ACT = 'preview'; + } + } + + //cancel conflicting edit + if($ACT == 'cancel') + $ACT = 'show'; + + //draft deletion + if($ACT == 'draftdel') + $ACT = act_draftdel($ACT); + + //draft saving on preview + if($ACT == 'preview') + $ACT = act_draftsave($ACT); + + //edit + if(in_array($ACT, array('edit', 'preview', 'recover'))) { + $ACT = act_edit($ACT); + }else{ + unlock($ID); //try to unlock + } + + //handle export + if(substr($ACT,0,7) == 'export_') + $ACT = act_export($ACT); + + //handle admin tasks + if($ACT == 'admin'){ + // retrieve admin plugin name from $_REQUEST['page'] + if (!empty($_REQUEST['page'])) { + $pluginlist = plugin_list('admin'); + if (in_array($_REQUEST['page'], $pluginlist)) { + // attempt to load the plugin + if ($plugin =& plugin_load('admin',$_REQUEST['page']) !== null){ + if($plugin->forAdminOnly() && !$INFO['isadmin']){ + // a manager tried to load a plugin that's for admins only + unset($_REQUEST['page']); + msg('For admins only',-1); + }else{ + $plugin->handle(); + } + } + } + } + } + + // check permissions again - the action may have changed + $ACT = act_permcheck($ACT); + } // end event ACTION_ACT_PREPROCESS default action + $evt->advise_after(); + // Make sure plugs can handle 'denied' + if($conf['send404'] && $ACT == 'denied') { + header('HTTP/1.0 403 Forbidden'); + } + unset($evt); + + // when action 'show', the intial not 'show' and POST, do a redirect + if($ACT == 'show' && $preact != 'show' && strtolower($_SERVER['REQUEST_METHOD']) == 'post'){ + act_redirect($ID,$preact); + } + + global $INFO; + global $conf; + global $license; + + //call template FIXME: all needed vars available? + $headers[] = 'Content-Type: text/html; charset=utf-8'; + trigger_event('ACTION_HEADERS_SEND',$headers,'act_sendheaders'); + + include(template('main.php')); + // output for the commands is now handled in inc/templates.php + // in function tpl_content() +} + +function act_sendheaders($headers) { + foreach ($headers as $hdr) header($hdr); +} + +/** + * Sanitize the action command + * + * Add all allowed commands here. + * + * @author Andreas Gohr + */ +function act_clean($act){ + global $lang; + global $conf; + global $INFO; + + // check if the action was given as array key + if(is_array($act)){ + list($act) = array_keys($act); + } + + //remove all bad chars + $act = strtolower($act); + $act = preg_replace('/[^1-9a-z_]+/','',$act); + + if($act == 'export_html') $act = 'export_xhtml'; + if($act == 'export_htmlbody') $act = 'export_xhtmlbody'; + + if($act === '') $act = 'show'; + + // check if action is disabled + if(!actionOK($act)){ + msg('Command disabled: '.htmlspecialchars($act),-1); + return 'show'; + } + + //disable all acl related commands if ACL is disabled + if(!$conf['useacl'] && in_array($act,array('login','logout','register','admin', + 'subscribe','unsubscribe','profile','revert', + 'resendpwd'))){ + msg('Command unavailable: '.htmlspecialchars($act),-1); + return 'show'; + } + + //is there really a draft? + if($act == 'draft' && !file_exists($INFO['draft'])) return 'edit'; + + if(!in_array($act,array('login','logout','register','save','cancel','edit','draft', + 'preview','search','show','check','index','revisions', + 'diff','recent','backlink','admin','subscribe','revert', + 'unsubscribe','profile','resendpwd','recover', + 'draftdel','sitemap','media')) && substr($act,0,7) != 'export_' ) { + msg('Command unknown: '.htmlspecialchars($act),-1); + return 'show'; + } + return $act; +} + +/** + * Run permissionchecks + * + * @author Andreas Gohr + */ +function act_permcheck($act){ + global $INFO; + global $conf; + + if(in_array($act,array('save','preview','edit','recover'))){ + if($INFO['exists']){ + if($act == 'edit'){ + //the edit function will check again and do a source show + //when no AUTH_EDIT available + $permneed = AUTH_READ; + }else{ + $permneed = AUTH_EDIT; + } + }else{ + $permneed = AUTH_CREATE; + } + }elseif(in_array($act,array('login','search','recent','profile','index', 'sitemap'))){ + $permneed = AUTH_NONE; + }elseif($act == 'revert'){ + $permneed = AUTH_ADMIN; + if($INFO['ismanager']) $permneed = AUTH_EDIT; + }elseif($act == 'register'){ + $permneed = AUTH_NONE; + }elseif($act == 'resendpwd'){ + $permneed = AUTH_NONE; + }elseif($act == 'admin'){ + if($INFO['ismanager']){ + // if the manager has the needed permissions for a certain admin + // action is checked later + $permneed = AUTH_READ; + }else{ + $permneed = AUTH_ADMIN; + } + }else{ + $permneed = AUTH_READ; + } + if($INFO['perm'] >= $permneed) return $act; + + return 'denied'; +} + +/** + * Handle 'draftdel' + * + * Deletes the draft for the current page and user + */ +function act_draftdel($act){ + global $INFO; + @unlink($INFO['draft']); + $INFO['draft'] = null; + return 'show'; +} + +/** + * Saves a draft on preview + * + * @todo this currently duplicates code from ajax.php :-/ + */ +function act_draftsave($act){ + global $INFO; + global $ID; + global $conf; + if($conf['usedraft'] && $_POST['wikitext']){ + $draft = array('id' => $ID, + 'prefix' => substr($_POST['prefix'], 0, -1), + 'text' => $_POST['wikitext'], + 'suffix' => $_POST['suffix'], + 'date' => (int) $_POST['date'], + 'client' => $INFO['client'], + ); + $cname = getCacheName($draft['client'].$ID,'.draft'); + if(io_saveFile($cname,serialize($draft))){ + $INFO['draft'] = $cname; + } + } + return $act; +} + +/** + * Handle 'save' + * + * Checks for spam and conflicts and saves the page. + * Does a redirect to show the page afterwards or + * returns a new action. + * + * @author Andreas Gohr + */ +function act_save($act){ + global $ID; + global $DATE; + global $PRE; + global $TEXT; + global $SUF; + global $SUM; + global $lang; + global $INFO; + + //spam check + if(checkwordblock()) { + msg($lang['wordblock'], -1); + return 'edit'; + } + //conflict check + if($DATE != 0 && $INFO['meta']['date']['modified'] > $DATE ) + return 'conflict'; + + //save it + saveWikiText($ID,con($PRE,$TEXT,$SUF,1),$SUM,$_REQUEST['minor']); //use pretty mode for con + //unlock it + unlock($ID); + + //delete draft + act_draftdel($act); + session_write_close(); + + // when done, show page + return 'show'; +} + +/** + * Revert to a certain revision + * + * @author Andreas Gohr + */ +function act_revert($act){ + global $ID; + global $REV; + global $lang; + // FIXME $INFO['writable'] currently refers to the attic version + // global $INFO; + // if (!$INFO['writable']) { + // return 'show'; + // } + + // when no revision is given, delete current one + // FIXME this feature is not exposed in the GUI currently + $text = ''; + $sum = $lang['deleted']; + if($REV){ + $text = rawWiki($ID,$REV); + if(!$text) return 'show'; //something went wrong + $sum = $lang['restored']; + } + + // spam check + + if (checkwordblock($text)) { + msg($lang['wordblock'], -1); + return 'edit'; + } + + saveWikiText($ID,$text,$sum,false); + msg($sum,1); + + //delete any draft + act_draftdel($act); + session_write_close(); + + // when done, show current page + $_SERVER['REQUEST_METHOD'] = 'post'; //should force a redirect + $REV = ''; + return 'show'; +} + +/** + * Do a redirect after receiving post data + * + * Tries to add the section id as hash mark after section editing + */ +function act_redirect($id,$preact){ + global $PRE; + global $TEXT; + + $opts = array( + 'id' => $id, + 'preact' => $preact + ); + //get section name when coming from section edit + if($PRE && preg_match('/^\s*==+([^=\n]+)/',$TEXT,$match)){ + $check = false; //Byref + $opts['fragment'] = sectionID($match[0], $check); + } + + trigger_event('ACTION_SHOW_REDIRECT',$opts,'act_redirect_execute'); +} + +function act_redirect_execute($opts){ + $go = wl($opts['id'],'',true); + if(isset($opts['fragment'])) $go .= '#'.$opts['fragment']; + + //show it + send_redirect($go); +} + +/** + * Handle 'login', 'logout' + * + * @author Andreas Gohr + */ +function act_auth($act){ + global $ID; + global $INFO; + + //already logged in? + if(isset($_SERVER['REMOTE_USER']) && $act=='login'){ + return 'show'; + } + + //handle logout + if($act=='logout'){ + $lockedby = checklock($ID); //page still locked? + if($lockedby == $_SERVER['REMOTE_USER']) + unlock($ID); //try to unlock + + // do the logout stuff + auth_logoff(); + + // rebuild info array + $INFO = pageinfo(); + + act_redirect($ID,'login'); + } + + return $act; +} + +/** + * Handle 'edit', 'preview', 'recover' + * + * @author Andreas Gohr + */ +function act_edit($act){ + global $ID; + global $INFO; + + global $TEXT; + global $RANGE; + global $PRE; + global $SUF; + global $REV; + global $SUM; + global $lang; + global $DATE; + + if (!isset($TEXT)) { + if ($INFO['exists']) { + if ($RANGE) { + list($PRE,$TEXT,$SUF) = rawWikiSlices($RANGE,$ID,$REV); + } else { + $TEXT = rawWiki($ID,$REV); + } + } else { + $TEXT = pageTemplate($ID); + } + } + + //set summary default + if(!$SUM){ + if($REV){ + $SUM = $lang['restored']; + }elseif(!$INFO['exists']){ + $SUM = $lang['created']; + } + } + + // Use the date of the newest revision, not of the revision we edit + // This is used for conflict detection + if(!$DATE) $DATE = $INFO['meta']['date']['modified']; + + //check if locked by anyone - if not lock for my self + //do not lock when the user can't edit anyway + if ($INFO['writable']) { + $lockedby = checklock($ID); + if($lockedby) return 'locked'; + + lock($ID); + } + + return $act; +} + +/** + * Export a wiki page for various formats + * + * Triggers ACTION_EXPORT_POSTPROCESS + * + * Event data: + * data['id'] -- page id + * data['mode'] -- requested export mode + * data['headers'] -- export headers + * data['output'] -- export output + * + * @author Andreas Gohr + * @author Michael Klier + */ +function act_export($act){ + global $ID; + global $REV; + global $conf; + global $lang; + + $pre = ''; + $post = ''; + $output = ''; + $headers = array(); + + // search engines: never cache exported docs! (Google only currently) + $headers['X-Robots-Tag'] = 'noindex'; + + $mode = substr($act,7); + switch($mode) { + case 'raw': + $headers['Content-Type'] = 'text/plain; charset=utf-8'; + $headers['Content-Disposition'] = 'attachment; filename='.noNS($ID).'.txt'; + $output = rawWiki($ID,$REV); + break; + case 'xhtml': + $pre .= '' . DOKU_LF; + $pre .= '' . DOKU_LF; + $pre .= '' . DOKU_LF; + $pre .= ' ' . DOKU_LF; + $pre .= ' '.$ID.'' . DOKU_LF; + + // get metaheaders + ob_start(); + tpl_metaheaders(); + $pre .= ob_get_clean(); + + $pre .= '' . DOKU_LF; + $pre .= '' . DOKU_LF; + $pre .= '
' . DOKU_LF; + + // get toc + $pre .= tpl_toc(true); + + $headers['Content-Type'] = 'text/html; charset=utf-8'; + $output = p_wiki_xhtml($ID,$REV,false); + + $post .= '
' . DOKU_LF; + $post .= '' . DOKU_LF; + $post .= '' . DOKU_LF; + break; + case 'xhtmlbody': + $headers['Content-Type'] = 'text/html; charset=utf-8'; + $output = p_wiki_xhtml($ID,$REV,false); + break; + default: + $output = p_cached_output(wikiFN($ID,$REV), $mode); + $headers = p_get_metadata($ID,"format $mode"); + break; + } + + // prepare event data + $data = array(); + $data['id'] = $ID; + $data['mode'] = $mode; + $data['headers'] = $headers; + $data['output'] =& $output; + + trigger_event('ACTION_EXPORT_POSTPROCESS', $data); + + if(!empty($data['output'])){ + if(is_array($data['headers'])) foreach($data['headers'] as $key => $val){ + header("$key: $val"); + } + print $pre.$data['output'].$post; + exit; + } + return 'show'; +} + +/** + * Handle sitemap delivery + * + * @author Michael Hamann + */ +function act_sitemap($act) { + global $conf; + + if ($conf['sitemap'] < 1 || !is_numeric($conf['sitemap'])) { + header("HTTP/1.0 404 Not Found"); + print "Sitemap generation is disabled."; + exit; + } + + $sitemap = Sitemapper::getFilePath(); + if(strrchr($sitemap, '.') === '.gz'){ + $mime = 'application/x-gzip'; + }else{ + $mime = 'application/xml; charset=utf-8'; + } + + // Check if sitemap file exists, otherwise create it + if (!is_readable($sitemap)) { + Sitemapper::generate(); + } + + if (is_readable($sitemap)) { + // Send headers + header('Content-Type: '.$mime); + header('Content-Disposition: attachment; filename='.basename($sitemap)); + + http_conditionalRequest(filemtime($sitemap)); + + // Send file + //use x-sendfile header to pass the delivery to compatible webservers + if (http_sendfile($sitemap)) exit; + + readfile($sitemap); + exit; + } + + header("HTTP/1.0 500 Internal Server Error"); + print "Could not read the sitemap file - bad permissions?"; + exit; +} + +/** + * Handle page 'subscribe' + * + * Throws exception on error. + * + * @author Adrian Lang + */ +function act_subscription($act){ + global $lang; + global $INFO; + global $ID; + + // subcriptions work for logged in users only + if(!$_SERVER['REMOTE_USER']) return 'show'; + + // get and preprocess data. + $params = array(); + foreach(array('target', 'style', 'action') as $param) { + if (isset($_REQUEST["sub_$param"])) { + $params[$param] = $_REQUEST["sub_$param"]; + } + } + + // any action given? if not just return and show the subscription page + if(!$params['action'] || !checkSecurityToken()) return $act; + + // Handle POST data, may throw exception. + trigger_event('ACTION_HANDLE_SUBSCRIBE', $params, 'subscription_handle_post'); + + $target = $params['target']; + $style = $params['style']; + $data = $params['data']; + $action = $params['action']; + + // Perform action. + if (!subscription_set($_SERVER['REMOTE_USER'], $target, $style, $data)) { + throw new Exception(sprintf($lang["subscr_{$action}_error"], + hsc($INFO['userinfo']['name']), + prettyprint_id($target))); + } + msg(sprintf($lang["subscr_{$action}_success"], hsc($INFO['userinfo']['name']), + prettyprint_id($target)), 1); + act_redirect($ID, $act); + + // Assure that we have valid data if act_redirect somehow fails. + $INFO['subscribed'] = get_info_subscribed(); + return 'show'; +} + +/** + * Validate POST data + * + * Validates POST data for a subscribe or unsubscribe request. This is the + * default action for the event ACTION_HANDLE_SUBSCRIBE. + * + * @author Adrian Lang + */ +function subscription_handle_post(&$params) { + global $INFO; + global $lang; + + // Get and validate parameters. + if (!isset($params['target'])) { + throw new Exception('no subscription target given'); + } + $target = $params['target']; + $valid_styles = array('every', 'digest'); + if (substr($target, -1, 1) === ':') { + // Allow “list” subscribe style since the target is a namespace. + $valid_styles[] = 'list'; + } + $style = valid_input_set('style', $valid_styles, $params, + 'invalid subscription style given'); + $action = valid_input_set('action', array('subscribe', 'unsubscribe'), + $params, 'invalid subscription action given'); + + // Check other conditions. + if ($action === 'subscribe') { + if ($INFO['userinfo']['mail'] === '') { + throw new Exception($lang['subscr_subscribe_noaddress']); + } + } elseif ($action === 'unsubscribe') { + $is = false; + foreach($INFO['subscribed'] as $subscr) { + if ($subscr['target'] === $target) { + $is = true; + } + } + if ($is === false) { + throw new Exception(sprintf($lang['subscr_not_subscribed'], + $_SERVER['REMOTE_USER'], + prettyprint_id($target))); + } + // subscription_set deletes a subscription if style = null. + $style = null; + } + + $data = in_array($style, array('list', 'digest')) ? time() : null; + $params = compact('target', 'style', 'data', 'action'); +} + +//Setup VIM: ex: et ts=2 : diff --git a/inc/adLDAP.php b/inc/adLDAP.php new file mode 100644 index 0000000..a64096b --- /dev/null +++ b/inc/adLDAP.php @@ -0,0 +1,2422 @@ +_account_suffix = $_account_suffix; + } + + /** + * Get the account suffix + * + * @return string + */ + public function get_account_suffix() + { + return $this->_account_suffix; + } + + /** + * Set the domain controllers array + * + * @param array $_domain_controllers + * @return void + */ + public function set_domain_controllers(array $_domain_controllers) + { + $this->_domain_controllers = $_domain_controllers; + } + + /** + * Get the list of domain controllers + * + * @return void + */ + public function get_domain_controllers() + { + return $this->_domain_controllers; + } + + /** + * Set the username of an account with higher priviledges + * + * @param string $_ad_username + * @return void + */ + public function set_ad_username($_ad_username) + { + $this->_ad_username = $_ad_username; + } + + /** + * Get the username of the account with higher priviledges + * + * This will throw an exception for security reasons + */ + public function get_ad_username() + { + throw new adLDAPException('For security reasons you cannot access the domain administrator account details'); + } + + /** + * Set the password of an account with higher priviledges + * + * @param string $_ad_password + * @return void + */ + public function set_ad_password($_ad_password) + { + $this->_ad_password = $_ad_password; + } + + /** + * Get the password of the account with higher priviledges + * + * This will throw an exception for security reasons + */ + public function get_ad_password() + { + throw new adLDAPException('For security reasons you cannot access the domain administrator account details'); + } + + /** + * Set whether to detect the true primary group + * + * @param bool $_real_primary_group + * @return void + */ + public function set_real_primarygroup($_real_primarygroup) + { + $this->_real_primarygroup = $_real_primarygroup; + } + + /** + * Get the real primary group setting + * + * @return bool + */ + public function get_real_primarygroup() + { + return $this->_real_primarygroup; + } + + /** + * Set whether to use SSL + * + * @param bool $_use_ssl + * @return void + */ + public function set_use_ssl($_use_ssl) + { + $this->_use_ssl = $_use_ssl; + } + + /** + * Get the SSL setting + * + * @return bool + */ + public function get_use_ssl() + { + return $this->_use_ssl; + } + + /** + * Set whether to use TLS + * + * @param bool $_use_tls + * @return void + */ + public function set_use_tls($_use_tls) + { + $this->_use_tls = $_use_tls; + } + + /** + * Get the TLS setting + * + * @return bool + */ + public function get_use_tls() + { + return $this->_use_tls; + } + + /** + * Set whether to lookup recursive groups + * + * @param bool $_recursive_groups + * @return void + */ + public function set_recursive_groups($_recursive_groups) + { + $this->_recursive_groups = $_recursive_groups; + } + + /** + * Get the recursive groups setting + * + * @return bool + */ + public function get_recursive_groups() + { + return $this->_recursive_groups; + } + + /** + * Default Constructor + * + * Tries to bind to the AD domain over LDAP or LDAPs + * + * @param array $options Array of options to pass to the constructor + * @throws Exception - if unable to bind to Domain Controller + * @return bool + */ + function __construct($options=array()){ + // You can specifically overide any of the default configuration options setup above + if (count($options)>0){ + if (array_key_exists("account_suffix",$options)){ $this->_account_suffix=$options["account_suffix"]; } + if (array_key_exists("base_dn",$options)){ $this->_base_dn=$options["base_dn"]; } + if (array_key_exists("domain_controllers",$options)){ $this->_domain_controllers=$options["domain_controllers"]; } + if (array_key_exists("ad_username",$options)){ $this->_ad_username=$options["ad_username"]; } + if (array_key_exists("ad_password",$options)){ $this->_ad_password=$options["ad_password"]; } + if (array_key_exists("real_primarygroup",$options)){ $this->_real_primarygroup=$options["real_primarygroup"]; } + if (array_key_exists("use_ssl",$options)){ $this->_use_ssl=$options["use_ssl"]; } + if (array_key_exists("use_tls",$options)){ $this->_use_tls=$options["use_tls"]; } + if (array_key_exists("recursive_groups",$options)){ $this->_recursive_groups=$options["recursive_groups"]; } + } + + if ($this->ldap_supported() === false) { + throw new adLDAPException('No LDAP support for PHP. See: http://www.php.net/ldap'); + } + + return $this->connect(); + } + + /** + * Default Destructor + * + * Closes the LDAP connection + * + * @return void + */ + function __destruct(){ $this->close(); } + + /** + * Connects and Binds to the Domain Controller + * + * @return bool + */ + public function connect() { + // Connect to the AD/LDAP server as the username/password + $dc=$this->random_controller(); + if ($this->_use_ssl){ + $this->_conn = ldap_connect("ldaps://".$dc, 636); + } else { + $this->_conn = ldap_connect($dc); + } + + // Set some ldap options for talking to AD + ldap_set_option($this->_conn, LDAP_OPT_PROTOCOL_VERSION, 3); + ldap_set_option($this->_conn, LDAP_OPT_REFERRALS, 0); + + if ($this->_use_tls) { + ldap_start_tls($this->_conn); + } + + // Bind as a domain admin if they've set it up + if ($this->_ad_username!=NULL && $this->_ad_password!=NULL){ + $this->_bind = @ldap_bind($this->_conn,$this->_ad_username.$this->_account_suffix,$this->_ad_password); + if (!$this->_bind){ + if ($this->_use_ssl && !$this->_use_tls){ + // If you have problems troubleshooting, remove the @ character from the ldap_bind command above to get the actual error message + throw new adLDAPException('Bind to Active Directory failed. Either the LDAPs connection failed or the login credentials are incorrect. AD said: ' . $this->get_last_error()); + } else { + throw new adLDAPException('Bind to Active Directory failed. Check the login credentials and/or server details. AD said: ' . $this->get_last_error()); + } + } + } + + if ($this->_base_dn == NULL) { + $this->_base_dn = $this->find_base_dn(); + } + + return (true); + } + + /** + * Closes the LDAP connection + * + * @return void + */ + public function close() { + ldap_close ($this->_conn); + } + + /** + * Validate a user's login credentials + * + * @param string $username A user's AD username + * @param string $password A user's AD password + * @param bool optional $prevent_rebind + * @return bool + */ + public function authenticate($username, $password, $prevent_rebind = false) { + // Prevent null binding + if ($username === NULL || $password === NULL) { return false; } + if (empty($username) || empty($password)) { return false; } + + // Bind as the user + $ret = true; + $this->_bind = @ldap_bind($this->_conn, $username . $this->_account_suffix, $password); + if (!$this->_bind){ $ret = false; } + + // Cnce we've checked their details, kick back into admin mode if we have it + if ($this->_ad_username !== NULL && !$prevent_rebind) { + $this->_bind = @ldap_bind($this->_conn, $this->_ad_username . $this->_account_suffix , $this->_ad_password); + if (!$this->_bind){ + // This should never happen in theory + throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->get_last_error()); + } + } + + return $ret; + } + + //***************************************************************************************************************** + // GROUP FUNCTIONS + + /** + * Add a group to a group + * + * @param string $parent The parent group name + * @param string $child The child group name + * @return bool + */ + public function group_add_group($parent,$child){ + + // Find the parent group's dn + $parent_group=$this->group_info($parent,array("cn")); + if ($parent_group[0]["dn"]===NULL){ return (false); } + $parent_dn=$parent_group[0]["dn"]; + + // Find the child group's dn + $child_group=$this->group_info($child,array("cn")); + if ($child_group[0]["dn"]===NULL){ return (false); } + $child_dn=$child_group[0]["dn"]; + + $add=array(); + $add["member"] = $child_dn; + + $result=@ldap_mod_add($this->_conn,$parent_dn,$add); + if ($result==false){ return (false); } + return (true); + } + + /** + * Add a user to a group + * + * @param string $group The group to add the user to + * @param string $user The user to add to the group + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return bool + */ + public function group_add_user($group,$user,$isGUID=false){ + // Adding a user is a bit fiddly, we need to get the full DN of the user + // and add it using the full DN of the group + + // Find the user's dn + $user_dn=$this->user_dn($user,$isGUID); + if ($user_dn===false){ return (false); } + + // Find the group's dn + $group_info=$this->group_info($group,array("cn")); + if ($group_info[0]["dn"]===NULL){ return (false); } + $group_dn=$group_info[0]["dn"]; + + $add=array(); + $add["member"] = $user_dn; + + $result=@ldap_mod_add($this->_conn,$group_dn,$add); + if ($result==false){ return (false); } + return (true); + } + + /** + * Add a contact to a group + * + * @param string $group The group to add the contact to + * @param string $contact_dn The DN of the contact to add + * @return bool + */ + public function group_add_contact($group,$contact_dn){ + // To add a contact we take the contact's DN + // and add it using the full DN of the group + + // Find the group's dn + $group_info=$this->group_info($group,array("cn")); + if ($group_info[0]["dn"]===NULL){ return (false); } + $group_dn=$group_info[0]["dn"]; + + $add=array(); + $add["member"] = $contact_dn; + + $result=@ldap_mod_add($this->_conn,$group_dn,$add); + if ($result==false){ return (false); } + return (true); + } + + /** + * Create a group + * + * @param array $attributes Default attributes of the group + * @return bool + */ + public function group_create($attributes){ + if (!is_array($attributes)){ return ("Attributes must be an array"); } + if (!array_key_exists("group_name",$attributes)){ return ("Missing compulsory field [group_name]"); } + if (!array_key_exists("container",$attributes)){ return ("Missing compulsory field [container]"); } + if (!array_key_exists("description",$attributes)){ return ("Missing compulsory field [description]"); } + if (!is_array($attributes["container"])){ return ("Container attribute must be an array."); } + $attributes["container"]=array_reverse($attributes["container"]); + + //$member_array = array(); + //$member_array[0] = "cn=user1,cn=Users,dc=yourdomain,dc=com"; + //$member_array[1] = "cn=administrator,cn=Users,dc=yourdomain,dc=com"; + + $add=array(); + $add["cn"] = $attributes["group_name"]; + $add["samaccountname"] = $attributes["group_name"]; + $add["objectClass"] = "Group"; + $add["description"] = $attributes["description"]; + //$add["member"] = $member_array; UNTESTED + + $container="OU=".implode(",OU=",$attributes["container"]); + $result=ldap_add($this->_conn,"CN=".$add["cn"].", ".$container.",".$this->_base_dn,$add); + if ($result!=true){ return (false); } + + return (true); + } + + /** + * Remove a group from a group + * + * @param string $parent The parent group name + * @param string $child The child group name + * @return bool + */ + public function group_del_group($parent,$child){ + + // Find the parent dn + $parent_group=$this->group_info($parent,array("cn")); + if ($parent_group[0]["dn"]===NULL){ return (false); } + $parent_dn=$parent_group[0]["dn"]; + + // Find the child dn + $child_group=$this->group_info($child,array("cn")); + if ($child_group[0]["dn"]===NULL){ return (false); } + $child_dn=$child_group[0]["dn"]; + + $del=array(); + $del["member"] = $child_dn; + + $result=@ldap_mod_del($this->_conn,$parent_dn,$del); + if ($result==false){ return (false); } + return (true); + } + + /** + * Remove a user from a group + * + * @param string $group The group to remove a user from + * @param string $user The AD user to remove from the group + * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @return bool + */ + public function group_del_user($group,$user,$isGUID=false){ + + // Find the parent dn + $group_info=$this->group_info($group,array("cn")); + if ($group_info[0]["dn"]===NULL){ return (false); } + $group_dn=$group_info[0]["dn"]; + + // Find the users dn + $user_dn=$this->user_dn($user,$isGUID); + if ($user_dn===false){ return (false); } + + $del=array(); + $del["member"] = $user_dn; + + $result=@ldap_mod_del($this->_conn,$group_dn,$del); + if ($result==false){ return (false); } + return (true); + } + + /** + * Remove a contact from a group + * + * @param string $group The group to remove a user from + * @param string $contact_dn The DN of a contact to remove from the group + * @return bool + */ + public function group_del_contact($group,$contact_dn){ + + // Find the parent dn + $group_info=$this->group_info($group,array("cn")); + if ($group_info[0]["dn"]===NULL){ return (false); } + $group_dn=$group_info[0]["dn"]; + + $del=array(); + $del["member"] = $contact_dn; + + $result=@ldap_mod_del($this->_conn,$group_dn,$del); + if ($result==false){ return (false); } + return (true); + } + + /** + * Return a list of groups in a group + * + * @param string $group The group to query + * @param bool $recursive Recursively get groups + * @return array + */ + public function groups_in_group($group, $recursive = NULL){ + if (!$this->_bind){ return (false); } + if ($recursive===NULL){ $recursive=$this->_recursive_groups; } // Use the default option if they haven't set it + + // Search the directory for the members of a group + $info=$this->group_info($group,array("member","cn")); + $groups=$info[0]["member"]; + if (!is_array($groups)) { + return (false); + } + + $group_array=array(); + + for ($i=0; $i<$groups["count"]; $i++){ + $filter="(&(objectCategory=group)(distinguishedName=".$this->ldap_slashes($groups[$i])."))"; + $fields = array("samaccountname", "distinguishedname", "objectClass"); + $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); + $entries = ldap_get_entries($this->_conn, $sr); + + // not a person, look for a group + if ($entries['count'] == 0 && $recursive == true) { + $filter="(&(objectCategory=group)(distinguishedName=".$this->ldap_slashes($groups[$i])."))"; + $fields = array("distinguishedname"); + $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); + $entries = ldap_get_entries($this->_conn, $sr); + if (!isset($entries[0]['distinguishedname'][0])) { + continue; + } + $sub_groups = $this->groups_in_group($entries[0]['distinguishedname'][0], $recursive); + if (is_array($sub_groups)) { + $group_array = array_merge($group_array, $sub_groups); + $group_array = array_unique($group_array); + } + continue; + } + + $group_array[] = $entries[0]['distinguishedname'][0]; + } + return ($group_array); + } + + /** + * Return a list of members in a group + * + * @param string $group The group to query + * @param bool $recursive Recursively get group members + * @return array + */ + public function group_members($group, $recursive = NULL){ + if (!$this->_bind){ return (false); } + if ($recursive===NULL){ $recursive=$this->_recursive_groups; } // Use the default option if they haven't set it + // Search the directory for the members of a group + $info=$this->group_info($group,array("member","cn")); + $users=$info[0]["member"]; + if (!is_array($users)) { + return (false); + } + + $user_array=array(); + + for ($i=0; $i<$users["count"]; $i++){ + $filter="(&(objectCategory=person)(distinguishedName=".$this->ldap_slashes($users[$i])."))"; + $fields = array("samaccountname", "distinguishedname", "objectClass"); + $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); + $entries = ldap_get_entries($this->_conn, $sr); + + // not a person, look for a group + if ($entries['count'] == 0 && $recursive == true) { + $filter="(&(objectCategory=group)(distinguishedName=".$this->ldap_slashes($users[$i])."))"; + $fields = array("samaccountname"); + $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); + $entries = ldap_get_entries($this->_conn, $sr); + if (!isset($entries[0]['samaccountname'][0])) { + continue; + } + $sub_users = $this->group_members($entries[0]['samaccountname'][0], $recursive); + if (is_array($sub_users)) { + $user_array = array_merge($user_array, $sub_users); + $user_array = array_unique($user_array); + } + continue; + } + + if ($entries[0]['samaccountname'][0] === NULL && $entries[0]['distinguishedname'][0] !== NULL) { + $user_array[] = $entries[0]['distinguishedname'][0]; + } + elseif ($entries[0]['samaccountname'][0] !== NULL) { + $user_array[] = $entries[0]['samaccountname'][0]; + } + } + return ($user_array); + } + + /** + * Group Information. Returns an array of information about a group. + * The group name is case sensitive + * + * @param string $group_name The group name to retrieve info about + * @param array $fields Fields to retrieve + * @return array + */ + public function group_info($group_name,$fields=NULL){ + if ($group_name===NULL){ return (false); } + if (!$this->_bind){ return (false); } + + if (stristr($group_name, '+')) { + $group_name=stripslashes($group_name); + } + + $filter="(&(objectCategory=group)(name=".$this->ldap_slashes($group_name)."))"; + //echo ($filter."!!!
"); + if ($fields===NULL){ $fields=array("member","memberof","cn","description","distinguishedname","objectcategory","samaccountname"); } + $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); + $entries = ldap_get_entries($this->_conn, $sr); + //print_r($entries); + return ($entries); + } + + /** + * Return a complete list of "groups in groups" + * + * @param string $group The group to get the list from + * @return array + */ + public function recursive_groups($group){ + if ($group===NULL){ return (false); } + + $ret_groups=array(); + + $groups=$this->group_info($group,array("memberof")); + if (isset($groups[0]["memberof"]) && is_array($groups[0]["memberof"])) { + $groups=$groups[0]["memberof"]; + + if ($groups){ + $group_names=$this->nice_names($groups); + $ret_groups=array_merge($ret_groups,$group_names); //final groups to return + + foreach ($group_names as $id => $group_name){ + $child_groups=$this->recursive_groups($group_name); + $ret_groups=array_merge($ret_groups,$child_groups); + } + } + } + + return ($ret_groups); + } + + /** + * Returns a complete list of the groups in AD based on a SAM Account Type + * + * @param string $samaccounttype The account type to return + * @param bool $include_desc Whether to return a description + * @param string $search Search parameters + * @param bool $sorted Whether to sort the results + * @return array + */ + public function search_groups($samaccounttype = ADLDAP_SECURITY_GLOBAL_GROUP, $include_desc = false, $search = "*", $sorted = true) { + if (!$this->_bind){ return (false); } + + $filter = '(&(objectCategory=group)'; + if ($samaccounttype !== null) { + $filter .= '(samaccounttype='. $samaccounttype .')'; + } + $filter .= '(cn='.$search.'))'; + // Perform the search and grab all their details + $fields=array("samaccountname","description"); + $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields); + $entries = ldap_get_entries($this->_conn, $sr); + + $groups_array = array(); + for ($i=0; $i<$entries["count"]; $i++){ + if ($include_desc && strlen($entries[$i]["description"][0]) > 0 ){ + $groups_array[ $entries[$i]["samaccountname"][0] ] = $entries[$i]["description"][0]; + } elseif ($include_desc){ + $groups_array[ $entries[$i]["samaccountname"][0] ] = $entries[$i]["samaccountname"][0]; + } else { + array_push($groups_array, $entries[$i]["samaccountname"][0]); + } + } + if( $sorted ){ asort($groups_array); } + return ($groups_array); + } + + /** + * Returns a complete list of all groups in AD + * + * @param bool $include_desc Whether to return a description + * @param string $search Search parameters + * @param bool $sorted Whether to sort the results + * @return array + */ + public function all_groups($include_desc = false, $search = "*", $sorted = true){ + $groups_array = $this->search_groups(null, $include_desc, $search, $sorted); + return ($groups_array); + } + + /** + * Returns a complete list of security groups in AD + * + * @param bool $include_desc Whether to return a description + * @param string $search Search parameters + * @param bool $sorted Whether to sort the results + * @return array + */ + public function all_security_groups($include_desc = false, $search = "*", $sorted = true){ + $groups_array = $this->search_groups(ADLDAP_SECURITY_GLOBAL_GROUP, $include_desc, $search, $sorted); + return ($groups_array); + } + + /** + * Returns a complete list of distribution lists in AD + * + * @param bool $include_desc Whether to return a description + * @param string $search Search parameters + * @param bool $sorted Whether to sort the results + * @return array + */ + public function all_distribution_groups($include_desc = false, $search = "*", $sorted = true){ + $groups_array = $this->search_groups(ADLDAP_DISTRIBUTION_GROUP, $include_desc, $search, $sorted); + return ($groups_array); + } + + //***************************************************************************************************************** + // USER FUNCTIONS + + /** + * Create a user + * + * If you specify a password here, this can only be performed over SSL + * + * @param array $attributes The attributes to set to the user account + * @return bool + */ + public function user_create($attributes){ + // Check for compulsory fields + if (!array_key_exists("username",$attributes)){ return ("Missing compulsory field [username]"); } + if (!array_key_exists("firstname",$attributes)){ return ("Missing compulsory field [firstname]"); } + if (!array_key_exists("surname",$attributes)){ return ("Missing compulsory field [surname]"); } + if (!array_key_exists("email",$attributes)){ return ("Missing compulsory field [email]"); } + if (!array_key_exists("container",$attributes)){ return ("Missing compulsory field [container]"); } + if (!is_array($attributes["container"])){ return ("Container attribute must be an array."); } + + if (array_key_exists("password",$attributes) && (!$this->_use_ssl && !$this->_use_tls)){ + throw new adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.'); + } + + if (!array_key_exists("display_name",$attributes)){ $attributes["display_name"]=$attributes["firstname"]." ".$attributes["surname"]; } + + // Translate the schema + $add=$this->adldap_schema($attributes); + + // Additional stuff only used for adding accounts + $add["cn"][0]=$attributes["display_name"]; + $add["samaccountname"][0]=$attributes["username"]; + $add["objectclass"][0]="top"; + $add["objectclass"][1]="person"; + $add["objectclass"][2]="organizationalPerson"; + $add["objectclass"][3]="user"; //person? + //$add["name"][0]=$attributes["firstname"]." ".$attributes["surname"]; + + // Set the account control attribute + $control_options=array("NORMAL_ACCOUNT"); + if (!$attributes["enabled"]){ $control_options[]="ACCOUNTDISABLE"; } + $add["userAccountControl"][0]=$this->account_control($control_options); + //echo ("
"); print_r($add);
+
+        // Determine the container
+        $attributes["container"]=array_reverse($attributes["container"]);
+        $container="OU=".implode(",OU=",$attributes["container"]);
+
+        // Add the entry
+        $result=@ldap_add($this->_conn, "CN=".$add["cn"][0].", ".$container.",".$this->_base_dn, $add);
+        if ($result!=true){ return (false); }
+
+        return (true);
+    }
+
+    /**
+    * Delete a user account
+    *
+    * @param string $username The username to delete (please be careful here!)
+    * @param bool $isGUID Is the username a GUID or a samAccountName
+    * @return array
+    */
+    public function user_delete($username,$isGUID=false) {
+        $userinfo = $this->user_info($username, array("*"),$isGUID);
+        $dn = $userinfo[0]['distinguishedname'][0];
+        $result=$this->dn_delete($dn);
+        if ($result!=true){ return (false); }
+        return (true);
+    }
+
+    /**
+    * Groups the user is a member of
+    *
+    * @param string $username The username to query
+    * @param bool $recursive Recursive list of groups
+    * @param bool $isGUID Is the username passed a GUID or a samAccountName
+    * @return array
+    */
+    public function user_groups($username,$recursive=NULL,$isGUID=false){
+        if ($username===NULL){ return (false); }
+        if ($recursive===NULL){ $recursive=$this->_recursive_groups; } // Use the default option if they haven't set it
+        if (!$this->_bind){ return (false); }
+
+        // Search the directory for their information
+        $info=@$this->user_info($username,array("memberof","primarygroupid"),$isGUID);
+        $groups=$this->nice_names($info[0]["memberof"]); // Presuming the entry returned is our guy (unique usernames)
+
+        if ($recursive === true){
+            foreach ($groups as $id => $group_name){
+                $extra_groups=$this->recursive_groups($group_name);
+                $groups=array_merge($groups,$extra_groups);
+            }
+        }
+
+        return ($groups);
+    }
+
+    /**
+    * Find information about the users
+    *
+    * @param string $username The username to query
+    * @param array $fields Array of parameters to query
+    * @param bool $isGUID Is the username passed a GUID or a samAccountName
+    * @return array
+    */
+    public function user_info($username,$fields=NULL,$isGUID=false){
+        if ($username===NULL){ return (false); }
+        if (!$this->_bind){ return (false); }
+
+        if ($isGUID === true) {
+            $username = $this->strguid2hex($username);
+            $filter="objectguid=".$username;
+        }
+        else if (strstr($username, "@")) {
+             $filter="userPrincipalName=".$username;
+        }
+        else {
+             $filter="samaccountname=".$username;
+        }
+        $filter = "(&(objectCategory=person)({$filter}))";
+        if ($fields===NULL){ $fields=array("samaccountname","mail","memberof","department","displayname","telephonenumber","primarygroupid","objectsid"); }
+        if (!in_array("objectsid",$fields)){
+            $fields[] = "objectsid";
+        }
+        $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields);
+        $entries = ldap_get_entries($this->_conn, $sr);
+
+        if (isset($entries[0])) {
+            if ($entries[0]['count'] >= 1) {
+                if (in_array("memberof", $fields)) {
+                    // AD does not return the primary group in the ldap query, we may need to fudge it
+                    if ($this->_real_primarygroup && isset($entries[0]["primarygroupid"][0]) && isset($entries[0]["objectsid"][0])){
+                        //$entries[0]["memberof"][]=$this->group_cn($entries[0]["primarygroupid"][0]);
+                        $entries[0]["memberof"][]=$this->get_primary_group($entries[0]["primarygroupid"][0], $entries[0]["objectsid"][0]);
+                    } else {
+                        $entries[0]["memberof"][]="CN=Domain Users,CN=Users,".$this->_base_dn;
+                    }
+                    $entries[0]["memberof"]["count"]++;
+                }
+            }
+            return $entries;
+        }
+        return false;
+    }
+
+    /**
+    * Determine if a user is in a specific group
+    *
+    * @param string $username The username to query
+    * @param string $group The name of the group to check against
+    * @param bool $recursive Check groups recursively
+    * @param bool $isGUID Is the username passed a GUID or a samAccountName
+    * @return bool
+    */
+    public function user_ingroup($username,$group,$recursive=NULL,$isGUID=false){
+        if ($username===NULL){ return (false); }
+        if ($group===NULL){ return (false); }
+        if (!$this->_bind){ return (false); }
+        if ($recursive===NULL){ $recursive=$this->_recursive_groups; } // Use the default option if they haven't set it
+
+        // Get a list of the groups
+        $groups=$this->user_groups($username,$recursive,$isGUID);
+
+        // Return true if the specified group is in the group list
+        if (in_array($group,$groups)){ return (true); }
+
+        return (false);
+    }
+
+    /**
+    * Determine a user's password expiry date
+    *
+    * @param string $username The username to query
+    * @param book $isGUID Is the username passed a GUID or a samAccountName
+    * @requires bcmath http://www.php.net/manual/en/book.bc.php
+    * @return array
+    */
+    public function user_password_expiry($username,$isGUID=false) {
+        if ($username===NULL){ return ("Missing compulsory field [username]"); }
+        if (!$this->_bind){ return (false); }
+        if (!function_exists('bcmod')) { return ("Missing function support [bcmod] http://www.php.net/manual/en/book.bc.php"); };
+
+        $userinfo = $this->user_info($username, array("pwdlastset", "useraccountcontrol"), $isGUID);
+        $pwdlastset = $userinfo[0]['pwdlastset'][0];
+        $status = array();
+
+        if ($userinfo[0]['useraccountcontrol'][0] == '66048') {
+            // Password does not expire
+            return "Does not expire";
+        }
+        if ($pwdlastset === '0') {
+            // Password has already expired
+            return "Password has expired";
+        }
+
+         // Password expiry in AD can be calculated from TWO values:
+         //   - User's own pwdLastSet attribute: stores the last time the password was changed
+         //   - Domain's maxPwdAge attribute: how long passwords last in the domain
+         //
+         // Although Microsoft chose to use a different base and unit for time measurements.
+         // This function will convert them to Unix timestamps
+         $sr = ldap_read($this->_conn, $this->_base_dn, 'objectclass=*', array('maxPwdAge'));
+         if (!$sr) {
+             return false;
+         }
+         $info = ldap_get_entries($this->_conn, $sr);
+         $maxpwdage = $info[0]['maxpwdage'][0];
+
+
+         // See MSDN: http://msdn.microsoft.com/en-us/library/ms974598.aspx
+         //
+         // pwdLastSet contains the number of 100 nanosecond intervals since January 1, 1601 (UTC),
+         // stored in a 64 bit integer.
+         //
+         // The number of seconds between this date and Unix epoch is 11644473600.
+         //
+         // maxPwdAge is stored as a large integer that represents the number of 100 nanosecond
+         // intervals from the time the password was set before the password expires.
+         //
+         // We also need to scale this to seconds but also this value is a _negative_ quantity!
+         //
+         // If the low 32 bits of maxPwdAge are equal to 0 passwords do not expire
+         //
+         // Unfortunately the maths involved are too big for PHP integers, so I've had to require
+         // BCMath functions to work with arbitrary precision numbers.
+         if (bcmod($maxpwdage, 4294967296) === '0') {
+            return "Domain does not expire passwords";
+        }
+
+        // Add maxpwdage and pwdlastset and we get password expiration time in Microsoft's
+        // time units.  Because maxpwd age is negative we need to subtract it.
+        $pwdexpire = bcsub($pwdlastset, $maxpwdage);
+
+        // Convert MS's time to Unix time
+        $status['expiryts'] = bcsub(bcdiv($pwdexpire, '10000000'), '11644473600');
+        $status['expiryformat'] = date('Y-m-d H:i:s', bcsub(bcdiv($pwdexpire, '10000000'), '11644473600'));
+
+        return $status;
+    }
+
+    /**
+    * Modify a user
+    *
+    * @param string $username The username to query
+    * @param array $attributes The attributes to modify.  Note if you set the enabled attribute you must not specify any other attributes
+    * @param bool $isGUID Is the username passed a GUID or a samAccountName
+    * @return bool
+    */
+    public function user_modify($username,$attributes,$isGUID=false){
+        if ($username===NULL){ return ("Missing compulsory field [username]"); }
+        if (array_key_exists("password",$attributes) && !$this->_use_ssl){
+            throw new adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.');
+        }
+
+        // Find the dn of the user
+        $user_dn=$this->user_dn($username,$isGUID);
+        if ($user_dn===false){ return (false); }
+
+        // Translate the update to the LDAP schema
+        $mod=$this->adldap_schema($attributes);
+
+        // Check to see if this is an enabled status update
+        if (!$mod && !array_key_exists("enabled", $attributes)){ return (false); }
+
+        // Set the account control attribute (only if specified)
+        if (array_key_exists("enabled",$attributes)){
+            if ($attributes["enabled"]){ $control_options=array("NORMAL_ACCOUNT"); }
+            else { $control_options=array("NORMAL_ACCOUNT","ACCOUNTDISABLE"); }
+            $mod["userAccountControl"][0]=$this->account_control($control_options);
+        }
+
+        // Do the update
+        $result=@ldap_modify($this->_conn,$user_dn,$mod);
+        if ($result==false){ return (false); }
+
+        return (true);
+    }
+
+    /**
+    * Disable a user account
+    *
+    * @param string $username The username to disable
+    * @param bool $isGUID Is the username passed a GUID or a samAccountName
+    * @return bool
+    */
+    public function user_disable($username,$isGUID=false){
+        if ($username===NULL){ return ("Missing compulsory field [username]"); }
+        $attributes=array("enabled"=>0);
+        $result = $this->user_modify($username, $attributes, $isGUID);
+        if ($result==false){ return (false); }
+
+        return (true);
+    }
+
+    /**
+    * Enable a user account
+    *
+    * @param string $username The username to enable
+    * @param bool $isGUID Is the username passed a GUID or a samAccountName
+    * @return bool
+    */
+    public function user_enable($username,$isGUID=false){
+        if ($username===NULL){ return ("Missing compulsory field [username]"); }
+        $attributes=array("enabled"=>1);
+        $result = $this->user_modify($username, $attributes, $isGUID);
+        if ($result==false){ return (false); }
+
+        return (true);
+    }
+
+    /**
+    * Set the password of a user - This must be performed over SSL
+    *
+    * @param string $username The username to modify
+    * @param string $password The new password
+    * @param bool $isGUID Is the username passed a GUID or a samAccountName
+    * @return bool
+    */
+    public function user_password($username,$password,$isGUID=false){
+        if ($username===NULL){ return (false); }
+        if ($password===NULL){ return (false); }
+        if (!$this->_bind){ return (false); }
+        if (!$this->_use_ssl && !$this->_use_tls){
+            throw new adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.');
+        }
+
+        $user_dn=$this->user_dn($username,$isGUID);
+        if ($user_dn===false){ return (false); }
+
+        $add=array();
+        $add["unicodePwd"][0]=$this->encode_password($password);
+
+        $result=@ldap_mod_replace($this->_conn,$user_dn,$add);
+        if ($result==false){
+            $err = ldap_errno($this->_conn);
+            if($err){
+                $msg = 'Error '.$err.': '.ldap_err2str($err).'.';
+                if($err == 53) $msg .= ' Your password might not match the password policy.';
+                throw new adLDAPException($msg);
+            }else{
+                return false;
+            }
+        }
+
+        return (true);
+    }
+
+    /**
+    * Return a list of all users in AD
+    *
+    * @param bool $include_desc Return a description of the user
+    * @param string $search Search parameter
+    * @param bool $sorted Sort the user accounts
+    * @return array
+    */
+    public function all_users($include_desc = false, $search = "*", $sorted = true){
+        if (!$this->_bind){ return (false); }
+
+        // Perform the search and grab all their details
+        $filter = "(&(objectClass=user)(samaccounttype=". ADLDAP_NORMAL_ACCOUNT .")(objectCategory=person)(cn=".$search."))";
+        $fields=array("samaccountname","displayname");
+        $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields);
+        $entries = ldap_get_entries($this->_conn, $sr);
+
+        $users_array = array();
+        for ($i=0; $i<$entries["count"]; $i++){
+            if ($include_desc && strlen($entries[$i]["displayname"][0])>0){
+                $users_array[ $entries[$i]["samaccountname"][0] ] = $entries[$i]["displayname"][0];
+            } elseif ($include_desc){
+                $users_array[ $entries[$i]["samaccountname"][0] ] = $entries[$i]["samaccountname"][0];
+            } else {
+                array_push($users_array, $entries[$i]["samaccountname"][0]);
+            }
+        }
+        if ($sorted){ asort($users_array); }
+        return ($users_array);
+    }
+
+    /**
+    * Converts a username (samAccountName) to a GUID
+    *
+    * @param string $username The username to query
+    * @return string
+    */
+    public function username2guid($username) {
+        if (!$this->_bind){ return (false); }
+        if ($username === null){ return ("Missing compulsory field [username]"); }
+
+        $filter = "samaccountname=" . $username;
+        $fields = array("objectGUID");
+        $sr = @ldap_search($this->_conn, $this->_base_dn, $filter, $fields);
+        if (ldap_count_entries($this->_conn, $sr) > 0) {
+            $entry = @ldap_first_entry($this->_conn, $sr);
+            $guid = @ldap_get_values_len($this->_conn, $entry, 'objectGUID');
+            $strGUID = $this->binary2text($guid[0]);
+            return ($strGUID);
+        }
+        else {
+            return (false);
+        }
+    }
+
+    /**
+    * Move a user account to a different OU
+    *
+    * @param string $username The username to move (please be careful here!)
+    * @param array $container The container or containers to move the user to (please be careful here!).
+    * accepts containers in 1. parent 2. child order
+    * @return array
+    */
+    public function user_move($username, $container) {
+        if (!$this->_bind){ return (false); }
+        if ($username === null){ return ("Missing compulsory field [username]"); }
+        if ($container === null){ return ("Missing compulsory field [container]"); }
+        if (!is_array($container)){ return ("Container must be an array"); }
+
+        $userinfo = $this->user_info($username, array("*"));
+        $dn = $userinfo[0]['distinguishedname'][0];
+        $newrdn = "cn=" . $username;
+        $container = array_reverse($container);
+        $newcontainer = "ou=" . implode(",ou=",$container);
+        $newbasedn = strtolower($newcontainer) . "," . $this->_base_dn;
+        $result=@ldap_rename($this->_conn,$dn,$newrdn,$newbasedn,true);
+        if ($result !== true) {
+            return (false);
+        }
+        return (true);
+    }
+
+    //*****************************************************************************************************************
+    // CONTACT FUNCTIONS
+    // * Still work to do in this area, and new functions to write
+
+    /**
+    * Create a contact
+    *
+    * @param array $attributes The attributes to set to the contact
+    * @return bool
+    */
+    public function contact_create($attributes){
+        // Check for compulsory fields
+        if (!array_key_exists("display_name",$attributes)){ return ("Missing compulsory field [display_name]"); }
+        if (!array_key_exists("email",$attributes)){ return ("Missing compulsory field [email]"); }
+        if (!array_key_exists("container",$attributes)){ return ("Missing compulsory field [container]"); }
+        if (!is_array($attributes["container"])){ return ("Container attribute must be an array."); }
+
+        // Translate the schema
+        $add=$this->adldap_schema($attributes);
+
+        // Additional stuff only used for adding contacts
+        $add["cn"][0]=$attributes["display_name"];
+        $add["objectclass"][0]="top";
+        $add["objectclass"][1]="person";
+        $add["objectclass"][2]="organizationalPerson";
+        $add["objectclass"][3]="contact";
+        if (!isset($attributes['exchange_hidefromlists'])) {
+            $add["msExchHideFromAddressLists"][0]="TRUE";
+        }
+
+        // Determine the container
+        $attributes["container"]=array_reverse($attributes["container"]);
+        $container="OU=".implode(",OU=",$attributes["container"]);
+
+        // Add the entry
+        $result=@ldap_add($this->_conn, "CN=".$add["cn"][0].", ".$container.",".$this->_base_dn, $add);
+        if ($result!=true){ return (false); }
+
+        return (true);
+    }
+
+    /**
+    * Determine the list of groups a contact is a member of
+    *
+    * @param string $distinguisedname The full DN of a contact
+    * @param bool $recursive Recursively check groups
+    * @return array
+    */
+    public function contact_groups($distinguishedname,$recursive=NULL){
+        if ($distinguishedname===NULL){ return (false); }
+        if ($recursive===NULL){ $recursive=$this->_recursive_groups; } //use the default option if they haven't set it
+        if (!$this->_bind){ return (false); }
+
+        // Search the directory for their information
+        $info=@$this->contact_info($distinguishedname,array("memberof","primarygroupid"));
+        $groups=$this->nice_names($info[0]["memberof"]); //presuming the entry returned is our contact
+
+        if ($recursive === true){
+            foreach ($groups as $id => $group_name){
+                $extra_groups=$this->recursive_groups($group_name);
+                $groups=array_merge($groups,$extra_groups);
+            }
+        }
+
+        return ($groups);
+    }
+
+    /**
+    * Get contact information
+    *
+    * @param string $distinguisedname The full DN of a contact
+    * @param array $fields Attributes to be returned
+    * @return array
+    */
+    public function contact_info($distinguishedname,$fields=NULL){
+        if ($distinguishedname===NULL){ return (false); }
+        if (!$this->_bind){ return (false); }
+
+        $filter="distinguishedName=".$distinguishedname;
+        if ($fields===NULL){ $fields=array("distinguishedname","mail","memberof","department","displayname","telephonenumber","primarygroupid","objectsid"); }
+        $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields);
+        $entries = ldap_get_entries($this->_conn, $sr);
+
+        if ($entries[0]['count'] >= 1) {
+            // AD does not return the primary group in the ldap query, we may need to fudge it
+            if ($this->_real_primarygroup && isset($entries[0]["primarygroupid"][0]) && isset($entries[0]["primarygroupid"][0])){
+                //$entries[0]["memberof"][]=$this->group_cn($entries[0]["primarygroupid"][0]);
+                $entries[0]["memberof"][]=$this->get_primary_group($entries[0]["primarygroupid"][0], $entries[0]["objectsid"][0]);
+            } else {
+                $entries[0]["memberof"][]="CN=Domain Users,CN=Users,".$this->_base_dn;
+            }
+        }
+
+        $entries[0]["memberof"]["count"]++;
+        return ($entries);
+    }
+
+    /**
+    * Determine if a contact is a member of a group
+    *
+    * @param string $distinguisedname The full DN of a contact
+    * @param string $group The group name to query
+    * @param bool $recursive Recursively check groups
+    * @return bool
+    */
+    public function contact_ingroup($distinguisedname,$group,$recursive=NULL){
+        if ($distinguisedname===NULL){ return (false); }
+        if ($group===NULL){ return (false); }
+        if (!$this->_bind){ return (false); }
+        if ($recursive===NULL){ $recursive=$this->_recursive_groups; } //use the default option if they haven't set it
+
+        // Get a list of the groups
+        $groups=$this->contact_groups($distinguisedname,array("memberof"),$recursive);
+
+        // Return true if the specified group is in the group list
+        if (in_array($group,$groups)){ return (true); }
+
+        return (false);
+    }
+
+    /**
+    * Modify a contact
+    *
+    * @param string $distinguishedname The contact to query
+    * @param array $attributes The attributes to modify.  Note if you set the enabled attribute you must not specify any other attributes
+    * @return bool
+    */
+    public function contact_modify($distinguishedname,$attributes){
+        if ($distinguishedname===NULL){ return ("Missing compulsory field [distinguishedname]"); }
+
+        // Translate the update to the LDAP schema
+        $mod=$this->adldap_schema($attributes);
+
+        // Check to see if this is an enabled status update
+        if (!$mod){ return (false); }
+
+        // Do the update
+        $result=ldap_modify($this->_conn,$distinguishedname,$mod);
+        if ($result==false){ return (false); }
+
+        return (true);
+    }
+
+    /**
+    * Delete a contact
+    *
+    * @param string $distinguishedname The contact dn to delete (please be careful here!)
+    * @return array
+    */
+    public function contact_delete($distinguishedname) {
+        $result = $this->dn_delete($distinguishedname);
+        if ($result!=true){ return (false); }
+        return (true);
+    }
+
+    /**
+    * Return a list of all contacts
+    *
+    * @param bool $include_desc Include a description of a contact
+    * @param string $search The search parameters
+    * @param bool $sorted Whether to sort the results
+    * @return array
+    */
+    public function all_contacts($include_desc = false, $search = "*", $sorted = true){
+        if (!$this->_bind){ return (false); }
+
+        // Perform the search and grab all their details
+        $filter = "(&(objectClass=contact)(cn=".$search."))";
+        $fields=array("displayname","distinguishedname");
+        $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields);
+        $entries = ldap_get_entries($this->_conn, $sr);
+
+        $users_array = array();
+        for ($i=0; $i<$entries["count"]; $i++){
+            if ($include_desc && strlen($entries[$i]["displayname"][0])>0){
+                $users_array[ $entries[$i]["distinguishedname"][0] ] = $entries[$i]["displayname"][0];
+            } elseif ($include_desc){
+                $users_array[ $entries[$i]["distinguishedname"][0] ] = $entries[$i]["distinguishedname"][0];
+            } else {
+                array_push($users_array, $entries[$i]["distinguishedname"][0]);
+            }
+        }
+        if ($sorted){ asort($users_array); }
+        return ($users_array);
+    }
+
+    //*****************************************************************************************************************
+    // FOLDER FUNCTIONS
+
+    /**
+    * Returns a folder listing for a specific OU
+    * See http://adldap.sourceforge.net/wiki/doku.php?id=api_folder_functions
+    *
+    * @param array $folder_name An array to the OU you wish to list.
+    *                           If set to NULL will list the root, strongly recommended to set
+    *                           $recursive to false in that instance!
+    * @param string $dn_type The type of record to list.  This can be ADLDAP_FOLDER or ADLDAP_CONTAINER.
+    * @param bool $recursive Recursively search sub folders
+    * @param bool $type Specify a type of object to search for
+    * @return array
+    */
+    public function folder_list($folder_name = NULL, $dn_type = ADLDAP_FOLDER, $recursive = NULL, $type = NULL) {
+        if ($recursive===NULL){ $recursive=$this->_recursive_groups; } //use the default option if they haven't set it
+        if (!$this->_bind){ return (false); }
+
+        $filter = '(&';
+        if ($type !== NULL) {
+            switch ($type) {
+                case 'contact':
+                    $filter .= '(objectClass=contact)';
+                    break;
+                case 'computer':
+                    $filter .= '(objectClass=computer)';
+                    break;
+                case 'group':
+                    $filter .= '(objectClass=group)';
+                    break;
+                case 'folder':
+                    $filter .= '(objectClass=organizationalUnit)';
+                    break;
+                case 'container':
+                    $filter .= '(objectClass=container)';
+                    break;
+                case 'domain':
+                    $filter .= '(objectClass=builtinDomain)';
+                    break;
+                default:
+                    $filter .= '(objectClass=user)';
+                    break;
+            }
+        }
+        else {
+            $filter .= '(objectClass=*)';
+        }
+        // If the folder name is null then we will search the root level of AD
+        // This requires us to not have an OU= part, just the base_dn
+        $searchou = $this->_base_dn;
+        if (is_array($folder_name)) {
+            $ou = $dn_type . "=".implode("," . $dn_type . "=",$folder_name);
+            $filter .= '(!(distinguishedname=' . $ou . ',' . $this->_base_dn . ')))';
+            $searchou = $ou . ',' . $this->_base_dn;
+        }
+        else {
+            $filter .= '(!(distinguishedname=' . $this->_base_dn . ')))';
+        }
+
+        if ($recursive === true) {
+            $sr=ldap_search($this->_conn, $searchou, $filter, array('objectclass', 'distinguishedname', 'samaccountname'));
+            $entries = @ldap_get_entries($this->_conn, $sr);
+            if (is_array($entries)) {
+                return $entries;
+            }
+        }
+        else {
+            $sr=ldap_list($this->_conn, $searchou, $filter, array('objectclass', 'distinguishedname', 'samaccountname'));
+            $entries = @ldap_get_entries($this->_conn, $sr);
+            if (is_array($entries)) {
+                return $entries;
+            }
+        }
+
+        return false;
+    }
+
+    //*****************************************************************************************************************
+    // COMPUTER FUNCTIONS
+
+    /**
+    * Get information about a specific computer
+    *
+    * @param string $computer_name The name of the computer
+    * @param array $fields Attributes to return
+    * @return array
+    */
+    public function computer_info($computer_name,$fields=NULL){
+        if ($computer_name===NULL){ return (false); }
+        if (!$this->_bind){ return (false); }
+
+        $filter="(&(objectClass=computer)(cn=".$computer_name."))";
+        if ($fields===NULL){ $fields=array("memberof","cn","displayname","dnshostname","distinguishedname","objectcategory","operatingsystem","operatingsystemservicepack","operatingsystemversion"); }
+        $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields);
+        $entries = ldap_get_entries($this->_conn, $sr);
+
+        return ($entries);
+    }
+
+    /**
+    * Check if a computer is in a group
+    *
+    * @param string $computer_name The name of the computer
+    * @param string $group The group to check
+    * @param bool $recursive Whether to check recursively
+    * @return array
+    */
+    public function computer_ingroup($computer_name,$group,$recursive=NULL){
+        if ($computer_name===NULL){ return (false); }
+        if ($group===NULL){ return (false); }
+        if (!$this->_bind){ return (false); }
+        if ($recursive===NULL){ $recursive=$this->_recursive_groups; } // use the default option if they haven't set it
+
+        //get a list of the groups
+        $groups=$this->computer_groups($computer_name,array("memberof"),$recursive);
+
+        //return true if the specified group is in the group list
+        if (in_array($group,$groups)){ return (true); }
+
+        return (false);
+    }
+
+    /**
+    * Get the groups a computer is in
+    *
+    * @param string $computer_name The name of the computer
+    * @param bool $recursive Whether to check recursively
+    * @return array
+    */
+    public function computer_groups($computer_name,$recursive=NULL){
+        if ($computer_name===NULL){ return (false); }
+        if ($recursive===NULL){ $recursive=$this->_recursive_groups; } //use the default option if they haven't set it
+        if (!$this->_bind){ return (false); }
+
+        //search the directory for their information
+        $info=@$this->computer_info($computer_name,array("memberof","primarygroupid"));
+        $groups=$this->nice_names($info[0]["memberof"]); //presuming the entry returned is our guy (unique usernames)
+
+        if ($recursive === true){
+            foreach ($groups as $id => $group_name){
+              $extra_groups=$this->recursive_groups($group_name);
+              $groups=array_merge($groups,$extra_groups);
+            }
+        }
+
+        return ($groups);
+    }
+
+    //************************************************************************************************************
+    //  ORGANIZATIONAL UNIT FUNCTIONS
+
+     /**
+    * Create an organizational unit
+    *
+    * @param array $attributes Default attributes of the ou
+    * @return bool
+    */
+    public function ou_create($attributes){
+        if (!is_array($attributes)){ return ("Attributes must be an array"); }
+        if (!array_key_exists("ou_name",$attributes)){ return ("Missing compulsory field [ou_name]"); }
+        if (!array_key_exists("container",$attributes)){ return ("Missing compulsory field [container]"); }
+        if (!is_array($attributes["container"])){ return ("Container attribute must be an array."); }
+        $attributes["container"]=array_reverse($attributes["container"]);
+
+        $add=array();
+        $add["objectClass"] = "organizationalUnit";
+
+        $container="OU=".implode(",OU=",$attributes["container"]);
+        $result=ldap_add($this->_conn,"CN=".$add["cn"].", ".$container.",".$this->_base_dn,$add);
+        if ($result!=true){ return (false); }
+
+        return (true);
+    }
+
+    //************************************************************************************************************
+    // EXCHANGE FUNCTIONS
+
+    /**
+    * Create an Exchange account
+    *
+    * @param string $username The username of the user to add the Exchange account to
+    * @param array $storagegroup The mailbox, Exchange Storage Group, for the user account, this must be a full CN
+    *                            If the storage group has a different base_dn to the adLDAP configuration, set it using $base_dn
+    * @param string $emailaddress The primary email address to add to this user
+    * @param string $mailnickname The mail nick name.  If mail nickname is blank, the username will be used
+    * @param bool $usedefaults Indicates whether the store should use the default quota, rather than the per-mailbox quota.
+    * @param string $base_dn Specify an alternative base_dn for the Exchange storage group
+    * @param bool $isGUID Is the username passed a GUID or a samAccountName
+    * @return bool
+    */
+    public function exchange_create_mailbox($username, $storagegroup, $emailaddress, $mailnickname=NULL, $usedefaults=TRUE, $base_dn=NULL, $isGUID=false){
+        if ($username===NULL){ return ("Missing compulsory field [username]"); }
+        if ($storagegroup===NULL){ return ("Missing compulsory array [storagegroup]"); }
+        if (!is_array($storagegroup)){ return ("[storagegroup] must be an array"); }
+        if ($emailaddress===NULL){ return ("Missing compulsory field [emailaddress]"); }
+
+        if ($base_dn===NULL) {
+            $base_dn = $this->_base_dn;
+        }
+
+        $container="CN=".implode(",CN=",$storagegroup);
+
+        if ($mailnickname===NULL) { $mailnickname=$username; }
+        $mdbUseDefaults = $this->bool2str($usedefaults);
+
+        $attributes = array(
+            'exchange_homemdb'=>$container.",".$base_dn,
+            'exchange_proxyaddress'=>'SMTP:' . $emailaddress,
+            'exchange_mailnickname'=>$mailnickname,
+            'exchange_usedefaults'=>$mdbUseDefaults
+        );
+        $result = $this->user_modify($username,$attributes,$isGUID);
+        if ($result==false){ return (false); }
+        return (true);
+    }
+
+    /**
+    * Add an X400 address to Exchange
+    * See http://tools.ietf.org/html/rfc1685 for more information.
+    * An X400 Address looks similar to this X400:c=US;a= ;p=Domain;o=Organization;s=Doe;g=John;
+    *
+    * @param string $username The username of the user to add the X400 to to
+    * @param string $country Country
+    * @param string $admd Administration Management Domain
+    * @param string $pdmd Private Management Domain (often your AD domain)
+    * @param string $org Organization
+    * @param string $surname Surname
+    * @param string $givenName Given name
+    * @param bool $isGUID Is the username passed a GUID or a samAccountName
+    * @return bool
+    */
+    public function exchange_add_X400($username, $country, $admd, $pdmd, $org, $surname, $givenname, $isGUID=false) {
+        if ($username===NULL){ return ("Missing compulsory field [username]"); }
+
+        $proxyvalue = 'X400:';
+
+        // Find the dn of the user
+        $user=$this->user_info($username,array("cn","proxyaddresses"), $isGUID);
+        if ($user[0]["dn"]===NULL){ return (false); }
+        $user_dn=$user[0]["dn"];
+
+        // We do not have to demote an email address from the default so we can just add the new proxy address
+        $attributes['exchange_proxyaddress'] = $proxyvalue . 'c=' . $country . ';a=' . $admd . ';p=' . $pdmd . ';o=' . $org . ';s=' . $surname . ';g=' . $givenname . ';';
+
+        // Translate the update to the LDAP schema
+        $add=$this->adldap_schema($attributes);
+
+        if (!$add){ return (false); }
+
+        // Do the update
+        // Take out the @ to see any errors, usually this error might occur because the address already
+        // exists in the list of proxyAddresses
+        $result=@ldap_mod_add($this->_conn,$user_dn,$add);
+        if ($result==false){ return (false); }
+
+        return (true);
+    }
+
+    /**
+    * Add an address to Exchange
+    *
+    * @param string $username The username of the user to add the Exchange account to
+    * @param string $emailaddress The email address to add to this user
+    * @param bool $default Make this email address the default address, this is a bit more intensive as we have to demote any existing default addresses
+    * @param bool $isGUID Is the username passed a GUID or a samAccountName
+    * @return bool
+    */
+    public function exchange_add_address($username, $emailaddress, $default=FALSE, $isGUID=false) {
+        if ($username===NULL){ return ("Missing compulsory field [username]"); }
+        if ($emailaddress===NULL) { return ("Missing compulsory fields [emailaddress]"); }
+
+        $proxyvalue = 'smtp:';
+        if ($default === true) {
+            $proxyvalue = 'SMTP:';
+        }
+
+        // Find the dn of the user
+        $user=$this->user_info($username,array("cn","proxyaddresses"),$isGUID);
+        if ($user[0]["dn"]===NULL){ return (false); }
+        $user_dn=$user[0]["dn"];
+
+        // We need to scan existing proxy addresses and demote the default one
+        if (is_array($user[0]["proxyaddresses"]) && $default===true) {
+            $modaddresses = array();
+            for ($i=0;$i_conn,$user_dn,$modaddresses);
+            if ($result==false){ return (false); }
+
+            return (true);
+        }
+        else {
+            // We do not have to demote an email address from the default so we can just add the new proxy address
+            $attributes['exchange_proxyaddress'] = $proxyvalue . $emailaddress;
+
+            // Translate the update to the LDAP schema
+            $add=$this->adldap_schema($attributes);
+
+            if (!$add){ return (false); }
+
+            // Do the update
+            // Take out the @ to see any errors, usually this error might occur because the address already
+            // exists in the list of proxyAddresses
+            $result=@ldap_mod_add($this->_conn,$user_dn,$add);
+            if ($result==false){ return (false); }
+
+            return (true);
+        }
+    }
+
+    /**
+    * Remove an address to Exchange
+    * If you remove a default address the account will no longer have a default,
+    * we recommend changing the default address first
+    *
+    * @param string $username The username of the user to add the Exchange account to
+    * @param string $emailaddress The email address to add to this user
+    * @param bool $isGUID Is the username passed a GUID or a samAccountName
+    * @return bool
+    */
+    public function exchange_del_address($username, $emailaddress, $isGUID=false) {
+        if ($username===NULL){ return ("Missing compulsory field [username]"); }
+        if ($emailaddress===NULL) { return ("Missing compulsory fields [emailaddress]"); }
+
+        // Find the dn of the user
+        $user=$this->user_info($username,array("cn","proxyaddresses"),$isGUID);
+        if ($user[0]["dn"]===NULL){ return (false); }
+        $user_dn=$user[0]["dn"];
+
+        if (is_array($user[0]["proxyaddresses"])) {
+            $mod = array();
+            for ($i=0;$i_conn,$user_dn,$mod);
+            if ($result==false){ return (false); }
+
+            return (true);
+        }
+        else {
+            return (false);
+        }
+    }
+    /**
+    * Change the default address
+    *
+    * @param string $username The username of the user to add the Exchange account to
+    * @param string $emailaddress The email address to make default
+    * @param bool $isGUID Is the username passed a GUID or a samAccountName
+    * @return bool
+    */
+    public function exchange_primary_address($username, $emailaddress, $isGUID=false) {
+        if ($username===NULL){ return ("Missing compulsory field [username]"); }
+        if ($emailaddress===NULL) { return ("Missing compulsory fields [emailaddress]"); }
+
+        // Find the dn of the user
+        $user=$this->user_info($username,array("cn","proxyaddresses"), $isGUID);
+        if ($user[0]["dn"]===NULL){ return (false); }
+        $user_dn=$user[0]["dn"];
+
+        if (is_array($user[0]["proxyaddresses"])) {
+            $modaddresses = array();
+            for ($i=0;$i_conn,$user_dn,$modaddresses);
+            if ($result==false){ return (false); }
+
+            return (true);
+        }
+
+    }
+
+    /**
+    * Mail enable a contact
+    * Allows email to be sent to them through Exchange
+    *
+    * @param string $distinguishedname The contact to mail enable
+    * @param string $emailaddress The email address to allow emails to be sent through
+    * @param string $mailnickname The mailnickname for the contact in Exchange.  If NULL this will be set to the display name
+    * @return bool
+    */
+    public function exchange_contact_mailenable($distinguishedname, $emailaddress, $mailnickname=NULL){
+        if ($distinguishedname===NULL){ return ("Missing compulsory field [distinguishedname]"); }
+        if ($emailaddress===NULL){ return ("Missing compulsory field [emailaddress]"); }
+
+        if ($mailnickname !== NULL) {
+            // Find the dn of the user
+            $user=$this->contact_info($distinguishedname,array("cn","displayname"));
+            if ($user[0]["displayname"]===NULL){ return (false); }
+            $mailnickname = $user[0]['displayname'][0];
+        }
+
+        $attributes = array("email"=>$emailaddress,"contact_email"=>"SMTP:" . $emailaddress,"exchange_proxyaddress"=>"SMTP:" . $emailaddress,"exchange_mailnickname"=>$mailnickname);
+
+        // Translate the update to the LDAP schema
+        $mod=$this->adldap_schema($attributes);
+
+        // Check to see if this is an enabled status update
+        if (!$mod){ return (false); }
+
+        // Do the update
+        $result=ldap_modify($this->_conn,$distinguishedname,$mod);
+        if ($result==false){ return (false); }
+
+        return (true);
+    }
+
+    /**
+    * Returns a list of Exchange Servers in the ConfigurationNamingContext of the domain
+    *
+    * @param array $attributes An array of the AD attributes you wish to return
+    * @return array
+    */
+    public function exchange_servers($attributes = array('cn','distinguishedname','serialnumber')) {
+        if (!$this->_bind){ return (false); }
+
+        $configurationNamingContext = $this->get_root_dse(array('configurationnamingcontext'));
+        $sr = @ldap_search($this->_conn,$configurationNamingContext[0]['configurationnamingcontext'][0],'(&(objectCategory=msExchExchangeServer))',$attributes);
+        $entries = @ldap_get_entries($this->_conn, $sr);
+        return $entries;
+    }
+
+    /**
+    * Returns a list of Storage Groups in Exchange for a given mail server
+    *
+    * @param string $exchangeServer The full DN of an Exchange server.  You can use exchange_servers() to find the DN for your server
+    * @param array $attributes An array of the AD attributes you wish to return
+    * @param bool $recursive If enabled this will automatically query the databases within a storage group
+    * @return array
+    */
+    public function exchange_storage_groups($exchangeServer, $attributes = array('cn','distinguishedname'), $recursive = NULL) {
+        if (!$this->_bind){ return (false); }
+        if ($exchangeServer===NULL){ return ("Missing compulsory field [exchangeServer]"); }
+        if ($recursive===NULL){ $recursive=$this->_recursive_groups; }
+
+        $filter = '(&(objectCategory=msExchStorageGroup))';
+        $sr=@ldap_search($this->_conn, $exchangeServer, $filter, $attributes);
+        $entries = @ldap_get_entries($this->_conn, $sr);
+
+        if ($recursive === true) {
+            for ($i=0; $i<$entries['count']; $i++) {
+                $entries[$i]['msexchprivatemdb'] = $this->exchange_storage_databases($entries[$i]['distinguishedname'][0]);
+            }
+        }
+
+        return $entries;
+    }
+
+    /**
+    * Returns a list of Databases within any given storage group in Exchange for a given mail server
+    *
+    * @param string $storageGroup The full DN of an Storage Group.  You can use exchange_storage_groups() to find the DN
+    * @param array $attributes An array of the AD attributes you wish to return
+    * @return array
+    */
+    public function exchange_storage_databases($storageGroup, $attributes = array('cn','distinguishedname','displayname')) {
+        if (!$this->_bind){ return (false); }
+        if ($storageGroup===NULL){ return ("Missing compulsory field [storageGroup]"); }
+
+        $filter = '(&(objectCategory=msExchPrivateMDB))';
+        $sr=@ldap_search($this->_conn, $storageGroup, $filter, $attributes);
+        $entries = @ldap_get_entries($this->_conn, $sr);
+        return $entries;
+    }
+
+    //************************************************************************************************************
+    // SERVER FUNCTIONS
+
+    /**
+    * Find the Base DN of your domain controller
+    *
+    * @return string
+    */
+    public function find_base_dn() {
+        $namingContext = $this->get_root_dse(array('defaultnamingcontext'));
+        return $namingContext[0]['defaultnamingcontext'][0];
+    }
+
+    /**
+    * Get the RootDSE properties from a domain controller
+    *
+    * @param array $attributes The attributes you wish to query e.g. defaultnamingcontext
+    * @return array
+    */
+    public function get_root_dse($attributes = array("*", "+")) {
+        if (!$this->_bind){ return (false); }
+
+        $sr = @ldap_read($this->_conn, NULL, 'objectClass=*', $attributes);
+        $entries = @ldap_get_entries($this->_conn, $sr);
+        return $entries;
+    }
+
+    //************************************************************************************************************
+    // UTILITY FUNCTIONS (Many of these functions are protected and can only be called from within the class)
+
+    /**
+    * Get last error from Active Directory
+    *
+    * This function gets the last message from Active Directory
+    * This may indeed be a 'Success' message but if you get an unknown error
+    * it might be worth calling this function to see what errors were raised
+    *
+    * return string
+    */
+    public function get_last_error() {
+        return @ldap_error($this->_conn);
+    }
+
+    /**
+    * Detect LDAP support in php
+    *
+    * @return bool
+    */
+    protected function ldap_supported() {
+        if (!function_exists('ldap_connect')) {
+            return (false);
+        }
+        return (true);
+    }
+
+    /**
+    * Schema
+    *
+    * @param array $attributes Attributes to be queried
+    * @return array
+    */
+    protected function adldap_schema($attributes){
+
+        // LDAP doesn't like NULL attributes, only set them if they have values
+        // If you wish to remove an attribute you should set it to a space
+        // TO DO: Adapt user_modify to use ldap_mod_delete to remove a NULL attribute
+        $mod=array();
+
+        // Check every attribute to see if it contains 8bit characters and then UTF8 encode them
+        array_walk($attributes, array($this, 'encode8bit'));
+
+        if ($attributes["address_city"]){ $mod["l"][0]=$attributes["address_city"]; }
+        if ($attributes["address_code"]){ $mod["postalCode"][0]=$attributes["address_code"]; }
+        //if ($attributes["address_country"]){ $mod["countryCode"][0]=$attributes["address_country"]; } // use country codes?
+        if ($attributes["address_country"]){ $mod["c"][0]=$attributes["address_country"]; }
+        if ($attributes["address_pobox"]){ $mod["postOfficeBox"][0]=$attributes["address_pobox"]; }
+        if ($attributes["address_state"]){ $mod["st"][0]=$attributes["address_state"]; }
+        if ($attributes["address_street"]){ $mod["streetAddress"][0]=$attributes["address_street"]; }
+        if ($attributes["company"]){ $mod["company"][0]=$attributes["company"]; }
+        if ($attributes["change_password"]){ $mod["pwdLastSet"][0]=0; }
+        if ($attributes["department"]){ $mod["department"][0]=$attributes["department"]; }
+        if ($attributes["description"]){ $mod["description"][0]=$attributes["description"]; }
+        if ($attributes["display_name"]){ $mod["displayName"][0]=$attributes["display_name"]; }
+        if ($attributes["email"]){ $mod["mail"][0]=$attributes["email"]; }
+        if ($attributes["expires"]){ $mod["accountExpires"][0]=$attributes["expires"]; } //unix epoch format?
+        if ($attributes["firstname"]){ $mod["givenName"][0]=$attributes["firstname"]; }
+        if ($attributes["home_directory"]){ $mod["homeDirectory"][0]=$attributes["home_directory"]; }
+        if ($attributes["home_drive"]){ $mod["homeDrive"][0]=$attributes["home_drive"]; }
+        if ($attributes["initials"]){ $mod["initials"][0]=$attributes["initials"]; }
+        if ($attributes["logon_name"]){ $mod["userPrincipalName"][0]=$attributes["logon_name"]; }
+        if ($attributes["manager"]){ $mod["manager"][0]=$attributes["manager"]; }  //UNTESTED ***Use DistinguishedName***
+        if ($attributes["office"]){ $mod["physicalDeliveryOfficeName"][0]=$attributes["office"]; }
+        if ($attributes["password"]){ $mod["unicodePwd"][0]=$this->encode_password($attributes["password"]); }
+        if ($attributes["profile_path"]){ $mod["profilepath"][0]=$attributes["profile_path"]; }
+        if ($attributes["script_path"]){ $mod["scriptPath"][0]=$attributes["script_path"]; }
+        if ($attributes["surname"]){ $mod["sn"][0]=$attributes["surname"]; }
+        if ($attributes["title"]){ $mod["title"][0]=$attributes["title"]; }
+        if ($attributes["telephone"]){ $mod["telephoneNumber"][0]=$attributes["telephone"]; }
+        if ($attributes["mobile"]){ $mod["mobile"][0]=$attributes["mobile"]; }
+        if ($attributes["pager"]){ $mod["pager"][0]=$attributes["pager"]; }
+        if ($attributes["ipphone"]){ $mod["ipphone"][0]=$attributes["ipphone"]; }
+        if ($attributes["web_page"]){ $mod["wWWHomePage"][0]=$attributes["web_page"]; }
+        if ($attributes["fax"]){ $mod["facsimileTelephoneNumber"][0]=$attributes["fax"]; }
+        if ($attributes["enabled"]){ $mod["userAccountControl"][0]=$attributes["enabled"]; }
+
+        // Distribution List specific schema
+        if ($attributes["group_sendpermission"]){ $mod["dlMemSubmitPerms"][0]=$attributes["group_sendpermission"]; }
+        if ($attributes["group_rejectpermission"]){ $mod["dlMemRejectPerms"][0]=$attributes["group_rejectpermission"]; }
+
+        // Exchange Schema
+        if ($attributes["exchange_homemdb"]){ $mod["homeMDB"][0]=$attributes["exchange_homemdb"]; }
+        if ($attributes["exchange_mailnickname"]){ $mod["mailNickname"][0]=$attributes["exchange_mailnickname"]; }
+        if ($attributes["exchange_proxyaddress"]){ $mod["proxyAddresses"][0]=$attributes["exchange_proxyaddress"]; }
+        if ($attributes["exchange_usedefaults"]){ $mod["mDBUseDefaults"][0]=$attributes["exchange_usedefaults"]; }
+        if ($attributes["exchange_policyexclude"]){ $mod["msExchPoliciesExcluded"][0]=$attributes["exchange_policyexclude"]; }
+        if ($attributes["exchange_policyinclude"]){ $mod["msExchPoliciesIncluded"][0]=$attributes["exchange_policyinclude"]; }
+        if ($attributes["exchange_addressbook"]){ $mod["showInAddressBook"][0]=$attributes["exchange_addressbook"]; }
+
+        // This schema is designed for contacts
+        if ($attributes["exchange_hidefromlists"]){ $mod["msExchHideFromAddressLists"][0]=$attributes["exchange_hidefromlists"]; }
+        if ($attributes["contact_email"]){ $mod["targetAddress"][0]=$attributes["contact_email"]; }
+
+        //echo ("
"); print_r($mod);
+        /*
+        // modifying a name is a bit fiddly
+        if ($attributes["firstname"] && $attributes["surname"]){
+            $mod["cn"][0]=$attributes["firstname"]." ".$attributes["surname"];
+            $mod["displayname"][0]=$attributes["firstname"]." ".$attributes["surname"];
+            $mod["name"][0]=$attributes["firstname"]." ".$attributes["surname"];
+        }
+        */
+
+        if (count($mod)==0){ return (false); }
+        return ($mod);
+    }
+
+    /**
+    * Coping with AD not returning the primary group
+    * http://support.microsoft.com/?kbid=321360
+    *
+    * For some reason it's not possible to search on primarygrouptoken=XXX
+    * If someone can show otherwise, I'd like to know about it :)
+    * this way is resource intensive and generally a pain in the @#%^
+    *
+    * @deprecated deprecated since version 3.1, see get get_primary_group
+    * @param string $gid Group ID
+    * @return string
+    */
+    protected function group_cn($gid){
+        if ($gid===NULL){ return (false); }
+        $r=false;
+
+        $filter="(&(objectCategory=group)(samaccounttype=". ADLDAP_SECURITY_GLOBAL_GROUP ."))";
+        $fields=array("primarygrouptoken","samaccountname","distinguishedname");
+        $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields);
+        $entries = ldap_get_entries($this->_conn, $sr);
+
+        for ($i=0; $i<$entries["count"]; $i++){
+            if ($entries[$i]["primarygrouptoken"][0]==$gid){
+                $r=$entries[$i]["distinguishedname"][0];
+                $i=$entries["count"];
+            }
+        }
+
+        return ($r);
+    }
+
+    /**
+    * Coping with AD not returning the primary group
+    * http://support.microsoft.com/?kbid=321360
+    *
+    * This is a re-write based on code submitted by Bruce which prevents the
+    * need to search each security group to find the true primary group
+    *
+    * @param string $gid Group ID
+    * @param string $usersid User's Object SID
+    * @return string
+    */
+    protected function get_primary_group($gid, $usersid){
+        if ($gid===NULL || $usersid===NULL){ return (false); }
+        $r=false;
+
+        $gsid = substr_replace($usersid,pack('V',$gid),strlen($usersid)-4,4);
+        $filter='(objectsid='.$this->getTextSID($gsid).')';
+        $fields=array("samaccountname","distinguishedname");
+        $sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields);
+        $entries = ldap_get_entries($this->_conn, $sr);
+
+        return $entries[0]['distinguishedname'][0];
+     }
+
+    /**
+    * Convert a binary SID to a text SID
+    *
+    * @param string $binsid A Binary SID
+    * @return string
+    */
+     protected function getTextSID($binsid) {
+        $hex_sid = bin2hex($binsid);
+        $rev = hexdec(substr($hex_sid, 0, 2));
+        $subcount = hexdec(substr($hex_sid, 2, 2));
+        $auth = hexdec(substr($hex_sid, 4, 12));
+        $result = "$rev-$auth";
+
+        for ($x=0;$x < $subcount; $x++) {
+            $subauth[$x] =
+                hexdec($this->little_endian(substr($hex_sid, 16 + ($x * 8), 8)));
+                $result .= "-" . $subauth[$x];
+        }
+
+        // Cheat by tacking on the S-
+        return 'S-' . $result;
+     }
+
+    /**
+    * Converts a little-endian hex number to one that hexdec() can convert
+    *
+    * @param string $hex A hex code
+    * @return string
+    */
+     protected function little_endian($hex) {
+        $result = '';
+        for ($x = strlen($hex) - 2; $x >= 0; $x = $x - 2) {
+            $result .= substr($hex, $x, 2);
+        }
+        return $result;
+     }
+
+    /**
+    * Converts a binary attribute to a string
+    *
+    * @param string $bin A binary LDAP attribute
+    * @return string
+    */
+    protected function binary2text($bin) {
+        $hex_guid = bin2hex($bin);
+        $hex_guid_to_guid_str = '';
+        for($k = 1; $k <= 4; ++$k) {
+            $hex_guid_to_guid_str .= substr($hex_guid, 8 - 2 * $k, 2);
+        }
+        $hex_guid_to_guid_str .= '-';
+        for($k = 1; $k <= 2; ++$k) {
+            $hex_guid_to_guid_str .= substr($hex_guid, 12 - 2 * $k, 2);
+        }
+        $hex_guid_to_guid_str .= '-';
+        for($k = 1; $k <= 2; ++$k) {
+            $hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2);
+        }
+        $hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4);
+        $hex_guid_to_guid_str .= '-' . substr($hex_guid, 20);
+        return strtoupper($hex_guid_to_guid_str);
+    }
+
+    /**
+    * Converts a binary GUID to a string GUID
+    *
+    * @param string $binaryGuid The binary GUID attribute to convert
+    * @return string
+    */
+    public function decodeGuid($binaryGuid) {
+        if ($binaryGuid === null){ return ("Missing compulsory field [binaryGuid]"); }
+
+        $strGUID = $this->binary2text($binaryGuid);
+        return ($strGUID);
+    }
+
+    /**
+    * Converts a string GUID to a hexdecimal value so it can be queried
+    *
+    * @param string $strGUID A string representation of a GUID
+    * @return string
+    */
+    protected function strguid2hex($strGUID) {
+        $strGUID = str_replace('-', '', $strGUID);
+
+        $octet_str = '\\' . substr($strGUID, 6, 2);
+        $octet_str .= '\\' . substr($strGUID, 4, 2);
+        $octet_str .= '\\' . substr($strGUID, 2, 2);
+        $octet_str .= '\\' . substr($strGUID, 0, 2);
+        $octet_str .= '\\' . substr($strGUID, 10, 2);
+        $octet_str .= '\\' . substr($strGUID, 8, 2);
+        $octet_str .= '\\' . substr($strGUID, 14, 2);
+        $octet_str .= '\\' . substr($strGUID, 12, 2);
+        //$octet_str .= '\\' . substr($strGUID, 16, strlen($strGUID));
+        for ($i=16; $i<=(strlen($strGUID)-2); $i++) {
+            if (($i % 2) == 0) {
+                $octet_str .= '\\' . substr($strGUID, $i, 2);
+            }
+        }
+
+        return $octet_str;
+    }
+
+    /**
+    * Obtain the user's distinguished name based on their userid
+    *
+    *
+    * @param string $username The username
+    * @param bool $isGUID Is the username passed a GUID or a samAccountName
+    * @return string
+    */
+    protected function user_dn($username,$isGUID=false){
+        $user=$this->user_info($username,array("cn"),$isGUID);
+        if ($user[0]["dn"]===NULL){ return (false); }
+        $user_dn=$user[0]["dn"];
+        return ($user_dn);
+    }
+
+    /**
+    * Encode a password for transmission over LDAP
+    *
+    * @param string $password The password to encode
+    * @return string
+    */
+    protected function encode_password($password){
+        $password="\"".$password."\"";
+        $encoded="";
+        for ($i=0; $i 
+    * @return string
+    */
+    protected function ldap_slashes($str){
+        return preg_replace('/([\x00-\x1F\*\(\)\\\\])/e',
+                            '"\\\\\".join("",unpack("H2","$1"))',
+                            $str);
+    }
+
+    /**
+    * Select a random domain controller from your domain controller array
+    *
+    * @return string
+    */
+    protected function random_controller(){
+        mt_srand(doubleval(microtime()) * 100000000); // For older PHP versions
+        return ($this->_domain_controllers[array_rand($this->_domain_controllers)]);
+    }
+
+    /**
+    * Account control options
+    *
+    * @param array $options The options to convert to int
+    * @return int
+    */
+    protected function account_control($options){
+        $val=0;
+
+        if (is_array($options)){
+            if (in_array("SCRIPT",$options)){ $val=$val+1; }
+            if (in_array("ACCOUNTDISABLE",$options)){ $val=$val+2; }
+            if (in_array("HOMEDIR_REQUIRED",$options)){ $val=$val+8; }
+            if (in_array("LOCKOUT",$options)){ $val=$val+16; }
+            if (in_array("PASSWD_NOTREQD",$options)){ $val=$val+32; }
+            //PASSWD_CANT_CHANGE Note You cannot assign this permission by directly modifying the UserAccountControl attribute.
+            //For information about how to set the permission programmatically, see the "Property flag descriptions" section.
+            if (in_array("ENCRYPTED_TEXT_PWD_ALLOWED",$options)){ $val=$val+128; }
+            if (in_array("TEMP_DUPLICATE_ACCOUNT",$options)){ $val=$val+256; }
+            if (in_array("NORMAL_ACCOUNT",$options)){ $val=$val+512; }
+            if (in_array("INTERDOMAIN_TRUST_ACCOUNT",$options)){ $val=$val+2048; }
+            if (in_array("WORKSTATION_TRUST_ACCOUNT",$options)){ $val=$val+4096; }
+            if (in_array("SERVER_TRUST_ACCOUNT",$options)){ $val=$val+8192; }
+            if (in_array("DONT_EXPIRE_PASSWORD",$options)){ $val=$val+65536; }
+            if (in_array("MNS_LOGON_ACCOUNT",$options)){ $val=$val+131072; }
+            if (in_array("SMARTCARD_REQUIRED",$options)){ $val=$val+262144; }
+            if (in_array("TRUSTED_FOR_DELEGATION",$options)){ $val=$val+524288; }
+            if (in_array("NOT_DELEGATED",$options)){ $val=$val+1048576; }
+            if (in_array("USE_DES_KEY_ONLY",$options)){ $val=$val+2097152; }
+            if (in_array("DONT_REQ_PREAUTH",$options)){ $val=$val+4194304; }
+            if (in_array("PASSWORD_EXPIRED",$options)){ $val=$val+8388608; }
+            if (in_array("TRUSTED_TO_AUTH_FOR_DELEGATION",$options)){ $val=$val+16777216; }
+        }
+        return ($val);
+    }
+
+    /**
+    * Take an LDAP query and return the nice names, without all the LDAP prefixes (eg. CN, DN)
+    *
+    * @param array $groups
+    * @return array
+    */
+    protected function nice_names($groups){
+
+        $group_array=array();
+        for ($i=0; $i<$groups["count"]; $i++){ // For each group
+            $line=$groups[$i];
+
+            if (strlen($line)>0){
+                // More presumptions, they're all prefixed with CN=
+                // so we ditch the first three characters and the group
+                // name goes up to the first comma
+                $bits=explode(",",$line);
+                $group_array[]=substr($bits[0],3,(strlen($bits[0])-3));
+            }
+        }
+        return ($group_array);
+    }
+
+    /**
+    * Delete a distinguished name from Active Directory
+    * You should never need to call this yourself, just use the wrapper functions user_delete and contact_delete
+    *
+    * @param string $dn The distinguished name to delete
+    * @return bool
+    */
+    protected function dn_delete($dn){
+        $result=ldap_delete($this->_conn, $dn);
+        if ($result!=true){ return (false); }
+        return (true);
+    }
+
+    /**
+    * Convert a boolean value to a string
+    * You should never need to call this yourself
+    *
+    * @param bool $bool Boolean value
+    * @return string
+    */
+    protected function bool2str($bool) {
+        return ($bool) ? 'TRUE' : 'FALSE';
+    }
+
+    /**
+    * Convert 8bit characters e.g. accented characters to UTF8 encoded characters
+    */
+    protected function encode8bit(&$item, $key) {
+        $encode = false;
+        if (is_string($item)) {
+            for ($i=0; $i> 7) {
+                    $encode = true;
+                }
+            }
+        }
+        if ($encode === true && $key != 'password') {
+            $item = utf8_encode($item);
+        }
+    }
+}
+
+/**
+* adLDAP Exception Handler
+*
+* Exceptions of this type are thrown on bind failure or when SSL is required but not configured
+* Example:
+* try {
+*   $adldap = new adLDAP();
+* }
+* catch (adLDAPException $e) {
+*   echo $e;
+*   exit();
+* }
+*/
+class adLDAPException extends Exception {}
+
+?>
diff --git a/inc/auth.php b/inc/auth.php
new file mode 100644
index 0000000..e0f58e5
--- /dev/null
+++ b/inc/auth.php
@@ -0,0 +1,1024 @@
+
+ */
+
+if(!defined('DOKU_INC')) die('meh.');
+
+// some ACL level defines
+define('AUTH_NONE',0);
+define('AUTH_READ',1);
+define('AUTH_EDIT',2);
+define('AUTH_CREATE',4);
+define('AUTH_UPLOAD',8);
+define('AUTH_DELETE',16);
+define('AUTH_ADMIN',255);
+
+/**
+ * Initialize the auth system.
+ *
+ * This function is automatically called at the end of init.php
+ *
+ * This used to be the main() of the auth.php
+ *
+ * @todo backend loading maybe should be handled by the class autoloader
+ * @todo maybe split into multiple functions at the XXX marked positions
+ */
+function auth_setup(){
+    global $conf;
+    global $auth;
+    global $AUTH_ACL;
+    global $lang;
+    global $config_cascade;
+    $AUTH_ACL = array();
+
+    if(!$conf['useacl']) return false;
+
+    // load the the backend auth functions and instantiate the auth object XXX
+    if (@file_exists(DOKU_INC.'inc/auth/'.$conf['authtype'].'.class.php')) {
+        require_once(DOKU_INC.'inc/auth/basic.class.php');
+        require_once(DOKU_INC.'inc/auth/'.$conf['authtype'].'.class.php');
+
+        $auth_class = "auth_".$conf['authtype'];
+        if (class_exists($auth_class)) {
+            $auth = new $auth_class();
+            if ($auth->success == false) {
+                // degrade to unauthenticated user
+                unset($auth);
+                auth_logoff();
+                msg($lang['authtempfail'], -1);
+            }
+        } else {
+            nice_die($lang['authmodfailed']);
+        }
+    } else {
+        nice_die($lang['authmodfailed']);
+    }
+
+    if(!$auth) return;
+
+    // do the login either by cookie or provided credentials XXX
+    if (!isset($_REQUEST['u'])) $_REQUEST['u'] = '';
+    if (!isset($_REQUEST['p'])) $_REQUEST['p'] = '';
+    if (!isset($_REQUEST['r'])) $_REQUEST['r'] = '';
+    $_REQUEST['http_credentials'] = false;
+    if (!$conf['rememberme']) $_REQUEST['r'] = false;
+
+    // handle renamed HTTP_AUTHORIZATION variable (can happen when a fix like
+    // the one presented at
+    // http://www.besthostratings.com/articles/http-auth-php-cgi.html is used
+    // for enabling HTTP authentication with CGI/SuExec)
+    if(isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']))
+        $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
+    // streamline HTTP auth credentials (IIS/rewrite -> mod_php)
+    if(isset($_SERVER['HTTP_AUTHORIZATION'])){
+        list($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW']) =
+            explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
+    }
+
+    // if no credentials were given try to use HTTP auth (for SSO)
+    if(empty($_REQUEST['u']) && empty($_COOKIE[DOKU_COOKIE]) && !empty($_SERVER['PHP_AUTH_USER'])){
+        $_REQUEST['u'] = $_SERVER['PHP_AUTH_USER'];
+        $_REQUEST['p'] = $_SERVER['PHP_AUTH_PW'];
+        $_REQUEST['http_credentials'] = true;
+    }
+
+    // apply cleaning
+    $_REQUEST['u'] = $auth->cleanUser($_REQUEST['u']);
+
+    if(isset($_REQUEST['authtok'])){
+        // when an authentication token is given, trust the session
+        auth_validateToken($_REQUEST['authtok']);
+    }elseif(!is_null($auth) && $auth->canDo('external')){
+        // external trust mechanism in place
+        $auth->trustExternal($_REQUEST['u'],$_REQUEST['p'],$_REQUEST['r']);
+    }else{
+        $evdata = array(
+                'user'     => $_REQUEST['u'],
+                'password' => $_REQUEST['p'],
+                'sticky'   => $_REQUEST['r'],
+                'silent'   => $_REQUEST['http_credentials'],
+                );
+        trigger_event('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper');
+    }
+
+    //load ACL into a global array XXX
+    $AUTH_ACL = auth_loadACL();
+}
+
+/**
+ * Loads the ACL setup and handle user wildcards
+ *
+ * @author Andreas Gohr 
+ * @returns array
+ */
+function auth_loadACL(){
+    global $config_cascade;
+
+    if(!is_readable($config_cascade['acl']['default'])) return array();
+
+    $acl = file($config_cascade['acl']['default']);
+
+    //support user wildcard
+    if(isset($_SERVER['REMOTE_USER'])){
+        $len = count($acl);
+        for($i=0; $i<$len; $i++){
+            if($acl[$i]{0} == '#') continue;
+            list($id,$rest) = preg_split('/\s+/',$acl[$i],2);
+            $id   = str_replace('%USER%',cleanID($_SERVER['REMOTE_USER']),$id);
+            $rest = str_replace('%USER%',auth_nameencode($_SERVER['REMOTE_USER']),$rest);
+            $acl[$i] = "$id\t$rest";
+        }
+    }
+    return $acl;
+}
+
+function auth_login_wrapper($evdata) {
+    return auth_login($evdata['user'],
+                      $evdata['password'],
+                      $evdata['sticky'],
+                      $evdata['silent']);
+}
+
+/**
+ * This tries to login the user based on the sent auth credentials
+ *
+ * The authentication works like this: if a username was given
+ * a new login is assumed and user/password are checked. If they
+ * are correct the password is encrypted with blowfish and stored
+ * together with the username in a cookie - the same info is stored
+ * in the session, too. Additonally a browserID is stored in the
+ * session.
+ *
+ * If no username was given the cookie is checked: if the username,
+ * crypted password and browserID match between session and cookie
+ * no further testing is done and the user is accepted
+ *
+ * If a cookie was found but no session info was availabe the
+ * blowfish encrypted password from the cookie is decrypted and
+ * together with username rechecked by calling this function again.
+ *
+ * On a successful login $_SERVER[REMOTE_USER] and $USERINFO
+ * are set.
+ *
+ * @author  Andreas Gohr 
+ *
+ * @param   string  $user    Username
+ * @param   string  $pass    Cleartext Password
+ * @param   bool    $sticky  Cookie should not expire
+ * @param   bool    $silent  Don't show error on bad auth
+ * @return  bool             true on successful auth
+ */
+function auth_login($user,$pass,$sticky=false,$silent=false){
+    global $USERINFO;
+    global $conf;
+    global $lang;
+    global $auth;
+    $sticky ? $sticky = true : $sticky = false; //sanity check
+
+    if (!$auth) return false;
+
+    if(!empty($user)){
+        //usual login
+        if ($auth->checkPass($user,$pass)){
+            // make logininfo globally available
+            $_SERVER['REMOTE_USER'] = $user;
+            $secret = auth_cookiesalt(!$sticky); //bind non-sticky to session
+            auth_setCookie($user,PMA_blowfish_encrypt($pass,$secret),$sticky);
+            return true;
+        }else{
+            //invalid credentials - log off
+            if(!$silent) msg($lang['badlogin'],-1);
+            auth_logoff();
+            return false;
+        }
+    }else{
+        // read cookie information
+        list($user,$sticky,$pass) = auth_getCookie();
+        if($user && $pass){
+            // we got a cookie - see if we can trust it
+
+            // get session info
+            $session = $_SESSION[DOKU_COOKIE]['auth'];
+            if(isset($session) &&
+                    $auth->useSessionCache($user) &&
+                    ($session['time'] >= time()-$conf['auth_security_timeout']) &&
+                    ($session['user'] == $user) &&
+                    ($session['pass'] == sha1($pass)) &&  //still crypted
+                    ($session['buid'] == auth_browseruid()) ){
+
+                // he has session, cookie and browser right - let him in
+                $_SERVER['REMOTE_USER'] = $user;
+                $USERINFO = $session['info']; //FIXME move all references to session
+                return true;
+            }
+            // no we don't trust it yet - recheck pass but silent
+            $secret = auth_cookiesalt(!$sticky); //bind non-sticky to session
+            $pass = PMA_blowfish_decrypt($pass,$secret);
+            return auth_login($user,$pass,$sticky,true);
+        }
+    }
+    //just to be sure
+    auth_logoff(true);
+    return false;
+}
+
+/**
+ * Checks if a given authentication token was stored in the session
+ *
+ * Will setup authentication data using data from the session if the
+ * token is correct. Will exit with a 401 Status if not.
+ *
+ * @author Andreas Gohr 
+ * @param  string $token The authentication token
+ * @return boolean true (or will exit on failure)
+ */
+function auth_validateToken($token){
+    if(!$token || $token != $_SESSION[DOKU_COOKIE]['auth']['token']){
+        // bad token
+        header("HTTP/1.0 401 Unauthorized");
+        print 'Invalid auth token - maybe the session timed out';
+        unset($_SESSION[DOKU_COOKIE]['auth']['token']); // no second chance
+        exit;
+    }
+    // still here? trust the session data
+    global $USERINFO;
+    $_SERVER['REMOTE_USER'] = $_SESSION[DOKU_COOKIE]['auth']['user'];
+    $USERINFO = $_SESSION[DOKU_COOKIE]['auth']['info'];
+    return true;
+}
+
+/**
+ * Create an auth token and store it in the session
+ *
+ * NOTE: this is completely unrelated to the getSecurityToken() function
+ *
+ * @author Andreas Gohr 
+ * @return string The auth token
+ */
+function auth_createToken(){
+    $token = md5(mt_rand());
+    @session_start(); // reopen the session if needed
+    $_SESSION[DOKU_COOKIE]['auth']['token'] = $token;
+    session_write_close();
+    return $token;
+}
+
+/**
+ * Builds a pseudo UID from browser and IP data
+ *
+ * This is neither unique nor unfakable - still it adds some
+ * security. Using the first part of the IP makes sure
+ * proxy farms like AOLs are stil okay.
+ *
+ * @author  Andreas Gohr 
+ *
+ * @return  string  a MD5 sum of various browser headers
+ */
+function auth_browseruid(){
+    $ip   = clientIP(true);
+    $uid  = '';
+    $uid .= $_SERVER['HTTP_USER_AGENT'];
+    $uid .= $_SERVER['HTTP_ACCEPT_ENCODING'];
+    $uid .= $_SERVER['HTTP_ACCEPT_LANGUAGE'];
+    $uid .= $_SERVER['HTTP_ACCEPT_CHARSET'];
+    $uid .= substr($ip,0,strpos($ip,'.'));
+    return md5($uid);
+}
+
+/**
+ * Creates a random key to encrypt the password in cookies
+ *
+ * This function tries to read the password for encrypting
+ * cookies from $conf['metadir'].'/_htcookiesalt'
+ * if no such file is found a random key is created and
+ * and stored in this file.
+ *
+ * @author  Andreas Gohr 
+ * @param   bool $addsession if true, the sessionid is added to the salt
+ * @return  string
+ */
+function auth_cookiesalt($addsession=false){
+    global $conf;
+    $file = $conf['metadir'].'/_htcookiesalt';
+    $salt = io_readFile($file);
+    if(empty($salt)){
+        $salt = uniqid(rand(),true);
+        io_saveFile($file,$salt);
+    }
+    if($addsession){
+        $salt .= session_id();
+    }
+    return $salt;
+}
+
+/**
+ * Log out the current user
+ *
+ * This clears all authentication data and thus log the user
+ * off. It also clears session data.
+ *
+ * @author  Andreas Gohr 
+ * @param bool $keepbc - when true, the breadcrumb data is not cleared
+ */
+function auth_logoff($keepbc=false){
+    global $conf;
+    global $USERINFO;
+    global $INFO, $ID;
+    global $auth;
+
+    // make sure the session is writable (it usually is)
+    @session_start();
+
+    if(isset($_SESSION[DOKU_COOKIE]['auth']['user']))
+        unset($_SESSION[DOKU_COOKIE]['auth']['user']);
+    if(isset($_SESSION[DOKU_COOKIE]['auth']['pass']))
+        unset($_SESSION[DOKU_COOKIE]['auth']['pass']);
+    if(isset($_SESSION[DOKU_COOKIE]['auth']['info']))
+        unset($_SESSION[DOKU_COOKIE]['auth']['info']);
+    if(!$keepbc && isset($_SESSION[DOKU_COOKIE]['bc']))
+        unset($_SESSION[DOKU_COOKIE]['bc']);
+    if(isset($_SERVER['REMOTE_USER']))
+        unset($_SERVER['REMOTE_USER']);
+    $USERINFO=null; //FIXME
+
+    $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'];
+    if (version_compare(PHP_VERSION, '5.2.0', '>')) {
+        setcookie(DOKU_COOKIE,'',time()-600000,$cookieDir,'',($conf['securecookie'] && is_ssl()),true);
+    }else{
+        setcookie(DOKU_COOKIE,'',time()-600000,$cookieDir,'',($conf['securecookie'] && is_ssl()));
+    }
+
+    if($auth) $auth->logOff();
+}
+
+/**
+ * Check if a user is a manager
+ *
+ * Should usually be called without any parameters to check the current
+ * user.
+ *
+ * The info is available through $INFO['ismanager'], too
+ *
+ * @author Andreas Gohr 
+ * @see    auth_isadmin
+ * @param  string user      - Username
+ * @param  array  groups    - List of groups the user is in
+ * @param  bool   adminonly - when true checks if user is admin
+ */
+function auth_ismanager($user=null,$groups=null,$adminonly=false){
+    global $conf;
+    global $USERINFO;
+    global $auth;
+
+    if (!$auth) return false;
+    if(is_null($user)) {
+        if (!isset($_SERVER['REMOTE_USER'])) {
+            return false;
+        } else {
+            $user = $_SERVER['REMOTE_USER'];
+        }
+    }
+    if(is_null($groups)){
+        $groups = (array) $USERINFO['grps'];
+    }
+
+    // check superuser match
+    if(auth_isMember($conf['superuser'],$user, $groups)) return true;
+    if($adminonly) return false;
+    // check managers
+    if(auth_isMember($conf['manager'],$user, $groups)) return true;
+
+    return false;
+}
+
+/**
+ * Check if a user is admin
+ *
+ * Alias to auth_ismanager with adminonly=true
+ *
+ * The info is available through $INFO['isadmin'], too
+ *
+ * @author Andreas Gohr 
+ * @see auth_ismanager
+ */
+function auth_isadmin($user=null,$groups=null){
+    return auth_ismanager($user,$groups,true);
+}
+
+
+/**
+ * Match a user and his groups against a comma separated list of
+ * users and groups to determine membership status
+ *
+ * Note: all input should NOT be nameencoded.
+ *
+ * @param $memberlist string commaseparated list of allowed users and groups
+ * @param $user       string user to match against
+ * @param $groups     array  groups the user is member of
+ * @returns bool      true for membership acknowledged
+ */
+function auth_isMember($memberlist,$user,array $groups){
+    global $auth;
+    if (!$auth) return false;
+
+    // clean user and groups
+    if(!$auth->isCaseSensitive()){
+        $user = utf8_strtolower($user);
+        $groups = array_map('utf8_strtolower',$groups);
+    }
+    $user = $auth->cleanUser($user);
+    $groups = array_map(array($auth,'cleanGroup'),$groups);
+
+    // extract the memberlist
+    $members = explode(',',$memberlist);
+    $members = array_map('trim',$members);
+    $members = array_unique($members);
+    $members = array_filter($members);
+
+    // compare cleaned values
+    foreach($members as $member){
+        if(!$auth->isCaseSensitive()) $member = utf8_strtolower($member);
+        if($member[0] == '@'){
+            $member = $auth->cleanGroup(substr($member,1));
+            if(in_array($member, $groups)) return true;
+        }else{
+            $member = $auth->cleanUser($member);
+            if($member == $user) return true;
+        }
+    }
+
+    // still here? not a member!
+    return false;
+}
+
+/**
+ * Convinience function for auth_aclcheck()
+ *
+ * This checks the permissions for the current user
+ *
+ * @author  Andreas Gohr 
+ *
+ * @param  string  $id  page ID (needs to be resolved and cleaned)
+ * @return int          permission level
+ */
+function auth_quickaclcheck($id){
+    global $conf;
+    global $USERINFO;
+    # if no ACL is used always return upload rights
+    if(!$conf['useacl']) return AUTH_UPLOAD;
+    return auth_aclcheck($id,$_SERVER['REMOTE_USER'],$USERINFO['grps']);
+}
+
+/**
+ * Returns the maximum rights a user has for
+ * the given ID or its namespace
+ *
+ * @author  Andreas Gohr 
+ *
+ * @param  string  $id     page ID (needs to be resolved and cleaned)
+ * @param  string  $user   Username
+ * @param  array   $groups Array of groups the user is in
+ * @return int             permission level
+ */
+function auth_aclcheck($id,$user,$groups){
+    global $conf;
+    global $AUTH_ACL;
+    global $auth;
+
+    // if no ACL is used always return upload rights
+    if(!$conf['useacl']) return AUTH_UPLOAD;
+    if (!$auth) return AUTH_NONE;
+
+    //make sure groups is an array
+    if(!is_array($groups)) $groups = array();
+
+    //if user is superuser or in superusergroup return 255 (acl_admin)
+    if(auth_isadmin($user,$groups)) { return AUTH_ADMIN; }
+
+    $ci = '';
+    if(!$auth->isCaseSensitive()) $ci = 'ui';
+
+    $user = $auth->cleanUser($user);
+    $groups = array_map(array($auth,'cleanGroup'),(array)$groups);
+    $user = auth_nameencode($user);
+
+    //prepend groups with @ and nameencode
+    $cnt = count($groups);
+    for($i=0; $i<$cnt; $i++){
+        $groups[$i] = '@'.auth_nameencode($groups[$i]);
+    }
+
+    $ns    = getNS($id);
+    $perm  = -1;
+
+    if($user || count($groups)){
+        //add ALL group
+        $groups[] = '@ALL';
+        //add User
+        if($user) $groups[] = $user;
+        //build regexp
+        $regexp   = join('|',$groups);
+    }else{
+        $regexp = '@ALL';
+    }
+
+    //check exact match first
+    $matches = preg_grep('/^'.preg_quote($id,'/').'\s+('.$regexp.')\s+/'.$ci,$AUTH_ACL);
+    if(count($matches)){
+        foreach($matches as $match){
+            $match = preg_replace('/#.*$/','',$match); //ignore comments
+            $acl   = preg_split('/\s+/',$match);
+            if($acl[2] > AUTH_DELETE) $acl[2] = AUTH_DELETE; //no admins in the ACL!
+            if($acl[2] > $perm){
+                $perm = $acl[2];
+            }
+        }
+        if($perm > -1){
+            //we had a match - return it
+            return $perm;
+        }
+    }
+
+    //still here? do the namespace checks
+    if($ns){
+        $path = $ns.':*';
+    }else{
+        $path = '*'; //root document
+    }
+
+    do{
+        $matches = preg_grep('/^'.preg_quote($path,'/').'\s+('.$regexp.')\s+/'.$ci,$AUTH_ACL);
+        if(count($matches)){
+            foreach($matches as $match){
+                $match = preg_replace('/#.*$/','',$match); //ignore comments
+                $acl   = preg_split('/\s+/',$match);
+                if($acl[2] > AUTH_DELETE) $acl[2] = AUTH_DELETE; //no admins in the ACL!
+                if($acl[2] > $perm){
+                    $perm = $acl[2];
+                }
+            }
+            //we had a match - return it
+            return $perm;
+        }
+
+        //get next higher namespace
+        $ns   = getNS($ns);
+
+        if($path != '*'){
+            $path = $ns.':*';
+            if($path == ':*') $path = '*';
+        }else{
+            //we did this already
+            //looks like there is something wrong with the ACL
+            //break here
+            msg('No ACL setup yet! Denying access to everyone.');
+            return AUTH_NONE;
+        }
+    }while(1); //this should never loop endless
+
+    //still here? return no permissions
+    return AUTH_NONE;
+}
+
+/**
+ * Encode ASCII special chars
+ *
+ * Some auth backends allow special chars in their user and groupnames
+ * The special chars are encoded with this function. Only ASCII chars
+ * are encoded UTF-8 multibyte are left as is (different from usual
+ * urlencoding!).
+ *
+ * Decoding can be done with rawurldecode
+ *
+ * @author Andreas Gohr 
+ * @see rawurldecode()
+ */
+function auth_nameencode($name,$skip_group=false){
+    global $cache_authname;
+    $cache =& $cache_authname;
+    $name  = (string) $name;
+
+    // never encode wildcard FS#1955
+    if($name == '%USER%') return $name;
+
+    if (!isset($cache[$name][$skip_group])) {
+        if($skip_group && $name{0} =='@'){
+            $cache[$name][$skip_group] = '@'.preg_replace('/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/e',
+                    "'%'.dechex(ord(substr('\\1',-1)))",substr($name,1));
+        }else{
+            $cache[$name][$skip_group] = preg_replace('/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f])/e',
+                    "'%'.dechex(ord(substr('\\1',-1)))",$name);
+        }
+    }
+
+    return $cache[$name][$skip_group];
+}
+
+/**
+ * Create a pronouncable password
+ *
+ * @author  Andreas Gohr 
+ * @link    http://www.phpbuilder.com/annotate/message.php3?id=1014451
+ *
+ * @return string  pronouncable password
+ */
+function auth_pwgen(){
+    $pw = '';
+    $c  = 'bcdfghjklmnprstvwz'; //consonants except hard to speak ones
+    $v  = 'aeiou';              //vowels
+    $a  = $c.$v;                //both
+
+    //use two syllables...
+    for($i=0;$i < 2; $i++){
+        $pw .= $c[rand(0, strlen($c)-1)];
+        $pw .= $v[rand(0, strlen($v)-1)];
+        $pw .= $a[rand(0, strlen($a)-1)];
+    }
+    //... and add a nice number
+    $pw .= rand(10,99);
+
+    return $pw;
+}
+
+/**
+ * Sends a password to the given user
+ *
+ * @author  Andreas Gohr 
+ *
+ * @return bool  true on success
+ */
+function auth_sendPassword($user,$password){
+    global $conf;
+    global $lang;
+    global $auth;
+    if (!$auth) return false;
+
+    $hdrs  = '';
+    $user     = $auth->cleanUser($user);
+    $userinfo = $auth->getUserData($user);
+
+    if(!$userinfo['mail']) return false;
+
+    $text = rawLocale('password');
+    $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text);
+    $text = str_replace('@FULLNAME@',$userinfo['name'],$text);
+    $text = str_replace('@LOGIN@',$user,$text);
+    $text = str_replace('@PASSWORD@',$password,$text);
+    $text = str_replace('@TITLE@',$conf['title'],$text);
+
+    if(empty($conf['mailprefix'])) {
+        $subject = $lang['regpwmail'];
+    } else {  
+        $subject = '['.$conf['mailprefix'].'] '.$lang['regpwmail'];
+    }
+
+    return mail_send($userinfo['name'].' <'.$userinfo['mail'].'>',
+            $subject,
+            $text,
+            $conf['mailfrom']);
+}
+
+/**
+ * Register a new user
+ *
+ * This registers a new user - Data is read directly from $_POST
+ *
+ * @author  Andreas Gohr 
+ *
+ * @return bool  true on success, false on any error
+ */
+function register(){
+    global $lang;
+    global $conf;
+    global $auth;
+
+    if(!$_POST['save']) return false;
+    if(!actionOK('register')) return false;
+
+    //clean username
+    $_POST['login'] = trim($auth->cleanUser($_POST['login']));
+
+    //clean fullname and email
+    $_POST['fullname'] = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/','',$_POST['fullname']));
+    $_POST['email']    = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/','',$_POST['email']));
+
+    if( empty($_POST['login']) ||
+        empty($_POST['fullname']) ||
+        empty($_POST['email']) ){
+        msg($lang['regmissing'],-1);
+        return false;
+    }
+
+    if ($conf['autopasswd']) {
+        $pass = auth_pwgen();                // automatically generate password
+    } elseif (empty($_POST['pass']) ||
+            empty($_POST['passchk'])) {
+        msg($lang['regmissing'], -1);        // complain about missing passwords
+        return false;
+    } elseif ($_POST['pass'] != $_POST['passchk']) {
+        msg($lang['regbadpass'], -1);      // complain about misspelled passwords
+        return false;
+    } else {
+        $pass = $_POST['pass'];              // accept checked and valid password
+    }
+
+    //check mail
+    if(!mail_isvalid($_POST['email'])){
+        msg($lang['regbadmail'],-1);
+        return false;
+    }
+
+    //okay try to create the user
+    if(!$auth->triggerUserMod('create', array($_POST['login'],$pass,$_POST['fullname'],$_POST['email']))){
+        msg($lang['reguexists'],-1);
+        return false;
+    }
+
+    // create substitutions for use in notification email
+    $substitutions = array(
+            'NEWUSER' => $_POST['login'],
+            'NEWNAME' => $_POST['fullname'],
+            'NEWEMAIL' => $_POST['email'],
+            );
+
+    if (!$conf['autopasswd']) {
+        msg($lang['regsuccess2'],1);
+        notify('', 'register', '', $_POST['login'], false, $substitutions);
+        return true;
+    }
+
+    // autogenerated password? then send him the password
+    if (auth_sendPassword($_POST['login'],$pass)){
+        msg($lang['regsuccess'],1);
+        notify('', 'register', '', $_POST['login'], false, $substitutions);
+        return true;
+    }else{
+        msg($lang['regmailfail'],-1);
+        return false;
+    }
+}
+
+/**
+ * Update user profile
+ *
+ * @author    Christopher Smith 
+ */
+function updateprofile() {
+    global $conf;
+    global $INFO;
+    global $lang;
+    global $auth;
+
+    if(empty($_POST['save'])) return false;
+    if(!checkSecurityToken()) return false;
+
+    if(!actionOK('profile')) {
+        msg($lang['profna'],-1);
+        return false;
+    }
+
+    if ($_POST['newpass'] != $_POST['passchk']) {
+        msg($lang['regbadpass'], -1);      // complain about misspelled passwords
+        return false;
+    }
+
+    //clean fullname and email
+    $_POST['fullname'] = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/','',$_POST['fullname']));
+    $_POST['email']    = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/','',$_POST['email']));
+
+    if ((empty($_POST['fullname']) && $auth->canDo('modName')) ||
+        (empty($_POST['email']) && $auth->canDo('modMail'))) {
+        msg($lang['profnoempty'],-1);
+        return false;
+    }
+
+    if (!mail_isvalid($_POST['email']) && $auth->canDo('modMail')){
+        msg($lang['regbadmail'],-1);
+        return false;
+    }
+
+    if ($_POST['fullname'] != $INFO['userinfo']['name'] && $auth->canDo('modName')) $changes['name'] = $_POST['fullname'];
+    if ($_POST['email'] != $INFO['userinfo']['mail'] && $auth->canDo('modMail')) $changes['mail'] = $_POST['email'];
+    if (!empty($_POST['newpass']) && $auth->canDo('modPass')) $changes['pass'] = $_POST['newpass'];
+
+    if (!count($changes)) {
+        msg($lang['profnochange'], -1);
+        return false;
+    }
+
+    if ($conf['profileconfirm']) {
+        if (!$auth->checkPass($_SERVER['REMOTE_USER'], $_POST['oldpass'])) {
+            msg($lang['badlogin'],-1);
+            return false;
+        }
+    }
+
+    if ($result = $auth->triggerUserMod('modify', array($_SERVER['REMOTE_USER'], $changes))) {
+        // update cookie and session with the changed data
+        if ($changes['pass']){
+            list($user,$sticky,$pass) = auth_getCookie();
+            $pass = PMA_blowfish_encrypt($changes['pass'],auth_cookiesalt(!$sticky));
+            auth_setCookie($_SERVER['REMOTE_USER'],$pass,(bool)$sticky);
+        }
+        return true;
+    }
+}
+
+/**
+ * Send a  new password
+ *
+ * This function handles both phases of the password reset:
+ *
+ *   - handling the first request of password reset
+ *   - validating the password reset auth token
+ *
+ * @author Benoit Chesneau 
+ * @author Chris Smith 
+ * @author Andreas Gohr 
+ *
+ * @return bool true on success, false on any error
+ */
+function act_resendpwd(){
+    global $lang;
+    global $conf;
+    global $auth;
+
+    if(!actionOK('resendpwd')) {
+        msg($lang['resendna'],-1);
+        return false;
+    }
+
+    $token = preg_replace('/[^a-f0-9]+/','',$_REQUEST['pwauth']);
+
+    if($token){
+        // we're in token phase
+
+        $tfile = $conf['cachedir'].'/'.$token{0}.'/'.$token.'.pwauth';
+        if(!@file_exists($tfile)){
+            msg($lang['resendpwdbadauth'],-1);
+            return false;
+        }
+        $user = io_readfile($tfile);
+        @unlink($tfile);
+        $userinfo = $auth->getUserData($user);
+        if(!$userinfo['mail']) {
+            msg($lang['resendpwdnouser'], -1);
+            return false;
+        }
+
+        $pass = auth_pwgen();
+        if (!$auth->triggerUserMod('modify', array($user,array('pass' => $pass)))) {
+            msg('error modifying user data',-1);
+            return false;
+        }
+
+        if (auth_sendPassword($user,$pass)) {
+            msg($lang['resendpwdsuccess'],1);
+        } else {
+            msg($lang['regmailfail'],-1);
+        }
+        return true;
+
+    } else {
+        // we're in request phase
+
+        if(!$_POST['save']) return false;
+
+        if (empty($_POST['login'])) {
+            msg($lang['resendpwdmissing'], -1);
+            return false;
+        } else {
+            $user = trim($auth->cleanUser($_POST['login']));
+        }
+
+        $userinfo = $auth->getUserData($user);
+        if(!$userinfo['mail']) {
+            msg($lang['resendpwdnouser'], -1);
+            return false;
+        }
+
+        // generate auth token
+        $token = md5(auth_cookiesalt().$user); //secret but user based
+        $tfile = $conf['cachedir'].'/'.$token{0}.'/'.$token.'.pwauth';
+        $url = wl('',array('do'=>'resendpwd','pwauth'=>$token),true,'&');
+
+        io_saveFile($tfile,$user);
+
+        $text = rawLocale('pwconfirm');
+        $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text);
+        $text = str_replace('@FULLNAME@',$userinfo['name'],$text);
+        $text = str_replace('@LOGIN@',$user,$text);
+        $text = str_replace('@TITLE@',$conf['title'],$text);
+        $text = str_replace('@CONFIRM@',$url,$text);
+
+        if(empty($conf['mailprefix'])) {
+            $subject = $lang['regpwmail'];
+        } else {  
+            $subject = '['.$conf['mailprefix'].'] '.$lang['regpwmail'];
+        }
+        
+        if(mail_send($userinfo['name'].' <'.$userinfo['mail'].'>',
+                     $subject,
+                     $text,
+                     $conf['mailfrom'])){
+            msg($lang['resendpwdconfirm'],1);
+        }else{
+            msg($lang['regmailfail'],-1);
+        }
+        return true;
+    }
+
+    return false; // never reached
+}
+
+/**
+ * Encrypts a password using the given method and salt
+ *
+ * If the selected method needs a salt and none was given, a random one
+ * is chosen.
+ *
+ * @author  Andreas Gohr 
+ * @return  string  The crypted password
+ */
+function auth_cryptPassword($clear,$method='',$salt=null){
+    global $conf;
+    if(empty($method)) $method = $conf['passcrypt'];
+
+    $pass  = new PassHash();
+    $call  = 'hash_'.$method;
+
+    if(!method_exists($pass,$call)){
+        msg("Unsupported crypt method $method",-1);
+        return false;
+    }
+
+    return $pass->$call($clear,$salt);
+}
+
+/**
+ * Verifies a cleartext password against a crypted hash
+ *
+ * @author  Andreas Gohr 
+ * @return  bool
+ */
+function auth_verifyPassword($clear,$crypt){
+    $pass = new PassHash();
+    return $pass->verify_hash($clear,$crypt);
+}
+
+/**
+ * Set the authentication cookie and add user identification data to the session
+ *
+ * @param string  $user       username
+ * @param string  $pass       encrypted password
+ * @param bool    $sticky     whether or not the cookie will last beyond the session
+ */
+function auth_setCookie($user,$pass,$sticky) {
+    global $conf;
+    global $auth;
+    global $USERINFO;
+
+    if (!$auth) return false;
+    $USERINFO = $auth->getUserData($user);
+
+    // set cookie
+    $cookie = base64_encode($user).'|'.((int) $sticky).'|'.base64_encode($pass);
+    $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'];
+    $time = $sticky ? (time()+60*60*24*365) : 0; //one year
+    if (version_compare(PHP_VERSION, '5.2.0', '>')) {
+        setcookie(DOKU_COOKIE,$cookie,$time,$cookieDir,'',($conf['securecookie'] && is_ssl()),true);
+    }else{
+        setcookie(DOKU_COOKIE,$cookie,$time,$cookieDir,'',($conf['securecookie'] && is_ssl()));
+    }
+    // set session
+    $_SESSION[DOKU_COOKIE]['auth']['user'] = $user;
+    $_SESSION[DOKU_COOKIE]['auth']['pass'] = sha1($pass);
+    $_SESSION[DOKU_COOKIE]['auth']['buid'] = auth_browseruid();
+    $_SESSION[DOKU_COOKIE]['auth']['info'] = $USERINFO;
+    $_SESSION[DOKU_COOKIE]['auth']['time'] = time();
+}
+
+/**
+ * Returns the user, (encrypted) password and sticky bit from cookie
+ *
+ * @returns array
+ */
+function auth_getCookie(){
+    if (!isset($_COOKIE[DOKU_COOKIE])) {
+        return array(null, null, null);
+    }
+    list($user,$sticky,$pass) = explode('|',$_COOKIE[DOKU_COOKIE],3);
+    $sticky = (bool) $sticky;
+    $pass   = base64_decode($pass);
+    $user   = base64_decode($user);
+    return array($user,$sticky,$pass);
+}
+
+//Setup VIM: ex: et ts=2 :
diff --git a/inc/blowfish.php b/inc/blowfish.php
new file mode 100644
index 0000000..bcf5804
--- /dev/null
+++ b/inc/blowfish.php
@@ -0,0 +1,514 @@
+
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Mike Cochrane 
+ * @version $Id: blowfish.php 11081 2008-01-25 09:35:48Z cybot_tm $
+ * @since   Horde 2.2
+ * @package horde.cipher
+ */
+
+// Change for phpMyAdmin by lem9:
+//class Horde_Cipher_blowfish extends Horde_Cipher {
+class Horde_Cipher_blowfish
+{
+    /* Pi Array */
+    var $p = array(
+            0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344,
+            0xA4093822, 0x299F31D0, 0x082EFA98, 0xEC4E6C89,
+            0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C,
+            0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917,
+            0x9216D5D9, 0x8979FB1B);
+
+    /* S Boxes */
+    var $s1 = array(
+            0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7,
+            0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99,
+            0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16,
+            0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E,
+            0x0D95748F, 0x728EB658, 0x718BCD58, 0x82154AEE,
+            0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013,
+            0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF,
+            0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E,
+            0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60,
+            0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440,
+            0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE,
+            0xA15486AF, 0x7C72E993, 0xB3EE1411, 0x636FBC2A,
+            0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E,
+            0xAFD6BA33, 0x6C24CF5C, 0x7A325381, 0x28958677,
+            0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193,
+            0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032,
+            0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88,
+            0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239,
+            0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E,
+            0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0,
+            0x6A51A0D2, 0xD8542F68, 0x960FA728, 0xAB5133A3,
+            0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98,
+            0xA1F1651D, 0x39AF0176, 0x66CA593E, 0x82430E88,
+            0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE,
+            0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6,
+            0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D,
+            0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B,
+            0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7,
+            0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA,
+            0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2, 0x196A2463,
+            0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F,
+            0x6DFC511F, 0x9B30952C, 0xCC814544, 0xAF5EBD09,
+            0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3,
+            0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB,
+            0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279,
+            0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8,
+            0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB,
+            0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82,
+            0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB,
+            0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573,
+            0x695B27B0, 0xBBCA58C8, 0xE1FFA35D, 0xB8F011A0,
+            0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B,
+            0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790,
+            0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8,
+            0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4,
+            0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0,
+            0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7,
+            0x8FF6E2FB, 0xF2122B64, 0x8888B812, 0x900DF01C,
+            0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD,
+            0x2F2F2218, 0xBE0E1777, 0xEA752DFE, 0x8B021FA1,
+            0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299,
+            0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9,
+            0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477,
+            0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF,
+            0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49,
+            0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF,
+            0x2464369B, 0xF009B91E, 0x5563911D, 0x59DFA6AA,
+            0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5,
+            0x83260376, 0x6295CFA9, 0x11C81968, 0x4E734A41,
+            0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915,
+            0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400,
+            0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915,
+            0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664,
+            0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A);
+    var $s2 = array(
+            0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623,
+            0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266,
+            0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1,
+            0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E,
+            0x3F54989A, 0x5B429D65, 0x6B8FE4D6, 0x99F73FD6,
+            0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1,
+            0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E,
+            0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1,
+            0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737,
+            0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8,
+            0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF,
+            0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD,
+            0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701,
+            0x3AE5E581, 0x37C2DADC, 0xC8B57634, 0x9AF3DDA7,
+            0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41,
+            0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331,
+            0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF,
+            0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF,
+            0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E,
+            0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87,
+            0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C,
+            0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2,
+            0xEF1C1847, 0x3215D908, 0xDD433B37, 0x24C2BA16,
+            0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD,
+            0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B,
+            0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509,
+            0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E,
+            0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3,
+            0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F,
+            0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A,
+            0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4,
+            0xF2F74EA7, 0x361D2B3D, 0x1939260F, 0x19C27960,
+            0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66,
+            0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28,
+            0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802,
+            0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84,
+            0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510,
+            0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF,
+            0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14,
+            0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E,
+            0x648B1EAF, 0x19BDF0CA, 0xA02369B9, 0x655ABB50,
+            0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7,
+            0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8,
+            0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281,
+            0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99,
+            0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696,
+            0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128,
+            0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73,
+            0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0,
+            0x45EEE2B6, 0xA3AAABEA, 0xDB6C4F15, 0xFACB4FD0,
+            0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105,
+            0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250,
+            0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3,
+            0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285,
+            0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00,
+            0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061,
+            0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB,
+            0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E,
+            0xA6078084, 0x19F8509E, 0xE8EFD855, 0x61D99735,
+            0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC,
+            0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9,
+            0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340,
+            0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20,
+            0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7);
+    var $s3 = array(
+            0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934,
+            0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068,
+            0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF,
+            0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840,
+            0x4D95FC1D, 0x96B591AF, 0x70F4DDD3, 0x66A02F45,
+            0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504,
+            0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A,
+            0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB,
+            0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE,
+            0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6,
+            0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42,
+            0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B,
+            0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2,
+            0x3A6EFA74, 0xDD5B4332, 0x6841E7F7, 0xCA7820FB,
+            0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527,
+            0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B,
+            0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33,
+            0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C,
+            0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3,
+            0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC,
+            0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17,
+            0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564,
+            0x257B7834, 0x602A9C60, 0xDFF8E8A3, 0x1F636C1B,
+            0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115,
+            0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922,
+            0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728,
+            0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0,
+            0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E,
+            0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37,
+            0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D,
+            0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804,
+            0xF1290DC7, 0xCC00FFA3, 0xB5390F92, 0x690FED0B,
+            0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3,
+            0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB,
+            0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D,
+            0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C,
+            0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350,
+            0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9,
+            0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A,
+            0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE,
+            0x9DBC8057, 0xF0F7C086, 0x60787BF8, 0x6003604D,
+            0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC,
+            0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F,
+            0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61,
+            0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2,
+            0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9,
+            0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2,
+            0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C,
+            0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E,
+            0xB77F19B6, 0xE0A9DC09, 0x662D09A1, 0xC4324633,
+            0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10,
+            0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169,
+            0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52,
+            0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027,
+            0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5,
+            0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62,
+            0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634,
+            0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76,
+            0x6F05E409, 0x4B7C0188, 0x39720A3D, 0x7C927C24,
+            0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC,
+            0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4,
+            0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C,
+            0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837,
+            0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0);
+    var $s4 = array(
+            0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B,
+            0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE,
+            0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B,
+            0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4,
+            0x5748AB2F, 0xBC946E79, 0xC6A376D2, 0x6549C2C8,
+            0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6,
+            0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304,
+            0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22,
+            0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4,
+            0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6,
+            0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9,
+            0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59,
+            0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593,
+            0xE990FD5A, 0x9E34D797, 0x2CF0B7D9, 0x022B8B51,
+            0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28,
+            0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C,
+            0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B,
+            0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28,
+            0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C,
+            0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD,
+            0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A,
+            0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319,
+            0x7533D928, 0xB155FDF5, 0x03563482, 0x8ABA3CBB,
+            0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F,
+            0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991,
+            0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32,
+            0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680,
+            0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166,
+            0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE,
+            0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB,
+            0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5,
+            0x72EACEA8, 0xFA6484BB, 0x8D6612AE, 0xBF3C6F47,
+            0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370,
+            0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D,
+            0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84,
+            0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048,
+            0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8,
+            0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD,
+            0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9,
+            0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7,
+            0x1A908749, 0xD44FBD9A, 0xD0DADECB, 0xD50ADA38,
+            0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F,
+            0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C,
+            0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525,
+            0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1,
+            0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442,
+            0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964,
+            0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E,
+            0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8,
+            0xDF359F8D, 0x9B992F2E, 0xE60B6F47, 0x0FE3F11D,
+            0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F,
+            0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299,
+            0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02,
+            0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC,
+            0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614,
+            0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A,
+            0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6,
+            0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B,
+            0x53113EC0, 0x1640E3D3, 0x38ABBD60, 0x2547ADF0,
+            0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060,
+            0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E,
+            0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9,
+            0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F,
+            0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6);
+
+    /* The number of rounds to do */
+    var $_rounds = 16;
+
+    /**
+     * Set the key to be used for en/decryption
+     *
+     * @param String $key   The key to use
+     */
+    function setKey($key) {
+        $key = $this->_formatKey($key);
+        $keyPos = $keyXor = 0;
+
+        $iMax = count($this->p);
+        $keyLen = count($key);
+        for ($i = 0; $i < $iMax; $i++) {
+            for ($t = 0; $t < 4; $t++) {
+                $keyXor = ($keyXor << 8) | (($key[$keyPos]) & 0x0ff);
+                if (++$keyPos == $keyLen) {
+                    $keyPos = 0;
+                }
+            }
+            $this->p[$i] = $this->p[$i] ^ $keyXor;
+        }
+
+        $encZero = array('L' => 0, 'R' => 0);
+        for ($i = 0; $i + 1 < $iMax; $i += 2) {
+            $encZero = $this->_encryptBlock($encZero['L'], $encZero['R']);
+            $this->p[$i] = $encZero['L'];
+            $this->p[$i + 1] = $encZero['R'];
+        }
+
+        $iMax = count($this->s1);
+        for ($i = 0; $i < $iMax; $i += 2) {
+            $encZero = $this->_encryptBlock($encZero['L'], $encZero['R']);
+            $this->s1[$i] = $encZero['L'];
+            $this->s1[$i + 1] = $encZero['R'];
+        }
+
+        $iMax = count($this->s2);
+        for ($i = 0; $i < $iMax; $i += 2) {
+            $encZero = $this->_encryptBlock($encZero['L'], $encZero['R']);
+            $this->s2[$i] = $encZero['L'];
+            $this->s2[$i + 1] = $encZero['R'];
+        }
+
+        $iMax = count($this->s3);
+        for ($i = 0; $i < $iMax; $i += 2) {
+            $encZero = $this->_encryptBlock($encZero['L'], $encZero['R']);
+            $this->s3[$i] = $encZero['L'];
+            $this->s3[$i + 1] = $encZero['R'];
+        }
+
+        $iMax = count($this->s4);
+        for ($i = 0; $i < $iMax; $i += 2) {
+            $encZero = $this->_encryptBlock($encZero['L'], $encZero['R']);
+            $this->s4[$i] = $encZero['L'];
+            $this->s4[$i + 1] = $encZero['R'];
+        }
+
+    }
+
+    /**
+     * Encrypt a block on data.
+     *
+     * @param String $block         The data to encrypt
+     * @param optional String $key  The key to use
+     *
+     * @return String the encrypted output
+     */
+    function encryptBlock($block, $key = null) {
+        if (!is_null($key)) {
+            $this->setKey($key);
+        }
+
+        list($L, $R) = array_values(unpack('N*', $block));
+        $parts = $this->_encryptBlock($L, $R);
+        return pack("NN", $parts['L'], $parts['R']);
+    }
+
+    /**
+     * Encrypt a block on data.
+     *
+     * @param String $L  The data to encrypt.
+     * @param String $R  The data to encrypt.
+     *
+     * @return String  The encrypted output.
+     */
+    function _encryptBlock($L, $R) {
+        $L ^= $this->p[0];
+        $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[1];
+        $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[2];
+        $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[3];
+        $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[4];
+        $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[5];
+        $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[6];
+        $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[7];
+        $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[8];
+        $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[9];
+        $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[10];
+        $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[11];
+        $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[12];
+        $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[13];
+        $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[14];
+        $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[15];
+        $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[16];
+        $R ^= $this->p[17];
+
+        return array('L' => $R, 'R' => $L);
+    }
+
+    /**
+     * Decrypt a block on data.
+     *
+     * @param String $block         The data to decrypt
+     * @param optional String $key  The key to use
+     *
+     * @return String the decrypted output
+     */
+    function decryptBlock($block, $key = null) {
+        if (!is_null($key)) {
+            $this->setKey($key);
+        }
+
+        // change for phpMyAdmin
+        $L = null;
+        $R = null;
+
+        $retarray = array_values(unpack('N*', $block));
+        if (isset($retarray[0])) {
+            $L = $retarray[0];
+        }
+        if (isset($retarray[1])) {
+            $R = $retarray[1];
+        }
+        // end change for phpMyAdmin
+
+        $L ^= $this->p[17];
+        $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[16];
+        $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[15];
+        $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[14];
+        $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[13];
+        $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[12];
+        $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[11];
+        $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[10];
+        $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[9];
+        $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[8];
+        $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[7];
+        $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[6];
+        $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[5];
+        $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[4];
+        $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[3];
+        $R ^= ((($this->s1[($L >> 24) & 0xFF] + $this->s2[($L >> 16) & 0x0ff]) ^ $this->s3[($L >> 8) & 0x0ff]) + $this->s4[$L & 0x0ff]) ^ $this->p[2];
+        $L ^= ((($this->s1[($R >> 24) & 0xFF] + $this->s2[($R >> 16) & 0x0ff]) ^ $this->s3[($R >> 8) & 0x0ff]) + $this->s4[$R & 0x0ff]) ^ $this->p[1];
+
+        $decrypted = pack("NN", $R ^ $this->p[0], $L);
+        return $decrypted;
+    }
+
+    /**
+     * Converts a text key into an array.
+     *
+     * @return array  The key.
+     */
+    function _formatKey($key) {
+        return array_values(unpack('C*', $key));
+    }
+
+}
+
+// higher-level functions:
+/**
+ * Encryption using blowfish algorithm
+ *
+ * @param   string  original data
+ * @param   string  the secret
+ *
+ * @return  string  the encrypted result
+ *
+ * @access  public
+ *
+ * @author  lem9
+ */
+function PMA_blowfish_encrypt($data, $secret) {
+    $pma_cipher = new Horde_Cipher_blowfish;
+    $encrypt = '';
+
+    $data .= '_'; // triming fixed for DokuWiki FS#1690 FS#1713
+    $mod = strlen($data) % 8;
+
+    if ($mod > 0) {
+        $data .= str_repeat("\0", 8 - $mod);
+    }
+
+    foreach (str_split($data, 8) as $chunk) {
+        $encrypt .= $pma_cipher->encryptBlock($chunk, $secret);
+    }
+    return base64_encode($encrypt);
+}
+
+/**
+ * Decryption using blowfish algorithm
+ *
+ * @param   string  encrypted data
+ * @param   string  the secret
+ *
+ * @return  string  original data
+ *
+ * @access  public
+ *
+ * @author  lem9
+ */
+function PMA_blowfish_decrypt($encdata, $secret) {
+    $pma_cipher = new Horde_Cipher_blowfish;
+    $decrypt = '';
+    $data = base64_decode($encdata);
+
+    foreach (str_split($data, 8) as $chunk) {
+        $decrypt .= $pma_cipher->decryptBlock($chunk, $secret);
+    }
+    return substr(rtrim($decrypt, "\0"), 0, -1); // triming fixed for DokuWiki FS#1690 FS#1713
+}
diff --git a/inc/cache.php b/inc/cache.php
new file mode 100644
index 0000000..ff78e37
--- /dev/null
+++ b/inc/cache.php
@@ -0,0 +1,270 @@
+
+ */
+
+if(!defined('DOKU_INC')) die('meh.');
+
+class cache {
+    var $key = '';          // primary identifier for this item
+    var $ext = '';          // file ext for cache data, secondary identifier for this item
+    var $cache = '';        // cache file name
+    var $depends = array(); // array containing cache dependency information,
+    //   used by _useCache to determine cache validity
+
+    var $_event = '';       // event to be triggered during useCache
+
+    function cache($key,$ext) {
+        $this->key = $key;
+        $this->ext = $ext;
+        $this->cache = getCacheName($key,$ext);
+    }
+
+    /**
+     * public method to determine whether the cache can be used
+     *
+     * to assist in cetralisation of event triggering and calculation of cache statistics,
+     * don't override this function override _useCache()
+     *
+     * @param  array   $depends   array of cache dependencies, support dependecies:
+     *                            'age'   => max age of the cache in seconds
+     *                            'files' => cache must be younger than mtime of each file
+     *                                       (nb. dependency passes if file doesn't exist)
+     *
+     * @return bool    true if cache can be used, false otherwise
+     */
+    function useCache($depends=array()) {
+        $this->depends = $depends;
+        $this->_addDependencies();
+
+        if ($this->_event) {
+            return $this->_stats(trigger_event($this->_event,$this,array($this,'_useCache')));
+        } else {
+            return $this->_stats($this->_useCache());
+        }
+    }
+
+    /**
+     * private method containing cache use decision logic
+     *
+     * this function processes the following keys in the depends array
+     *   purge - force a purge on any non empty value
+     *   age   - expire cache if older than age (seconds)
+     *   files - expire cache if any file in this array was updated more recently than the cache
+     *
+     * can be overridden
+     *
+     * @return bool               see useCache()
+     */
+    function _useCache() {
+
+        if (!empty($this->depends['purge'])) return false;              // purge requested?
+        if (!($this->_time = @filemtime($this->cache))) return false;   // cache exists?
+
+        // cache too old?
+        if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) return false;
+
+        if (!empty($this->depends['files'])) {
+            foreach ($this->depends['files'] as $file) {
+                if ($this->_time < @filemtime($file)) return false;         // cache older than files it depends on?
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * add dependencies to the depends array
+     *
+     * this method should only add dependencies,
+     * it should not remove any existing dependencies and
+     * it should only overwrite a dependency when the new value is more stringent than the old
+     */
+    function _addDependencies() {
+        if (isset($_REQUEST['purge'])) $this->depends['purge'] = true;   // purge requested
+    }
+
+    /**
+     * retrieve the cached data
+     *
+     * @param   bool   $clean   true to clean line endings, false to leave line endings alone
+     * @return  string          cache contents
+     */
+    function retrieveCache($clean=true) {
+        return io_readFile($this->cache, $clean);
+    }
+
+    /**
+     * cache $data
+     *
+     * @param   string $data   the data to be cached
+     * @return  bool           true on success, false otherwise
+     */
+    function storeCache($data) {
+        return io_savefile($this->cache, $data);
+    }
+
+    /**
+     * remove any cached data associated with this cache instance
+     */
+    function removeCache() {
+        @unlink($this->cache);
+    }
+
+    /**
+     * Record cache hits statistics.
+     * (Only when debugging allowed, to reduce overhead.)
+     *
+     * @param    bool   $success   result of this cache use attempt
+     * @return   bool              pass-thru $success value
+     */
+    function _stats($success) {
+        global $conf;
+        static $stats = null;
+        static $file;
+
+        if (!$conf['allowdebug']) { return $success; }
+
+        if (is_null($stats)) {
+            $file = $conf['cachedir'].'/cache_stats.txt';
+            $lines = explode("\n",io_readFile($file));
+
+            foreach ($lines as $line) {
+                $i = strpos($line,',');
+                $stats[substr($line,0,$i)] = $line;
+            }
+        }
+
+        if (isset($stats[$this->ext])) {
+            list($ext,$count,$hits) = explode(',',$stats[$this->ext]);
+        } else {
+            $ext = $this->ext;
+            $count = 0;
+            $hits = 0;
+        }
+
+        $count++;
+        if ($success) $hits++;
+        $stats[$this->ext] = "$ext,$count,$hits";
+
+        io_saveFile($file,join("\n",$stats));
+
+        return $success;
+    }
+}
+
+class cache_parser extends cache {
+
+    var $file = '';       // source file for cache
+    var $mode = '';       // input mode (represents the processing the input file will undergo)
+
+    var $_event = 'PARSER_CACHE_USE';
+
+    function cache_parser($id, $file, $mode) {
+        if ($id) $this->page = $id;
+        $this->file = $file;
+        $this->mode = $mode;
+
+        parent::cache($file.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'],'.'.$mode);
+    }
+
+    function _useCache() {
+
+        if (!@file_exists($this->file)) return false;                   // source exists?
+        return parent::_useCache();
+    }
+
+    function _addDependencies() {
+        global $conf, $config_cascade;
+
+        $this->depends['age'] = isset($this->depends['age']) ?
+            min($this->depends['age'],$conf['cachetime']) : $conf['cachetime'];
+
+        // parser cache file dependencies ...
+        $files = array($this->file,                                     // ... source
+                DOKU_INC.'inc/parser/parser.php',                // ... parser
+                DOKU_INC.'inc/parser/handler.php',               // ... handler
+                );
+        $files = array_merge($files, getConfigFiles('main'));           // ... wiki settings
+
+        $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
+        parent::_addDependencies();
+    }
+
+}
+
+class cache_renderer extends cache_parser {
+    function _useCache() {
+        global $conf;
+
+        if (!parent::_useCache()) return false;
+
+        if (!isset($this->page)) {
+            return true;
+        }
+
+        // check current link existence is consistent with cache version
+        // first check the purgefile
+        // - if the cache is more recent than the purgefile we know no links can have been updated
+        if ($this->_time >= @filemtime($conf['cachedir'].'/purgefile')) {
+            return true;
+        }
+
+        // for wiki pages, check metadata dependencies
+        $metadata = p_get_metadata($this->page);
+
+        if (!isset($metadata['relation']['references']) ||
+                empty($metadata['relation']['references'])) {
+            return true;
+        }
+
+        foreach ($metadata['relation']['references'] as $id => $exists) {
+            if ($exists != page_exists($id,'',false)) return false;
+        }
+
+        return true;
+    }
+
+    function _addDependencies() {
+
+        // renderer cache file dependencies ...
+        $files = array(
+                DOKU_INC.'inc/parser/'.$this->mode.'.php',       // ... the renderer
+                );
+
+        // page implies metadata and possibly some other dependencies
+        if (isset($this->page)) {
+
+            $metafile = metaFN($this->page,'.meta');
+            $files[] = $metafile;                                       // ... the page's own metadata
+
+            $valid = p_get_metadata($this->page, 'date valid');         // for xhtml this will render the metadata if needed
+            if (!empty($valid['age'])) {
+                $this->depends['age'] = isset($this->depends['age']) ?
+                    min($this->depends['age'],$valid['age']) : $valid['age'];
+            }
+        }
+
+        $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
+        parent::_addDependencies();
+    }
+}
+
+class cache_instructions extends cache_parser {
+
+    function cache_instructions($id, $file) {
+        parent::cache_parser($id, $file, 'i');
+    }
+
+    function retrieveCache($clean=true) {
+        $contents = io_readFile($this->cache, false);
+        return !empty($contents) ? unserialize($contents) : array();
+    }
+
+    function storeCache($instructions) {
+        return io_savefile($this->cache,serialize($instructions));
+    }
+}
diff --git a/inc/changelog.php b/inc/changelog.php
new file mode 100644
index 0000000..60f9b86
--- /dev/null
+++ b/inc/changelog.php
@@ -0,0 +1,539 @@
+
+ */
+
+// Constants for known core changelog line types.
+// Use these in place of string literals for more readable code.
+define('DOKU_CHANGE_TYPE_CREATE',       'C');
+define('DOKU_CHANGE_TYPE_EDIT',         'E');
+define('DOKU_CHANGE_TYPE_MINOR_EDIT',   'e');
+define('DOKU_CHANGE_TYPE_DELETE',       'D');
+define('DOKU_CHANGE_TYPE_REVERT',       'R');
+
+/**
+ * parses a changelog line into it's components
+ *
+ * @author Ben Coburn 
+ */
+function parseChangelogLine($line) {
+    $tmp = explode("\t", $line);
+    if ($tmp!==false && count($tmp)>1) {
+        $info = array();
+        $info['date']  = (int)$tmp[0]; // unix timestamp
+        $info['ip']    = $tmp[1]; // IPv4 address (127.0.0.1)
+        $info['type']  = $tmp[2]; // log line type
+        $info['id']    = $tmp[3]; // page id
+        $info['user']  = $tmp[4]; // user name
+        $info['sum']   = $tmp[5]; // edit summary (or action reason)
+        $info['extra'] = rtrim($tmp[6], "\n"); // extra data (varies by line type)
+        return $info;
+    } else { return false; }
+}
+
+/**
+ * Add's an entry to the changelog and saves the metadata for the page
+ *
+ * @param int    $date      Timestamp of the change
+ * @param String $id        Name of the affected page
+ * @param String $type      Type of the change see DOKU_CHANGE_TYPE_*
+ * @param String $summary   Summary of the change
+ * @param mixed  $extra     In case of a revert the revision (timestmp) of the reverted page
+ * @param array  $flags     Additional flags in a key value array.
+ *                             Availible flags:
+ *                             - ExternalEdit - mark as an external edit.
+ *
+ * @author Andreas Gohr 
+ * @author Esther Brunner 
+ * @author Ben Coburn 
+ */
+function addLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='', $extra='', $flags=null){
+    global $conf, $INFO;
+
+    // check for special flags as keys
+    if (!is_array($flags)) { $flags = array(); }
+    $flagExternalEdit = isset($flags['ExternalEdit']);
+
+    $id = cleanid($id);
+    $file = wikiFN($id);
+    $created = @filectime($file);
+    $minor = ($type===DOKU_CHANGE_TYPE_MINOR_EDIT);
+    $wasRemoved = ($type===DOKU_CHANGE_TYPE_DELETE);
+
+    if(!$date) $date = time(); //use current time if none supplied
+    $remote = (!$flagExternalEdit)?clientIP(true):'127.0.0.1';
+    $user   = (!$flagExternalEdit)?$_SERVER['REMOTE_USER']:'';
+
+    $strip = array("\t", "\n");
+    $logline = array(
+            'date'  => $date,
+            'ip'    => $remote,
+            'type'  => str_replace($strip, '', $type),
+            'id'    => $id,
+            'user'  => $user,
+            'sum'   => utf8_substr(str_replace($strip, '', $summary),0,255),
+            'extra' => str_replace($strip, '', $extra)
+            );
+
+    // update metadata
+    if (!$wasRemoved) {
+        $oldmeta = p_read_metadata($id);
+        $meta    = array();
+        if (!$INFO['exists'] && empty($oldmeta['persistent']['date']['created'])){ // newly created
+            $meta['date']['created'] = $created;
+            if ($user){
+                $meta['creator'] = $INFO['userinfo']['name'];
+                $meta['user']    = $user;
+            }
+        } elseif (!$INFO['exists'] && !empty($oldmeta['persistent']['date']['created'])) { // re-created / restored
+            $meta['date']['created']  = $oldmeta['persistent']['date']['created'];
+            $meta['date']['modified'] = $created; // use the files ctime here
+            $meta['creator'] = $oldmeta['persistent']['creator'];
+            if ($user) $meta['contributor'][$user] = $INFO['userinfo']['name'];
+        } elseif (!$minor) {   // non-minor modification
+            $meta['date']['modified'] = $date;
+            if ($user) $meta['contributor'][$user] = $INFO['userinfo']['name'];
+        }
+        $meta['last_change'] = $logline;
+        p_set_metadata($id, $meta);
+    }
+
+    // add changelog lines
+    $logline = implode("\t", $logline)."\n";
+    io_saveFile(metaFN($id,'.changes'),$logline,true); //page changelog
+    io_saveFile($conf['changelog'],$logline,true); //global changelog cache
+}
+
+/**
+ * Add's an entry to the media changelog
+ *
+ * @author Michael Hamann 
+ * @author Andreas Gohr 
+ * @author Esther Brunner 
+ * @author Ben Coburn 
+ */
+function addMediaLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='', $extra='', $flags=null){
+    global $conf;
+
+    $id = cleanid($id);
+
+    if(!$date) $date = time(); //use current time if none supplied
+    $remote = clientIP(true);
+    $user   = $_SERVER['REMOTE_USER'];
+
+    $strip = array("\t", "\n");
+    $logline = array(
+            'date'  => $date,
+            'ip'    => $remote,
+            'type'  => str_replace($strip, '', $type),
+            'id'    => $id,
+            'user'  => $user,
+            'sum'   => utf8_substr(str_replace($strip, '', $summary),0,255),
+            'extra' => str_replace($strip, '', $extra)
+            );
+
+    // add changelog lines
+    $logline = implode("\t", $logline)."\n";
+    io_saveFile($conf['media_changelog'],$logline,true); //global media changelog cache
+    io_saveFile(mediaMetaFN($id,'.changes'),$logline,true); //media file's changelog
+}
+
+/**
+ * returns an array of recently changed files using the
+ * changelog
+ *
+ * The following constants can be used to control which changes are
+ * included. Add them together as needed.
+ *
+ * RECENTS_SKIP_DELETED   - don't include deleted pages
+ * RECENTS_SKIP_MINORS    - don't include minor changes
+ * RECENTS_SKIP_SUBSPACES - don't include subspaces
+ * RECENTS_MEDIA_CHANGES  - return media changes instead of page changes
+ * RECENTS_MEDIA_PAGES_MIXED  - return both media changes and page changes
+ *
+ * @param int    $first   number of first entry returned (for paginating
+ * @param int    $num     return $num entries
+ * @param string $ns      restrict to given namespace
+ * @param bool   $flags   see above
+ *
+ * @author Ben Coburn 
+ * @author Kate Arzamastseva 
+ */
+function getRecents($first,$num,$ns='',$flags=0){
+    global $conf;
+    $recent = array();
+    $count  = 0;
+
+    if(!$num)
+        return $recent;
+
+    // read all recent changes. (kept short)
+    if ($flags & RECENTS_MEDIA_CHANGES) {
+        $lines = @file($conf['media_changelog']);
+    } else {
+        $lines = @file($conf['changelog']);
+    }
+    $lines_position = count($lines)-1;
+
+    if ($flags & RECENTS_MEDIA_PAGES_MIXED) {
+        $media_lines = @file($conf['media_changelog']);
+        $media_lines_position = count($media_lines)-1;
+    }
+
+    $seen = array(); // caches seen lines, _handleRecent() skips them
+
+    // handle lines
+    while ($lines_position >= 0 || (($flags & RECENTS_MEDIA_PAGES_MIXED) && $media_lines_position >=0)) {
+        if (empty($rec) && $lines_position >= 0) {
+            $rec = _handleRecent(@$lines[$lines_position], $ns, $flags & ~RECENTS_MEDIA_CHANGES, $seen);
+            if (!$rec) {
+                $lines_position --;
+                continue;
+            }
+        }
+        if (($flags & RECENTS_MEDIA_PAGES_MIXED) && empty($media_rec) && $media_lines_position >= 0) {
+            $media_rec = _handleRecent(@$media_lines[$media_lines_position], $ns, $flags | RECENTS_MEDIA_CHANGES, $seen);
+            if (!$media_rec) {
+            	$media_lines_position --;
+            	continue;
+            }
+        }
+        if (($flags & RECENTS_MEDIA_PAGES_MIXED) && @$media_rec['date'] >= @$rec['date']) {
+            $media_lines_position--;
+            $x = $media_rec;
+            $x['media'] = true;
+            $media_rec = false;
+        } else {
+            $lines_position--;
+            $x = $rec;
+            if ($flags & RECENTS_MEDIA_CHANGES) $x['media'] = true;
+            $rec = false;
+        }
+        if(--$first >= 0) continue; // skip first entries
+        $recent[] = $x;
+        $count++;
+        // break when we have enough entries
+        if($count >= $num){ break; }
+    }
+    return $recent;
+}
+
+/**
+ * returns an array of files changed since a given time using the
+ * changelog
+ *
+ * The following constants can be used to control which changes are
+ * included. Add them together as needed.
+ *
+ * RECENTS_SKIP_DELETED   - don't include deleted pages
+ * RECENTS_SKIP_MINORS    - don't include minor changes
+ * RECENTS_SKIP_SUBSPACES - don't include subspaces
+ * RECENTS_MEDIA_CHANGES  - return media changes instead of page changes
+ *
+ * @param int    $from    date of the oldest entry to return
+ * @param int    $to      date of the newest entry to return (for pagination, optional)
+ * @param string $ns      restrict to given namespace (optional)
+ * @param bool   $flags   see above (optional)
+ *
+ * @author Michael Hamann 
+ * @author Ben Coburn 
+ */
+function getRecentsSince($from,$to=null,$ns='',$flags=0){
+    global $conf;
+    $recent = array();
+
+    if($to && $to < $from)
+        return $recent;
+
+    // read all recent changes. (kept short)
+    if ($flags & RECENTS_MEDIA_CHANGES) {
+        $lines = @file($conf['media_changelog']);
+    } else {
+        $lines = @file($conf['changelog']);
+    }
+
+    // we start searching at the end of the list
+    $lines = array_reverse($lines);
+
+    // handle lines
+    $seen = array(); // caches seen lines, _handleRecent() skips them
+
+    foreach($lines as $line){
+        $rec = _handleRecent($line, $ns, $flags, $seen);
+        if($rec !== false) {
+            if ($rec['date'] >= $from) {
+                if (!$to || $rec['date'] <= $to) {
+                    $recent[] = $rec;
+                }
+            } else {
+                break;
+            }
+        }
+    }
+
+    return array_reverse($recent);
+}
+
+/**
+ * Internal function used by getRecents
+ *
+ * don't call directly
+ *
+ * @see getRecents()
+ * @author Andreas Gohr 
+ * @author Ben Coburn 
+ */
+function _handleRecent($line,$ns,$flags,&$seen){
+    if(empty($line)) return false;   //skip empty lines
+
+    // split the line into parts
+    $recent = parseChangelogLine($line);
+    if ($recent===false) { return false; }
+
+    // skip seen ones
+    if(isset($seen[$recent['id']])) return false;
+
+    // skip minors
+    if($recent['type']===DOKU_CHANGE_TYPE_MINOR_EDIT && ($flags & RECENTS_SKIP_MINORS)) return false;
+
+    // remember in seen to skip additional sights
+    $seen[$recent['id']] = 1;
+
+    // check if it's a hidden page
+    if(isHiddenPage($recent['id'])) return false;
+
+    // filter namespace
+    if (($ns) && (strpos($recent['id'],$ns.':') !== 0)) return false;
+
+    // exclude subnamespaces
+    if (($flags & RECENTS_SKIP_SUBSPACES) && (getNS($recent['id']) != $ns)) return false;
+
+    // check ACL
+    if ($flags & RECENTS_MEDIA_CHANGES) {
+        $recent['perms'] = auth_quickaclcheck(getNS($recent['id']).':*');
+    } else {
+        $recent['perms'] = auth_quickaclcheck($recent['id']);
+    }
+    if ($recent['perms'] < AUTH_READ) return false;
+
+    // check existance
+    $fn = (($flags & RECENTS_MEDIA_CHANGES) ? mediaFN($recent['id']) : wikiFN($recent['id']));
+    if((!@file_exists($fn)) && ($flags & RECENTS_SKIP_DELETED)) return false;
+
+    return $recent;
+}
+
+/**
+ * Get the changelog information for a specific page id
+ * and revision (timestamp). Adjacent changelog lines
+ * are optimistically parsed and cached to speed up
+ * consecutive calls to getRevisionInfo. For large
+ * changelog files, only the chunk containing the
+ * requested changelog line is read.
+ *
+ * @author Ben Coburn 
+ * @author Kate Arzamastseva 
+ */
+function getRevisionInfo($id, $rev, $chunk_size=8192, $media=false) {
+    global $cache_revinfo;
+    $cache =& $cache_revinfo;
+    if (!isset($cache[$id])) { $cache[$id] = array(); }
+    $rev = max($rev, 0);
+
+    // check if it's already in the memory cache
+    if (isset($cache[$id]) && isset($cache[$id][$rev])) {
+        return $cache[$id][$rev];
+    }
+
+    if ($media) {
+        $file = mediaMetaFN($id, '.changes');
+    } else {
+        $file = metaFN($id, '.changes');
+    }
+    if (!@file_exists($file)) { return false; }
+    if (filesize($file)<$chunk_size || $chunk_size==0) {
+        // read whole file
+        $lines = file($file);
+        if ($lines===false) { return false; }
+    } else {
+        // read by chunk
+        $fp = fopen($file, 'rb'); // "file pointer"
+        if ($fp===false) { return false; }
+        $head = 0;
+        fseek($fp, 0, SEEK_END);
+        $tail = ftell($fp);
+        $finger = 0;
+        $finger_rev = 0;
+
+        // find chunk
+        while ($tail-$head>$chunk_size) {
+            $finger = $head+floor(($tail-$head)/2.0);
+            fseek($fp, $finger);
+            fgets($fp); // slip the finger forward to a new line
+            $finger = ftell($fp);
+            $tmp = fgets($fp); // then read at that location
+            $tmp = parseChangelogLine($tmp);
+            $finger_rev = $tmp['date'];
+            if ($finger==$head || $finger==$tail) { break; }
+            if ($finger_rev>$rev) {
+                $tail = $finger;
+            } else {
+                $head = $finger;
+            }
+        }
+
+        if ($tail-$head<1) {
+            // cound not find chunk, assume requested rev is missing
+            fclose($fp);
+            return false;
+        }
+
+        // read chunk
+        $chunk = '';
+        $chunk_size = max($tail-$head, 0); // found chunk size
+        $got = 0;
+        fseek($fp, $head);
+        while ($got<$chunk_size && !feof($fp)) {
+            $tmp = @fread($fp, max($chunk_size-$got, 0));
+            if ($tmp===false) { break; } //error state
+            $got += strlen($tmp);
+            $chunk .= $tmp;
+        }
+        $lines = explode("\n", $chunk);
+        array_pop($lines); // remove trailing newline
+        fclose($fp);
+    }
+
+    // parse and cache changelog lines
+    foreach ($lines as $value) {
+        $tmp = parseChangelogLine($value);
+        if ($tmp!==false) {
+            $cache[$id][$tmp['date']] = $tmp;
+        }
+    }
+    if (!isset($cache[$id][$rev])) { return false; }
+    return $cache[$id][$rev];
+}
+
+/**
+ * Return a list of page revisions numbers
+ * Does not guarantee that the revision exists in the attic,
+ * only that a line with the date exists in the changelog.
+ * By default the current revision is skipped.
+ *
+ * id:    the page of interest
+ * first: skip the first n changelog lines
+ * num:   number of revisions to return
+ *
+ * The current revision is automatically skipped when the page exists.
+ * See $INFO['meta']['last_change'] for the current revision.
+ *
+ * For efficiency, the log lines are parsed and cached for later
+ * calls to getRevisionInfo. Large changelog files are read
+ * backwards in chunks until the requested number of changelog
+ * lines are recieved.
+ *
+ * @author Ben Coburn 
+ * @author Kate Arzamastseva 
+ */
+function getRevisions($id, $first, $num, $chunk_size=8192, $media=false) {
+    global $cache_revinfo;
+    $cache =& $cache_revinfo;
+    if (!isset($cache[$id])) { $cache[$id] = array(); }
+
+    $revs = array();
+    $lines = array();
+    $count  = 0;
+    if ($media) {
+        $file = mediaMetaFN($id, '.changes');
+    } else {
+        $file = metaFN($id, '.changes');
+    }
+    $num = max($num, 0);
+    $chunk_size = max($chunk_size, 0);
+    if ($first<0) {
+        $first = 0;
+    } else if (!$media && @file_exists(wikiFN($id)) || $media && @file_exists(mediaFN($id))) {
+        // skip current revision if the page exists
+        $first = max($first+1, 0);
+    }
+
+    if (!@file_exists($file)) { return $revs; }
+    if (filesize($file)<$chunk_size || $chunk_size==0) {
+        // read whole file
+        $lines = file($file);
+        if ($lines===false) { return $revs; }
+    } else {
+        // read chunks backwards
+        $fp = fopen($file, 'rb'); // "file pointer"
+        if ($fp===false) { return $revs; }
+        fseek($fp, 0, SEEK_END);
+        $tail = ftell($fp);
+
+        // chunk backwards
+        $finger = max($tail-$chunk_size, 0);
+        while ($count<$num+$first) {
+            fseek($fp, $finger);
+            $nl = $finger;
+            if ($finger>0) {
+                fgets($fp); // slip the finger forward to a new line
+                $nl = ftell($fp);
+            }
+
+            // was the chunk big enough? if not, take another bite
+            if($nl > 0 && $tail <= $nl){
+                $finger = max($finger-$chunk_size, 0);
+                continue;
+            }else{
+                $finger = $nl;
+            }
+
+            // read chunk
+            $chunk = '';
+            $read_size = max($tail-$finger, 0); // found chunk size
+            $got = 0;
+            while ($got<$read_size && !feof($fp)) {
+                $tmp = @fread($fp, max($read_size-$got, 0));
+                if ($tmp===false) { break; } //error state
+                $got += strlen($tmp);
+                $chunk .= $tmp;
+            }
+            $tmp = explode("\n", $chunk);
+            array_pop($tmp); // remove trailing newline
+
+            // combine with previous chunk
+            $count += count($tmp);
+            $lines = array_merge($tmp, $lines);
+
+            // next chunk
+            if ($finger==0) { break; } // already read all the lines
+            else {
+                $tail = $finger;
+                $finger = max($tail-$chunk_size, 0);
+            }
+        }
+        fclose($fp);
+    }
+
+    // skip parsing extra lines
+    $num = max(min(count($lines)-$first, $num), 0);
+    if      ($first>0 && $num>0)  { $lines = array_slice($lines, max(count($lines)-$first-$num, 0), $num); }
+    else if ($first>0 && $num==0) { $lines = array_slice($lines, 0, max(count($lines)-$first, 0)); }
+    else if ($first==0 && $num>0) { $lines = array_slice($lines, max(count($lines)-$num, 0)); }
+
+    // handle lines in reverse order
+    for ($i = count($lines)-1; $i >= 0; $i--) {
+        $tmp = parseChangelogLine($lines[$i]);
+        if ($tmp!==false) {
+            $cache[$id][$tmp['date']] = $tmp;
+            $revs[] = $tmp['date'];
+        }
+    }
+
+    return $revs;
+}
+
+
diff --git a/inc/cliopts.php b/inc/cliopts.php
new file mode 100644
index 0000000..588f0bc
--- /dev/null
+++ b/inc/cliopts.php
@@ -0,0 +1,504 @@
+
+ * @modified Harry Fuecks hfuecks  gmail.com
+ * @modified Tanguy Ortolo 
+ * @license  http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version  CVS: $Id$
+ * @link     http://pear.php.net/package/Console_Getopt
+ *
+ */
+
+//------------------------------------------------------------------------------
+/**
+ * Sets up CLI environment based on SAPI and PHP version
+ * Helps resolve some issues between the CGI and CLI SAPIs
+ * as well is inconsistencies between PHP 4.3+ and older versions
+ */
+if (version_compare(phpversion(), '4.3.0', '<') || php_sapi_name() == 'cgi') {
+    // Handle output buffering
+    @ob_end_flush();
+    ob_implicit_flush(true);
+
+    // PHP ini settings
+    set_time_limit(0);
+    ini_set('track_errors', true);
+    ini_set('html_errors', false);
+    ini_set('magic_quotes_runtime', false);
+
+    // Define stream constants
+    define('STDIN', fopen('php://stdin', 'r'));
+    define('STDOUT', fopen('php://stdout', 'w'));
+    define('STDERR', fopen('php://stderr', 'w'));
+
+    // Close the streams on script termination
+    register_shutdown_function(
+        create_function('',
+        'fclose(STDIN); fclose(STDOUT); fclose(STDERR); return true;')
+        );
+}
+
+//------------------------------------------------------------------------------
+/**
+* Error codes
+*/
+define('DOKU_CLI_OPTS_UNKNOWN_OPT',1); //Unrecognized option
+define('DOKU_CLI_OPTS_OPT_ARG_REQUIRED',2); //Option requires argument
+define('DOKU_CLI_OPTS_OPT_ARG_DENIED',3); //Option not allowed argument
+define('DOKU_CLI_OPTS_OPT_ABIGUOUS',4);//Option abiguous
+define('DOKU_CLI_OPTS_ARG_READ',5);//Could not read argv
+
+//------------------------------------------------------------------------------
+/**
+ * Command-line options parsing class.
+ *
+ * @author Andrei Zmievski 
+ *
+ */
+class Doku_Cli_Opts {
+
+    /**
+     * 
+     * @see http://www.sitepoint.com/article/php-command-line-1/3
+     * @param string executing file name - this MUST be passed the __FILE__ constant
+     * @param string short options
+     * @param array (optional) long options
+     * @return Doku_Cli_Opts_Container or Doku_Cli_Opts_Error
+     */
+    function & getOptions($bin_file, $short_options, $long_options = null) {
+        $args = Doku_Cli_Opts::readPHPArgv();
+
+        if ( Doku_Cli_Opts::isError($args) ) {
+            return $args;
+        }
+
+        // Compatibility between "php extensions.php" and "./extensions.php"
+        if ( realpath($_SERVER['argv'][0