diff options
author | Evan Prodromou <evan@controlyourself.ca> | 2009-08-27 07:56:19 -0700 |
---|---|---|
committer | Evan Prodromou <evan@controlyourself.ca> | 2009-08-27 07:56:19 -0700 |
commit | f1fda6c0503e32edfc99b297f4bf2cb07e629d9e (patch) | |
tree | 4190903afb80c20b7e98c95e6ea8d57ced0695d9 | |
parent | 209486d3be877d85b3e241797e6042283d0f66d6 (diff) | |
parent | fd53fba114283935a304ed2e05111da256ae798f (diff) |
Merge branch '0.8.x' into 0.9.x
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | classes/File_redirection.php | 2 | ||||
-rw-r--r-- | extlib/php-gettext/AUTHORS | 3 | ||||
-rw-r--r-- | extlib/php-gettext/COPYING | 340 | ||||
-rw-r--r-- | extlib/php-gettext/ChangeLog | 144 | ||||
-rw-r--r-- | extlib/php-gettext/README | 189 | ||||
-rw-r--r-- | extlib/php-gettext/gettext.inc | 318 | ||||
-rw-r--r-- | extlib/php-gettext/gettext.php | 358 | ||||
-rw-r--r-- | extlib/php-gettext/streams.php | 167 | ||||
-rw-r--r-- | install.php | 3 | ||||
-rw-r--r-- | lib/common.php | 3 | ||||
-rw-r--r-- | lib/designsettings.php | 1 | ||||
-rw-r--r-- | lib/queuehandler.php | 7 | ||||
-rw-r--r-- | lib/util.php | 156 | ||||
-rw-r--r-- | lib/xmppqueuehandler.php | 18 | ||||
-rw-r--r-- | plugins/TemplatePlugin.php | 9 | ||||
-rw-r--r-- | tests/HashTagDetectionTest.php | 47 | ||||
-rw-r--r-- | tests/URLDetectionTest.php | 245 | ||||
-rw-r--r-- | tpl/index.php | 2 |
19 files changed, 1953 insertions, 60 deletions
diff --git a/.gitignore b/.gitignore index 5394f5eac..1cde3a625 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ avatar/* background/* files/* file/* +local/* _darcs/* logs/* config.php diff --git a/classes/File_redirection.php b/classes/File_redirection.php index d6fa0bcb6..363e3b947 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -182,7 +182,7 @@ class File_redirection extends Memcached_DataObject } } - if (('ftp' == $p['scheme']) || ('http' == $p['scheme']) || ('https' == $p['scheme'])) { + if (('ftp' == $p['scheme']) || ('ftps' == $p['scheme']) || ('http' == $p['scheme']) || ('https' == $p['scheme'])) { if (empty($p['host'])) return false; if (empty($p['path'])) { $out_url .= '/'; diff --git a/extlib/php-gettext/AUTHORS b/extlib/php-gettext/AUTHORS new file mode 100644 index 000000000..da6ade7b6 --- /dev/null +++ b/extlib/php-gettext/AUTHORS @@ -0,0 +1,3 @@ +Danilo Segan <danilo@kvota.net> +Nico Kaiser <nico@siriux.net> (contributed most changes between 1.0.2 and 1.0.3, bugfix for 1.0.5) +Steven Armstrong <sa@c-area.ch> (gettext.inc, leading to 1.0.6) diff --git a/extlib/php-gettext/COPYING b/extlib/php-gettext/COPYING new file mode 100644 index 000000000..5b6e7c66c --- /dev/null +++ b/extlib/php-gettext/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + 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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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. + + <signature of Ty Coon>, 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 Library General +Public License instead of this License. diff --git a/extlib/php-gettext/ChangeLog b/extlib/php-gettext/ChangeLog new file mode 100644 index 000000000..5e0949dfd --- /dev/null +++ b/extlib/php-gettext/ChangeLog @@ -0,0 +1,144 @@ +2006-02-07 Danilo Šegan <danilo@gnome.org> + + * examples/pigs_dropin.php: comment-out bind_textdomain_codeset + + * gettext.inc (T_bind_textdomain_codeset): bind_textdomain_codeset + is available only in PHP 4.2.0+ (thanks to Jens A. Tkotz). + + * Makefile: Include gettext.inc in DIST_FILES, VERSION up to + 1.0.7. + +2006-02-03 Danilo Šegan <danilo@gnome.org> + + Added setlocale() emulation as well. + + * examples/pigs_dropin.php: Use T_setlocale() and locale_emulation(). + * examples/pigs_fallback.php: Use T_setlocale() and locale_emulation(). + + * gettext.inc: Added globals $EMULATEGETTEXT and $CURRENTLOCALE. + (locale_emulation): Whether emulation is active. + (_check_locale): Rewrite. + (_setlocale): Added emulated setlocale function. + (T_setlocale): Wrapper around _setlocale. + (_get_reader): Use variables and _setlocale. + +2006-02-02 Danilo Šegan <danilo@gnome.org> + + Fix bug #12192. + + * examples/locale/sr_CS/LC_MESSAGES/messages.po: Correct grammar. + * examples/locale/sr_CS/LC_MESSAGES/messages.mo: Rebuild. + +2006-02-02 Danilo Šegan <danilo@gnome.org> + + Fix bug #15419. + + * streams.php: Support for PHP 5.1.1 fread() which reads most 8kb. + (Fix by Piotr Szotkowski <shot@hot.pl>) + +2006-02-02 Danilo Šegan <danilo@gnome.org> + + Merge Steven Armstrong's changes, supporting standard gettext + interfaces: + + * examples/*: Restructured examples. + * gettext.inc: Added. + * AUTHORS: Added Steven. + * Makefile (VERSION): Up to 1.0.6. + +2006-01-28 Nico Kaiser <nico@siriux.net> + + * gettext.php (select_string): Fix "true" <-> 1 difference of PHP + +2005-07-29 Danilo Šegan <danilo@gnome.org> + + * Makefile (VERSION): Up to 1.0.5. + +2005-07-29 Danilo Šegan <danilo@gnome.org> + + Fixes bug #13850. + + * gettext.php (gettext_reader): check $Reader->error as well. + +2005-07-29 Danilo Šegan <danilo@gnome.org> + + * Makefile (VERSION): Up to 1.0.4. + +2005-07-29 Danilo Šegan <danilo@gnome.org> + + Fixes bug #13771. + + * gettext.php (gettext_reader->get_plural_forms): Plural forms + header extraction regex change. Reported by Edgar Gonzales. + +2005-02-28 Danilo Šegan <dsegan@gmx.net> + + * AUTHORS: Added Nico to the list. + + * Makefile (VERSION): Up to 1.0.3. + + * README: Updated. + +2005-02-28 Danilo Šegan <dsegan@gmx.net> + + * gettext.php: Added pre-loading, code documentation, and many + code clean-ups by Nico Kaiser <nico@siriux.net>. + +2005-02-28 Danilo Šegan <dsegan@gmx.net> + + * streams.php (FileReader.read): Handle read($bytes = 0). + + * examples/pigs.php: Prefix gettext function names with T or T_. + + * examples/update: Use the same keywords T_ and T_ngettext. + + * streams.php: Added CachedFileReader. + +2003-11-11 Danilo Šegan <dsegan@gmx.net> + + * gettext.php: Added hashing to find_string. + +2003-11-01 Danilo Šegan <dsegan@gmx.net> + + * Makefile (DIST_FILES): Replaced LICENSE with COPYING. + (VERSION): Up to 1.0.2. + + * AUTHORS: Minor edits. + + * README: Minor edits. + + * COPYING: Removed LICENSE, added this file. + + * gettext.php: Added copyright notice and disclaimer. + * streams.php: Same. + * examples/pigs.php: Same. + +2003-10-23 Danilo Šegan <dsegan@gmx.net> + + * Makefile: Upped version to 1.0.1. + + * gettext.php (gettext_reader): Remove a call to set_total_plurals. + (set_total_plurals): Removed unused function for some better days. + +2003-10-23 Danilo Šegan <dsegan@gmx.net> + + * Makefile: Added, version 1.0.0. + + * examples/*: Added an example of usage. + + * README: Described all the crap. + +2003-10-22 Danilo Šegan <dsegan@gmx.net> + + * gettext.php: Plural forms implemented too. + + * streams.php: Added FileReader for direct access to files (no + need to keep file in memory). + + * gettext.php: It works, except for plural forms. + + * streams.php: Created abstract class StreamReader. + Added StringReader class. + + * gettext.php: Started writing gettext_reader. + diff --git a/extlib/php-gettext/README b/extlib/php-gettext/README new file mode 100644 index 000000000..c7525e29c --- /dev/null +++ b/extlib/php-gettext/README @@ -0,0 +1,189 @@ +PHP-gettext 1.0 + +Copyright 2003, 2006 -- Danilo "angry with PHP[1]" Segan +Licensed under GPLv2 (or any later version, see COPYING) + +[1] PHP is actually cyrillic, and translates roughly to + "works-doesn't-work" (UTF-8: Ради-Не-Ради) + + +Introduction + + How many times did you look for a good translation tool, and + found out that gettext is best for the job? Many times. + + How many times did you try to use gettext in PHP, but failed + miserably, because either your hosting provider didn't support + it, or the server didn't have adequate locale? Many times. + + Well, this is a solution to your needs. It allows using gettext + tools for managing translations, yet it doesn't require gettext + library at all. It parses generated MO files directly, and thus + might be a bit slower than the (maybe provided) gettext library. + + PHP-gettext is a simple reader for GNU gettext MO files. Those + are binary containers for translations, produced by GNU msgfmt. + +Why? + + I got used to having gettext work even without gettext + library. It's there in my favourite language Python, so I was + surprised that I couldn't find it in PHP. I even Googled for it, + but to no avail. + + So, I said, what the heck, I'm going to write it for this + disguisting language of PHP, because I'm often constrained to it. + +Features + + o Support for simple translations + Just define a simple alias for translate() function (suggested + use of _() or gettext(); see provided example). + + o Support for ngettext calls (plural forms, see a note under bugs) + You may also use plural forms. Translations in MO files need to + provide this, and they must also provide "plural-forms" header. + Please see 'info gettext' for more details. + + o Support for reading straight files, or strings (!!!) + Since I can imagine many different backends for reading in the MO + file data, I used imaginary abstract class StreamReader to do all + the input (check streams.php). For your convenience, I've already + provided two classes for reading files: FileReader and + StringReader (CachedFileReader is a combination of the two: it + loads entire file contents into a string, and then works on that). + See example below for usage. You can for instance use StringReader + when you read in data from a database, or you can create your own + derivative of StreamReader for anything you like. + + +Bugs + + Plural-forms field in MO header (translation for empty string, + i.e. "") is treated according to PHP syntactic rules (it's + eval()ed). Since these should actually follow C syntax, there are + some problems. + + For instance, I'm used to using this: + Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : \ + n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; + but it fails with PHP (it sets $plural=2 instead of 0 for $n==1). + + The fix is usually simple, but I'm lazy to go into the details of + PHP operator precedence, and maybe try to fix it. In here, I had + to put everything after the first ':' in parenthesis: + Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : \ + (n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); + That works, and I'm satisfied. + + Besides this one, there are probably a bunch of other bugs, since + I hate PHP (did I mention it already? no? strange), and don't + know it very well. So, feel free to fix any of those and report + them back to me at <danilo@kvota.net>. + +Usage + + Put files streams.php and gettext.php somewhere you can load them + from, and require 'em in where you want to use them. + + Then, create one 'stream reader' (a class that provides functions + like read(), seekto(), currentpos() and length()) which will + provide data for the 'gettext_reader', with eg. + $streamer = new FileStream('data.mo'); + + Then, use that as a parameter to gettext_reader constructor: + $wohoo = new gettext_reader($streamer); + + If you want to disable pre-loading of entire message catalog in + memory (if, for example, you have a multi-thousand message catalog + which you'll use only occasionally), use "false" for second + parameter to gettext_reader constructor: + $wohoo = new gettext_reader($streamer, false); + + From now on, you have all the benefits of gettext data at your + disposal, so may run: + print $wohoo->translate("This is a test"); + print $wohoo->ngettext("%d bird", "%d birds", $birds); + + You might need to pass parameter "-k" to xgettext to make it + extract all the strings. In above example, try with + xgettext -ktranslate -kngettext:1,2 file.php + what should create messages.po which contains two messages for + translation. + + I suggest creating simple aliases for these functions (see + example/pigs.php for how do I do it, which means it's probably a + bad way). + + +Usage with gettext.inc (standard gettext interfaces emulation) + + Check example in examples/pig_dropin.php, basically you include + gettext.inc and use all the standard gettext interfaces as + documented on: + + http://www.php.net/gettext + + The only catch is that you can check return value of setlocale() + to see if your locale is system supported or not. + + +Example + + See in examples/ subdirectory. There are a couple of files. + pigs.php is an example, serbian.po is a translation to Serbian + language, and serbian.mo is generated with + msgfmt -o serbian.mo serbian.po + There is also simple "update" script that can be used to generate + POT file and to update the translation using msgmerge. + +Interesting TODO: + + o Try to parse "plural-forms" header field, and to follow C syntax + rules. This won't be easy. + +Boring TODO: + + o Learn PHP and fix bugs, slowness and other stuff resulting from + my lack of knowledge (but *maybe*, it's not my knowledge that is + bad, but PHP itself ;-). + + (This is mostly done thanks to Nico Kaiser.) + + o Try to use hash tables in MO files: with pre-loading, would it + be useful at all? + +Never-asked-questions: + + o Why did you mark this as version 1.0 when this is the first code + release? + + Well, it's quite simple. I consider that the first released thing + should be labeled "version 1" (first, right?). Zero is there to + indicate that there's zero improvement and/or change compared to + "version 1". + + I plan to use version numbers 1.0.* for small bugfixes, and to + release 1.1 as "first stable release of version 1". + + This may trick someone that this is actually useful software, but + as with any other free software, I take NO RESPONSIBILITY for + creating such a masterpiece that will smoke crack, trash your + hard disk, and make lasers in your CD device dance to the tune of + Mozart's 40th Symphony (there is one like that, right?). + + o Can I...? + + Yes, you can. This is free software (as in freedom, free speech), + and you might do whatever you wish with it, provided you do not + limit freedom of others (GPL). + + I'm considering licensing this under LGPL, but I *do* want + *every* PHP-gettext user to contribute and respect ideas of free + software, so don't count on it happening anytime soon. + + I'm sorry that I'm taking away your freedom of taking others' + freedom away, but I believe that's neglible as compared to what + freedoms you could take away. ;-) + + Uhm, whatever. diff --git a/extlib/php-gettext/gettext.inc b/extlib/php-gettext/gettext.inc new file mode 100644 index 000000000..eb94b256a --- /dev/null +++ b/extlib/php-gettext/gettext.inc @@ -0,0 +1,318 @@ +<?php +/* + Copyright (c) 2005 Steven Armstrong <sa at c-area dot ch> + + Drop in replacement for native gettext. + + This file is part of PHP-gettext. + + PHP-gettext 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. + + PHP-gettext 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 PHP-gettext; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ +/* +LC_CTYPE 0 +LC_NUMERIC 1 +LC_TIME 2 +LC_COLLATE 3 +LC_MONETARY 4 +LC_MESSAGES 5 +LC_ALL 6 +*/ + +require('streams.php'); +require('gettext.php'); + + +// Variables + +global $text_domains, $default_domain, $LC_CATEGORIES, $EMULATEGETTEXT, $CURRENTLOCALE; +$text_domains = array(); +$default_domain = 'messages'; +$LC_CATEGORIES = array('LC_CTYPE', 'LC_NUMERIC', 'LC_TIME', 'LC_COLLATE', 'LC_MONETARY', 'LC_MESSAGES', 'LC_ALL'); +$EMULATEGETTEXT = 0; +$CURRENTLOCALE = ''; + + +// Utility functions + +/** + * Utility function to get a StreamReader for the given text domain. + */ +function _get_reader($domain=null, $category=5, $enable_cache=true) { + global $text_domains, $default_domain, $LC_CATEGORIES; + if (!isset($domain)) $domain = $default_domain; + if (!isset($text_domains[$domain]->l10n)) { + // get the current locale + $locale = _setlocale(LC_MESSAGES, 0); + $p = isset($text_domains[$domain]->path) ? $text_domains[$domain]->path : './'; + $path = $p . "$locale/". $LC_CATEGORIES[$category] ."/$domain.mo"; + if (file_exists($path)) { + $input = new FileReader($path); + } + else { + $input = null; + } + $text_domains[$domain]->l10n = new gettext_reader($input, $enable_cache); + } + return $text_domains[$domain]->l10n; +} + +/** + * Returns whether we are using our emulated gettext API or PHP built-in one. + */ +function locale_emulation() { + global $EMULATEGETTEXT; + return $EMULATEGETTEXT; +} + +/** + * Checks if the current locale is supported on this system. + */ +function _check_locale() { + global $EMULATEGETTEXT; + return !$EMULATEGETTEXT; +} + +/** + * Get the codeset for the given domain. + */ +function _get_codeset($domain=null) { + global $text_domains, $default_domain, $LC_CATEGORIES; + if (!isset($domain)) $domain = $default_domain; + return (isset($text_domains[$domain]->codeset))? $text_domains[$domain]->codeset : ini_get('mbstring.internal_encoding'); +} + +/** + * Convert the given string to the encoding set by bind_textdomain_codeset. + */ +function _encode($text) { + $source_encoding = mb_detect_encoding($text); + $target_encoding = _get_codeset(); + if ($source_encoding != $target_encoding) { + return mb_convert_encoding($text, $target_encoding, $source_encoding); + } + else { + return $text; + } +} + + + + +// Custom implementation of the standard gettext related functions + +/** + * Sets a requested locale, if needed emulates it. + */ +function _setlocale($category, $locale) { + global $CURRENTLOCALE, $EMULATEGETTEXT; + if ($locale === 0) { // use === to differentiate between string "0" + if ($CURRENTLOCALE != '') + return $CURRENTLOCALE; + else + // obey LANG variable, maybe extend to support all of LC_* vars + // even if we tried to read locale without setting it first + return _setlocale($category, $CURRENTLOCALE); + } else { + $ret = 0; + if (function_exists('setlocale')) // I don't know if this ever happens ;) + $ret = setlocale($category, $locale); + if (($ret and $locale == '') or ($ret == $locale)) { + $EMULATEGETTEXT = 0; + $CURRENTLOCALE = $ret; + } else { + if ($locale == '') // emulate variable support + $CURRENTLOCALE = getenv('LANG'); + else + $CURRENTLOCALE = $locale; + $EMULATEGETTEXT = 1; + } + return $CURRENTLOCALE; + } +} + +/** + * Sets the path for a domain. + */ +function _bindtextdomain($domain, $path) { + global $text_domains; + // ensure $path ends with a slash + if ($path[strlen($path) - 1] != '/') $path .= '/'; + elseif ($path[strlen($path) - 1] != '\\') $path .= '\\'; + $text_domains[$domain]->path = $path; +} + +/** + * Specify the character encoding in which the messages from the DOMAIN message catalog will be returned. + */ +function _bind_textdomain_codeset($domain, $codeset) { + global $text_domains; + $text_domains[$domain]->codeset = $codeset; +} + +/** + * Sets the default domain. + */ +function _textdomain($domain) { + global $default_domain; + $default_domain = $domain; +} + +/** + * Lookup a message in the current domain. + */ +function _gettext($msgid) { + $l10n = _get_reader(); + //return $l10n->translate($msgid); + return _encode($l10n->translate($msgid)); +} +/** + * Alias for gettext. + */ +function __($msgid) { + return _gettext($msgid); +} +/** + * Plural version of gettext. + */ +function _ngettext($single, $plural, $number) { + $l10n = _get_reader(); + //return $l10n->ngettext($single, $plural, $number); + return _encode($l10n->ngettext($single, $plural, $number)); +} + +/** + * Override the current domain. + */ +function _dgettext($domain, $msgid) { + $l10n = _get_reader($domain); + //return $l10n->translate($msgid); + return _encode($l10n->translate($msgid)); +} +/** + * Plural version of dgettext. + */ +function _dngettext($domain, $single, $plural, $number) { + $l10n = _get_reader($domain); + //return $l10n->ngettext($single, $plural, $number); + return _encode($l10n->ngettext($single, $plural, $number)); +} + +/** + * Overrides the domain and category for a single lookup. + */ +function _dcgettext($domain, $msgid, $category) { + $l10n = _get_reader($domain, $category); + //return $l10n->translate($msgid); + return _encode($l10n->translate($msgid)); +} +/** + * Plural version of dcgettext. + */ +function _dcngettext($domain, $single, $plural, $number, $category) { + $l10n = _get_reader($domain, $category); + //return $l10n->ngettext($single, $plural, $number); + return _encode($l10n->ngettext($single, $plural, $number)); +} + + + +// Wrappers to use if the standard gettext functions are available, but the current locale is not supported by the system. +// Use the standard impl if the current locale is supported, use the custom impl otherwise. + +function T_setlocale($category, $locale) { + return _setlocale($category, $locale); +} + +function T_bindtextdomain($domain, $path) { + if (_check_locale()) return bindtextdomain($domain, $path); + else return _bindtextdomain($domain, $path); +} +function T_bind_textdomain_codeset($domain, $codeset) { + // bind_textdomain_codeset is available only in PHP 4.2.0+ + if (_check_locale() and function_exists('bind_textdomain_codeset')) return bind_textdomain_codeset($domain, $codeset); + else return _bind_textdomain_codeset($domain, $codeset); +} +function T_textdomain($domain) { + if (_check_locale()) return textdomain($domain); + else return _textdomain($domain); +} +function T_gettext($msgid) { + if (_check_locale()) return gettext($msgid); + else return _gettext($msgid); +} +function T_($msgid) { + if (_check_locale()) return _($msgid); + return __($msgid); +} +function T_ngettext($single, $plural, $number) { + if (_check_locale()) return ngettext($single, $plural, $number); + else return _ngettext($single, $plural, $number); +} +function T_dgettext($domain, $msgid) { + if (_check_locale()) return dgettext($domain, $msgid); + else return _dgettext($domain, $msgid); +} +function T_dngettext($domain, $single, $plural, $number) { + if (_check_locale()) return dngettext($domain, $single, $plural, $number); + else return _dngettext($domain, $single, $plural, $number); +} +function T_dcgettext($domain, $msgid, $category) { + if (_check_locale()) return dcgettext($domain, $msgid, $category); + else return _dcgettext($domain, $msgid, $category); +} +function T_dcngettext($domain, $single, $plural, $number, $category) { + if (_check_locale()) return dcngettext($domain, $single, $plural, $number, $category); + else return _dcngettext($domain, $single, $plural, $number, $category); +} + + + +// Wrappers used as a drop in replacement for the standard gettext functions + +if (!function_exists('gettext')) { + function bindtextdomain($domain, $path) { + return _bindtextdomain($domain, $path); + } + function bind_textdomain_codeset($domain, $codeset) { + return _bind_textdomain_codeset($domain, $codeset); + } + function textdomain($domain) { + return _textdomain($domain); + } + function gettext($msgid) { + return _gettext($msgid); + } + function _($msgid) { + return __($msgid); + } + function ngettext($single, $plural, $number) { + return _ngettext($single, $plural, $number); + } + function dgettext($domain, $msgid) { + return _dgettext($domain, $msgid); + } + function dngettext($domain, $single, $plural, $number) { + return _dngettext($domain, $single, $plural, $number); + } + function dcgettext($domain, $msgid, $category) { + return _dcgettext($domain, $msgid, $category); + } + function dcngettext($domain, $single, $plural, $number, $category) { + return _dcngettext($domain, $single, $plural, $number, $category); + } +} + +?>
\ No newline at end of file diff --git a/extlib/php-gettext/gettext.php b/extlib/php-gettext/gettext.php new file mode 100644 index 000000000..ad94a987b --- /dev/null +++ b/extlib/php-gettext/gettext.php @@ -0,0 +1,358 @@ +<?php +/* + Copyright (c) 2003 Danilo Segan <danilo@kvota.net>. + Copyright (c) 2005 Nico Kaiser <nico@siriux.net> + + This file is part of PHP-gettext. + + PHP-gettext 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. + + PHP-gettext 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 PHP-gettext; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +/** + * Provides a simple gettext replacement that works independently from + * the system's gettext abilities. + * It can read MO files and use them for translating strings. + * The files are passed to gettext_reader as a Stream (see streams.php) + * + * This version has the ability to cache all strings and translations to + * speed up the string lookup. + * While the cache is enabled by default, it can be switched off with the + * second parameter in the constructor (e.g. whenusing very large MO files + * that you don't want to keep in memory) + */ +class gettext_reader { + //public: + var $error = 0; // public variable that holds error code (0 if no error) + + //private: + var $BYTEORDER = 0; // 0: low endian, 1: big endian + var $STREAM = NULL; + var $short_circuit = false; + var $enable_cache = false; + var $originals = NULL; // offset of original table + var $translations = NULL; // offset of translation table + var $pluralheader = NULL; // cache header field for plural forms + var $total = 0; // total string count + var $table_originals = NULL; // table for original strings (offsets) + var $table_translations = NULL; // table for translated strings (offsets) + var $cache_translations = NULL; // original -> translation mapping + + + /* Methods */ + + + /** + * Reads a 32bit Integer from the Stream + * + * @access private + * @return Integer from the Stream + */ + function readint() { + if ($this->BYTEORDER == 0) { + // low endian + return array_shift(unpack('V', $this->STREAM->read(4))); + } else { + // big endian + return array_shift(unpack('N', $this->STREAM->read(4))); + } + } + + /** + * Reads an array of Integers from the Stream + * + * @param int count How many elements should be read + * @return Array of Integers + */ + function readintarray($count) { + if ($this->BYTEORDER == 0) { + // low endian + return unpack('V'.$count, $this->STREAM->read(4 * $count)); + } else { + // big endian + return unpack('N'.$count, $this->STREAM->read(4 * $count)); + } + } + + /** + * Constructor + * + * @param object Reader the StreamReader object + * @param boolean enable_cache Enable or disable caching of strings (default on) + */ + function gettext_reader($Reader, $enable_cache = true) { + // If there isn't a StreamReader, turn on short circuit mode. + if (! $Reader || isset($Reader->error) ) { + $this->short_circuit = true; + return; + } + + // Caching can be turned off + $this->enable_cache = $enable_cache; + + // $MAGIC1 = (int)0x950412de; //bug in PHP 5 + $MAGIC1 = (int) - 1794895138; + // $MAGIC2 = (int)0xde120495; //bug + $MAGIC2 = (int) - 569244523; + + $this->STREAM = $Reader; + $magic = $this->readint(); + if ($magic == $MAGIC1) { + $this->BYTEORDER = 0; + } elseif ($magic == $MAGIC2) { + $this->BYTEORDER = 1; + } else { + $this->error = 1; // not MO file + return false; + } + + // FIXME: Do we care about revision? We should. + $revision = $this->readint(); + + $this->total = $this->readint(); + $this->originals = $this->readint(); + $this->translations = $this->readint(); + } + + /** + * Loads the translation tables from the MO file into the cache + * If caching is enabled, also loads all strings into a cache + * to speed up translation lookups + * + * @access private + */ + function load_tables() { + if (is_array($this->cache_translations) && + is_array($this->table_originals) && + is_array($this->table_translations)) + return; + + /* get original and translations tables */ + $this->STREAM->seekto($this->originals); + $this->table_originals = $this->readintarray($this->total * 2); + $this->STREAM->seekto($this->translations); + $this->table_translations = $this->readintarray($this->total * 2); + + if ($this->enable_cache) { + $this->cache_translations = array (); + /* read all strings in the cache */ + for ($i = 0; $i < $this->total; $i++) { + $this->STREAM->seekto($this->table_originals[$i * 2 + 2]); + $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]); + $this->STREAM->seekto($this->table_translations[$i * 2 + 2]); + $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]); + $this->cache_translations[$original] = $translation; + } + } + } + + /** + * Returns a string from the "originals" table + * + * @access private + * @param int num Offset number of original string + * @return string Requested string if found, otherwise '' + */ + function get_original_string($num) { + $length = $this->table_originals[$num * 2 + 1]; + $offset = $this->table_originals[$num * 2 + 2]; + if (! $length) + return ''; + $this->STREAM->seekto($offset); + $data = $this->STREAM->read($length); + return (string)$data; + } + + /** + * Returns a string from the "translations" table + * + * @access private + * @param int num Offset number of original string + * @return string Requested string if found, otherwise '' + */ + function get_translation_string($num) { + $length = $this->table_translations[$num * 2 + 1]; + $offset = $this->table_translations[$num * 2 + 2]; + if (! $length) + return ''; + $this->STREAM->seekto($offset); + $data = $this->STREAM->read($length); + return (string)$data; + } + + /** + * Binary search for string + * + * @access private + * @param string string + * @param int start (internally used in recursive function) + * @param int end (internally used in recursive function) + * @return int string number (offset in originals table) + */ + function find_string($string, $start = -1, $end = -1) { + if (($start == -1) or ($end == -1)) { + // find_string is called with only one parameter, set start end end + $start = 0; + $end = $this->total; + } + if (abs($start - $end) <= 1) { + // We're done, now we either found the string, or it doesn't exist + $txt = $this->get_original_string($start); + if ($string == $txt) + return $start; + else + return -1; + } else if ($start > $end) { + // start > end -> turn around and start over + return $this->find_string($string, $end, $start); + } else { + // Divide table in two parts + $half = (int)(($start + $end) / 2); + $cmp = strcmp($string, $this->get_original_string($half)); + if ($cmp == 0) + // string is exactly in the middle => return it + return $half; + else if ($cmp < 0) + // The string is in the upper half + return $this->find_string($string, $start, $half); + else + // The string is in the lower half + return $this->find_string($string, $half, $end); + } + } + + /** + * Translates a string + * + * @access public + * @param string string to be translated + * @return string translated string (or original, if not found) + */ + function translate($string) { + if ($this->short_circuit) + return $string; + $this->load_tables(); + + if ($this->enable_cache) { + // Caching enabled, get translated string from cache + if (array_key_exists($string, $this->cache_translations)) + return $this->cache_translations[$string]; + else + return $string; + } else { + // Caching not enabled, try to find string + $num = $this->find_string($string); + if ($num == -1) + return $string; + else + return $this->get_translation_string($num); + } + } + + /** + * Get possible plural forms from MO header + * + * @access private + * @return string plural form header + */ + function get_plural_forms() { + // lets assume message number 0 is header + // this is true, right? + $this->load_tables(); + + // cache header field for plural forms + if (! is_string($this->pluralheader)) { + if ($this->enable_cache) { + $header = $this->cache_translations[""]; + } else { + $header = $this->get_translation_string(0); + } + if (eregi("plural-forms: ([^\n]*)\n", $header, $regs)) + $expr = $regs[1]; + else + $expr = "nplurals=2; plural=n == 1 ? 0 : 1;"; + $this->pluralheader = $expr; + } + return $this->pluralheader; + } + + /** + * Detects which plural form to take + * + * @access private + * @param n count + * @return int array index of the right plural form + */ + function select_string($n) { + $string = $this->get_plural_forms(); + $string = str_replace('nplurals',"\$total",$string); + $string = str_replace("n",$n,$string); + $string = str_replace('plural',"\$plural",$string); + + $total = 0; + $plural = 0; + + eval("$string"); + if ($plural >= $total) $plural = $total - 1; + return $plural; + } + + /** + * Plural version of gettext + * + * @access public + * @param string single + * @param string plural + * @param string number + * @return translated plural form + */ + function ngettext($single, $plural, $number) { + if ($this->short_circuit) { + if ($number != 1) + return $plural; + else + return $single; + } + + // find out the appropriate form + $select = $this->select_string($number); + + // this should contains all strings separated by NULLs + $key = $single.chr(0).$plural; + + + if ($this->enable_cache) { + if (! array_key_exists($key, $this->cache_translations)) { + return ($number != 1) ? $plural : $single; + } else { + $result = $this->cache_translations[$key]; + $list = explode(chr(0), $result); + return $list[$select]; + } + } else { + $num = $this->find_string($key); + if ($num == -1) { + return ($number != 1) ? $plural : $single; + } else { + $result = $this->get_translation_string($num); + $list = explode(chr(0), $result); + return $list[$select]; + } + } + } + +} + +?> diff --git a/extlib/php-gettext/streams.php b/extlib/php-gettext/streams.php new file mode 100644 index 000000000..3eafa7482 --- /dev/null +++ b/extlib/php-gettext/streams.php @@ -0,0 +1,167 @@ +<?php +/* + Copyright (c) 2003, 2005 Danilo Segan <danilo@kvota.net>. + + This file is part of PHP-gettext. + + PHP-gettext 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. + + PHP-gettext 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 PHP-gettext; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + + +// Simple class to wrap file streams, string streams, etc. +// seek is essential, and it should be byte stream +class StreamReader { + // should return a string [FIXME: perhaps return array of bytes?] + function read($bytes) { + return false; + } + + // should return new position + function seekto($position) { + return false; + } + + // returns current position + function currentpos() { + return false; + } + + // returns length of entire stream (limit for seekto()s) + function length() { + return false; + } +} + +class StringReader { + var $_pos; + var $_str; + + function StringReader($str='') { + $this->_str = $str; + $this->_pos = 0; + } + + function read($bytes) { + $data = substr($this->_str, $this->_pos, $bytes); + $this->_pos += $bytes; + if (strlen($this->_str)<$this->_pos) + $this->_pos = strlen($this->_str); + + return $data; + } + + function seekto($pos) { + $this->_pos = $pos; + if (strlen($this->_str)<$this->_pos) + $this->_pos = strlen($this->_str); + return $this->_pos; + } + + function currentpos() { + return $this->_pos; + } + + function length() { + return strlen($this->_str); + } + +} + + +class FileReader { + var $_pos; + var $_fd; + var $_length; + + function FileReader($filename) { + if (file_exists($filename)) { + + $this->_length=filesize($filename); + $this->_pos = 0; + $this->_fd = fopen($filename,'rb'); + if (!$this->_fd) { + $this->error = 3; // Cannot read file, probably permissions + return false; + } + } else { + $this->error = 2; // File doesn't exist + return false; + } + } + + function read($bytes) { + if ($bytes) { + fseek($this->_fd, $this->_pos); + + // PHP 5.1.1 does not read more than 8192 bytes in one fread() + // the discussions at PHP Bugs suggest it's the intended behaviour + $data = ''; + while ($bytes > 0) { + $chunk = fread($this->_fd, $bytes); + $data .= $chunk; + $bytes -= strlen($chunk); + } + $this->_pos = ftell($this->_fd); + + return $data; + } else return ''; + } + + function seekto($pos) { + fseek($this->_fd, $pos); + $this->_pos = ftell($this->_fd); + return $this->_pos; + } + + function currentpos() { + return $this->_pos; + } + + function length() { + return $this->_length; + } + + function close() { + fclose($this->_fd); + } + +} + +// Preloads entire file in memory first, then creates a StringReader +// over it (it assumes knowledge of StringReader internals) +class CachedFileReader extends StringReader { + function CachedFileReader($filename) { + if (file_exists($filename)) { + + $length=filesize($filename); + $fd = fopen($filename,'rb'); + + if (!$fd) { + $this->error = 3; // Cannot read file, probably permissions + return false; + } + $this->_str = fread($fd, $length); + fclose($fd); + + } else { + $this->error = 2; // File doesn't exist + return false; + } + } +} + + +?>
\ No newline at end of file diff --git a/install.php b/install.php index 1826a8494..a03dfa775 100644 --- a/install.php +++ b/install.php @@ -49,8 +49,7 @@ function checkPrereqs() } $reqs = array('gd', 'curl', - 'xmlwriter', 'mbstring', - 'gettext'); + 'xmlwriter', 'mbstring'); foreach ($reqs as $req) { if (!checkExtension($req)) { diff --git a/lib/common.php b/lib/common.php index 62cf5640d..31b81da59 100644 --- a/lib/common.php +++ b/lib/common.php @@ -49,6 +49,9 @@ require_once('PEAR.php'); require_once('DB/DataObject.php'); require_once('DB/DataObject/Cast.php'); # for dates +if (!function_exists('gettext')) { + require_once("php-gettext/gettext.inc"); +} require_once(INSTALLDIR.'/lib/language.php'); // This gets included before the config file, so that admin code and plugins diff --git a/lib/designsettings.php b/lib/designsettings.php index a48ec9d22..b86265971 100644 --- a/lib/designsettings.php +++ b/lib/designsettings.php @@ -326,6 +326,7 @@ class DesignSettingsAction extends AccountSettingsAction $this->script('js/farbtastic/farbtastic.js'); $this->script('js/farbtastic/farbtastic.go.js'); + $this->script('js/userdesign.go.js'); } /** diff --git a/lib/queuehandler.php b/lib/queuehandler.php index f11e5bd90..5c5f31f0e 100644 --- a/lib/queuehandler.php +++ b/lib/queuehandler.php @@ -75,6 +75,7 @@ class QueueHandler extends Daemon function run() { if (!$this->start()) { + $this->log(LOG_WARNING, 'failed to start'); return false; } @@ -87,9 +88,15 @@ class QueueHandler extends Daemon $qm->service($queue, $this); + $this->log(LOG_INFO, 'finished servicing the queue'); + if (!$this->finish()) { + $this->log(LOG_WARNING, 'failed to clean up'); return false; } + + $this->log(LOG_INFO, 'terminating normally'); + return true; } diff --git a/lib/util.php b/lib/util.php index b798cfe15..3a35e15d2 100644 --- a/lib/util.php +++ b/lib/util.php @@ -404,7 +404,7 @@ function common_render_text($text) $r = preg_replace('/[\x{0}-\x{8}\x{b}-\x{c}\x{e}-\x{19}]/', '', $r); $r = common_replace_urls_callback($r, 'common_linkify'); - $r = preg_replace('/(^|\(|\[|\s+)#([\pL\pN_\-\.]{1,64})/e', "'\\1#'.common_tag_link('\\2')", $r); + $r = preg_replace('/(^|\"\;|\'|\(|\[|\{|\s+)#([\pL\pN_\-\.]{1,64})/e', "'\\1#'.common_tag_link('\\2')", $r); // XXX: machine tags return $r; } @@ -412,57 +412,112 @@ function common_render_text($text) function common_replace_urls_callback($text, $callback, $notice_id = null) { // Start off with a regex $regex = '#'. - '(?:^|\s+)('. + '(?:^|[\s\(\)\[\]\{\}\\\'\\\";]+)(?![\@\!\#])'. + '(?P<url>'. '(?:'. - '(?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|irc)://'. - '|'. - '(?:mailto|aim|tel|xmpp):'. - ')?'. - '(?:'. - '(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'. //IPv4 - '|(?:'. - '(?:[0-9a-f]{1,4}:){1,1}(?::[0-9a-f]{1,4}){1,6}|'. //IPv6 - '(?:[0-9a-f]{1,4}:){1,2}(?::[0-9a-f]{1,4}){1,5}|'. - '(?:[0-9a-f]{1,4}:){1,3}(?::[0-9a-f]{1,4}){1,4}|'. - '(?:[0-9a-f]{1,4}:){1,4}(?::[0-9a-f]{1,4}){1,3}|'. - '(?:[0-9a-f]{1,4}:){1,5}(?::[0-9a-f]{1,4}){1,2}|'. - '(?:[0-9a-f]{1,4}:){1,6}(?::[0-9a-f]{1,4}){1,1}|'. - '(?:(?:[0-9a-f]{1,4}:){1,7}|:):|'. - ':(?::[0-9a-f]{1,4}){1,7}|'. - '(?:(?:(?:[0-9a-f]{1,4}:){6})(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3})|'. - '(?:(?:[0-9a-f]{1,4}:){5}[0-9a-f]{1,4}:(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3})|'. - '(?:[0-9a-f]{1,4}:){5}:[0-9a-f]{1,4}:(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}|'. - '(?:[0-9a-f]{1,4}:){1,1}(?::[0-9a-f]{1,4}){1,4}:(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}|'. - '(?:[0-9a-f]{1,4}:){1,2}(?::[0-9a-f]{1,4}){1,3}:(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}|'. - '(?:[0-9a-f]{1,4}:){1,3}(?::[0-9a-f]{1,4}){1,2}:(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}|'. - '(?:[0-9a-f]{1,4}:){1,4}(?::[0-9a-f]{1,4}){1,1}:(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}|'. - '(?:(?:[0-9a-f]{1,4}:){1,5}|:):(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}|'. - ':(?::[0-9a-f]{1,4}){1,5}:(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}'. - ')|'. - '(?:[^.\s/:]+\.)+'. //DNS - '(?:museum|travel|onion|[a-z]{2,4})'. + '(?:'. //Known protocols + '(?:'. + '(?:(?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|irc)://)'. + '|'. + '(?:(?:mailto|aim|tel|xmpp):)'. + ')'. + '(?:[\pN\pL\-\_\+]+(?::[\pN\pL\-\_\+]+)?\@)?'. //user:pass@ + '(?:'. + '(?:'. + '\[[\pN\pL\-\_\:\.]+(?<![\.\:])\]'. //[dns] + ')|(?:'. + '[\pN\pL\-\_\:\.]+(?<![\.\:])'. //dns + ')'. + ')'. + ')'. + '|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'. //IPv4 + '|(?:'. //IPv6 + '\[?(?:(?:(?:[0-9A-Fa-f]{1,4}:){7}(?:(?:[0-9A-Fa-f]{1,4})|:))|(?:(?:[0-9A-Fa-f]{1,4}:){6}(?::|(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})|(?::[0-9A-Fa-f]{1,4})))|(?:(?:[0-9A-Fa-f]{1,4}:){5}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){4}(?::[0-9A-Fa-f]{1,4}){0,1}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){3}(?::[0-9A-Fa-f]{1,4}){0,2}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){2}(?::[0-9A-Fa-f]{1,4}){0,3}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:)(?::[0-9A-Fa-f]{1,4}){0,4}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?::(?::[0-9A-Fa-f]{1,4}){0,5}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})))\]?'. + ')|(?:'. //DNS + '(?:[\pN\pL\-\_\+]+(?:\:[\pN\pL\-\_\+]+)?\@)?'. //user:pass@ + '[\pN\pL\-\_]+(?:\.[\pN\pL\-\_]+)*\.'. + //tld list from http://data.iana.org/TLD/tlds-alpha-by-domain.txt, also added local, loc, and onion + '(?:AC|AD|AE|AERO|AF|AG|AI|AL|AM|AN|AO|AQ|AR|ARPA|AS|ASIA|AT|AU|AW|AX|AZ|BA|BB|BD|BE|BF|BG|BH|BI|BIZ|BJ|BM|BN|BO|BR|BS|BT|BV|BW|BY|BZ|CA|CAT|CC|CD|CF|CG|CH|CI|CK|CL|CM|CN|CO|COM|COOP|CR|CU|CV|CX|CY|CZ|DE|DJ|DK|DM|DO|DZ|EC|EDU|EE|EG|ER|ES|ET|EU|FI|FJ|FK|FM|FO|FR|GA|GB|GD|GE|GF|GG|GH|GI|GL|GM|GN|GOV|GP|GQ|GR|GS|GT|GU|GW|GY|HK|HM|HN|HR|HT|HU|ID|IE|IL|IM|IN|INFO|INT|IO|IQ|IR|IS|IT|JE|JM|JO|JOBS|JP|KE|KG|KH|KI|KM|KN|KP|KR|KW|KY|KZ|LA|LB|LC|LI|LK|LR|LS|LT|LU|LV|LY|MA|MC|MD|ME|MG|MH|MIL|MK|ML|MM|MN|MO|MOBI|MP|MQ|MR|MS|MT|MU|MUSEUM|MV|MW|MX|MY|MZ|NA|NAME|NC|NE|NET|NF|NG|NI|NL|NO|NP|NR|NU|NZ|OM|ORG|PA|PE|PF|PG|PH|PK|PL|PM|PN|PR|PRO|PS|PT|PW|PY|QA|RE|RO|RS|RU|RW|SA|SB|SC|SD|SE|SG|SH|SI|SJ|SK|SL|SM|SN|SO|SR|ST|SU|SV|SY|SZ|TC|TD|TEL|TF|TG|TH|TJ|TK|TL|TM|TN|TO|TP|TR|TRAVEL|TT|TV|TW|TZ|UA|UG|UK|US|UY|UZ|VA|VC|VE|VG|VI|VN|VU|WF|WS|XN--0ZWM56D|测试|XN--11B5BS3A9AJ6G|परीक्षा|XN--80AKHBYKNJ4F|испытание|XN--9T4B11YI5A|테스트|XN--DEBA0AD|טעסט|XN--G6W251D|測試|XN--HGBK6AJ7F53BBA|آزمایشی|XN--HLCJ6AYA9ESC7A|பரிட்சை|XN--JXALPDLP|δοκιμή|XN--KGBECHTV|إختبار|XN--ZCKZAH|テスト|YE|YT|YU|ZA|ZM|ZW|local|loc|onion)'. + ')(?![\pN\pL\-\_])'. ')'. - '(?:[:/][^\s]*)?'. + '(?:'. + '(?:\:\d+)?'. //:port + '(?:/[\pN\pL$\[\]\,\!\(\)\.\-\_\+\/\=\&\;]*)?'. // /path + '(?:\?[\pN\pL\$\[\]\,\!\(\)\.\-\_\+\/\=\&\;\/]*)?'. // ?query string + '(?:\#[\pN\pL$\[\]\,\!\(\)\.\-\_\+\/\=\&\;\/\?\#]*)?'. // #fragment + ')(?<![\?\.\,\#\,])'. ')'. - '#ix'; - preg_match_all($regex, $text, $matches); - // Then clean up what the regex left behind - $offset = 0; - foreach($matches[1] as $url) { - // Call user specified func - if (empty($notice_id)) { - $modified_url = call_user_func($callback, $url); - } else { - $modified_url = call_user_func($callback, array($url, $notice_id)); + '#ixu'; + preg_match_all($regex,$text,$matches); + //print_r($matches); + return preg_replace_callback($regex, curry(callback_helper,$callback,$notice_id) ,$text); +} + +function callback_helper($matches, $callback, $notice_id) { + $url=$matches['url']; + $left = strpos($matches[0],$url); + $right = $left+strlen($url); + + $groupSymbolSets=array( + array( + 'left'=>'(', + 'right'=>')' + ), + array( + 'left'=>'[', + 'right'=>']' + ), + array( + 'left'=>'{', + 'right'=>'}' + ) + ); + $cannotEndWith=array('.','?',',','#'); + $original_url=$url; + do{ + $original_url=$url; + foreach($groupSymbolSets as $groupSymbolSet){ + if(substr($url,-1)==$groupSymbolSet['right']){ + $group_left_count = substr_count($url,$groupSymbolSet['left']); + $group_right_count = substr_count($url,$groupSymbolSet['right']); + if($group_left_count<$group_right_count){ + $right-=1; + $url=substr($url,0,-1); + } + } } - - // Replace it! - $start = mb_strpos($text, $url, $offset); - $text = mb_substr($text, 0, $start).$modified_url.mb_substr($text, $start + mb_strlen($url), mb_strlen($text)); - $offset = $start + mb_strlen($modified_url); - } - - return $text; + if(in_array(substr($url,-1),$cannotEndWith)){ + $right-=1; + $url=substr($url,0,-1); + } + }while($original_url!=$url); + + + + if(empty($notice_id)){ + $result = call_user_func_array($callback,$url); + }else{ + $result = call_user_func_array($callback, array($url,$notice_id) ); + } + return substr($matches[0],0,$left) . $result . substr($matches[0],$right); +} + +function curry($fn) { + //TODO switch to a PHP 5.3 function closure based approach if PHP 5.3 is used + $args = func_get_args(); + array_shift($args); + $id = uniqid('_partial'); + $GLOBALS[$id] = array($fn, $args); + return create_function( + '', + ' + $args = func_get_args(); + return call_user_func_array( + $GLOBALS["'.$id.'"][0], + array_merge( + $args, + $GLOBALS["'.$id.'"][1])); + '); } function common_linkify($url) { @@ -470,6 +525,11 @@ function common_linkify($url) { // functions $url = htmlspecialchars_decode($url); + if(strpos($url, '@')!==false && strpos($url, ':')===false){ + //url is an email address without the mailto: protocol + return XMLStringer::estring('a', array('href' => "mailto:$url", 'rel' => 'external'), $url); + } + $canon = File_redirection::_canonUrl($url); $longurl_data = File_redirection::where($url); diff --git a/lib/xmppqueuehandler.php b/lib/xmppqueuehandler.php index 77d476c30..0ab837eb6 100644 --- a/lib/xmppqueuehandler.php +++ b/lib/xmppqueuehandler.php @@ -38,14 +38,20 @@ class XmppQueueHandler extends QueueHandler function start() { # Low priority; we don't want to receive messages + $this->log(LOG_INFO, "INITIALIZE"); $this->conn = jabber_connect($this->_id.$this->transport()); - if ($this->conn) { - $this->conn->addEventHandler('message', 'forward_message', $this); - $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this); - $this->conn->setReconnectTimeout(600); - jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', -1); + + if (empty($this->conn)) { + $this->log(LOG_ERR, "Couldn't connect to server."); + return false; } + + $this->conn->addEventHandler('message', 'forward_message', $this); + $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this); + $this->conn->setReconnectTimeout(600); + jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', -1); + return !is_null($this->conn); } @@ -56,6 +62,8 @@ class XmppQueueHandler extends QueueHandler function handle_reconnect(&$pl) { + $this->log(LOG_NOTICE, 'reconnected'); + $this->conn->processUntil('session_start'); $this->conn->presence(null, 'available', null, 'available', -1); } diff --git a/plugins/TemplatePlugin.php b/plugins/TemplatePlugin.php index 03daf6219..6c14f1988 100644 --- a/plugins/TemplatePlugin.php +++ b/plugins/TemplatePlugin.php @@ -198,13 +198,13 @@ class TemplatePlugin extends Plugin { // unless laconica config: // $config['template']['mode'] = 'html'; if (!(common_config('template', 'mode') == 'html')) { - $tpl_file = 'tpl/index.php'; + $tpl_file = $this->templateFolder() . '/index.php'; $tags = array_merge($vars,$this->blocks); include $tpl_file; return; } - $tpl_file = 'tpl/index.html'; + $tpl_file = $this->templateFolder() . '/index.html'; // read the static template $output = file_get_contents( $tpl_file ); @@ -236,6 +236,9 @@ class TemplatePlugin extends Plugin { return true; } + function templateFolder() { + return 'tpl'; + } // catching the StartShowHTML event to halt the rendering function onStartShowHTML( &$act ) { @@ -300,7 +303,7 @@ class TemplateAction extends Action $this->clientError(_('only User #1 can update the template'), $code = 401); // open the old template - $tpl_file = 'tpl/index.html'; + $tpl_file = $this->templateFolder() . '/index.html'; $fp = fopen( $tpl_file, 'w+' ); // overwrite with the new template diff --git a/tests/HashTagDetectionTest.php b/tests/HashTagDetectionTest.php new file mode 100644 index 000000000..901e0611c --- /dev/null +++ b/tests/HashTagDetectionTest.php @@ -0,0 +1,47 @@ +<?php + +if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { + print "This script must be run from the command line\n"; + exit(); +} + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); +define('LACONICA', true); + +require_once INSTALLDIR . '/lib/common.php'; + +class HashTagDetectionTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider provider + * + */ + public function testProduction($content, $expected) + { + $rendered = common_render_text($content); + $this->assertEquals($expected, $rendered); + } + + static public function provider() + { + return array( + array('hello', + 'hello'), + array('#hello people', + '#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('hello'))) . '" rel="tag">hello</a></span> people'), + array('"#hello" people', + '"#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('hello'))) . '" rel="tag">hello</a></span>" people'), + array('say "#hello" people', + 'say "#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('hello'))) . '" rel="tag">hello</a></span>" people'), + array('say (#hello) people', + 'say (#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('hello'))) . '" rel="tag">hello</a></span>) people'), + array('say [#hello] people', + 'say [#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('hello'))) . '" rel="tag">hello</a></span>] people'), + array('say {#hello} people', + 'say {#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('hello'))) . '" rel="tag">hello</a></span>} people'), + array('say \'#hello\' people', + 'say \'#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('hello'))) . '" rel="tag">hello</a></span>\' people'), + ); + } +} + diff --git a/tests/URLDetectionTest.php b/tests/URLDetectionTest.php new file mode 100644 index 000000000..7b2e8c5ae --- /dev/null +++ b/tests/URLDetectionTest.php @@ -0,0 +1,245 @@ +<?php + +if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { + print "This script must be run from the command line\n"; + exit(); +} + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); +define('LACONICA', true); + +require_once INSTALLDIR . '/lib/common.php'; + +class URLDetectionTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider provider + * + */ + public function testProduction($content, $expected) + { + $rendered = common_render_text($content); + $this->assertEquals($expected, $rendered); + } + + static public function provider() + { + return array( + array('http://127.0.0.1', + '<a href="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'), + array('127.0.0.1', + '<a href="http://127.0.0.1/" rel="external">127.0.0.1</a>'), + array('127.0.0.1:99', + '<a href="http://127.0.0.1:99/" rel="external">127.0.0.1:99</a>'), + array('127.0.0.1/test.php', + '<a href="http://127.0.0.1/test.php" rel="external">127.0.0.1/test.php</a>'), + array('http://[::1]:99/test.php', + '<a href="http://[::1]:99/test.php" rel="external">http://[::1]:99/test.php</a>'), + array('http://::1/test.php', + '<a href="http://::1/test.php" rel="external">http://::1/test.php</a>'), + array('http://::1', + '<a href="http://::1/" rel="external">http://::1</a>'), + array('2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php', + '<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php" rel="external">2001:4978:1b5:0:21d:e0ff:fe66:59ab/test.php</a>'), + array('[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php', + '<a href="http://[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php" rel="external">[2001:4978:1b5:0:21d:e0ff:fe66:59ab]:99/test.php</a>'), + array('2001:4978:1b5:0:21d:e0ff:fe66:59ab', + '<a href="http://2001:4978:1b5:0:21d:e0ff:fe66:59ab/" rel="external">2001:4978:1b5:0:21d:e0ff:fe66:59ab</a>'), + array('http://127.0.0.1', + '<a href="http://127.0.0.1/" rel="external">http://127.0.0.1</a>'), + array('example.com', + '<a href="http://example.com/" rel="external">example.com</a>'), + array('example.com', + '<a href="http://example.com/" rel="external">example.com</a>'), + array('http://example.com', + '<a href="http://example.com/" rel="external">http://example.com</a>'), + array('http://example.com.', + '<a href="http://example.com/" rel="external">http://example.com</a>.'), + array('/var/lib/example.so', + '/var/lib/example.so'), + array('example', + 'example'), + array('user@example.com', + '<a href="mailto:user@example.com" rel="external">user@example.com</a>'), + array('user_name+other@example.com', + '<a href="mailto:user_name+other@example.com" rel="external">user_name+other@example.com</a>'), + array('mailto:user@example.com', + '<a href="mailto:user@example.com" rel="external">mailto:user@example.com</a>'), + array('mailto:user@example.com?subject=test', + '<a href="mailto:user@example.com?subject=test" rel="external">mailto:user@example.com?subject=test</a>'), + array('#example', + '#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('example'))) . '" rel="tag">example</a></span>'), + array('#example.com', + '#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('example.com'))) . '" rel="tag">example.com</a></span>'), + array('#.net', + '#<span class="tag"><a href="' . common_local_url('tag', array('tag' => common_canonical_tag('.net'))) . '" rel="tag">.net</a></span>'), + array('http://example', + '<a href="http://example/" rel="external">http://example</a>'), + array('http://3xampl3', + '<a href="http://3xampl3/" rel="external">http://3xampl3</a>'), + array('http://example/', + '<a href="http://example/" rel="external">http://example/</a>'), + array('http://example/path', + '<a href="http://example/path" rel="external">http://example/path</a>'), + array('http://example.com', + '<a href="http://example.com/" rel="external">http://example.com</a>'), + array('https://example.com', + '<a href="https://example.com/" rel="external">https://example.com</a>'), + array('ftp://example.com', + '<a href="ftp://example.com/" rel="external">ftp://example.com</a>'), + array('ftps://example.com', + '<a href="ftps://example.com/" rel="external">ftps://example.com</a>'), + array('http://user@example.com', + '<a href="http://user@example.com/" rel="external">http://user@example.com</a>'), + array('http://user:pass@example.com', + '<a href="http://user:pass@example.com/" rel="external">http://user:pass@example.com</a>'), + array('http://example.com:8080', + '<a href="http://example.com:8080/" rel="external">http://example.com:8080</a>'), + array('http://example.com:8080/test.php', + '<a href="http://example.com:8080/test.php" rel="external">http://example.com:8080/test.php</a>'), + array('example.com:8080/test.php', + '<a href="http://example.com:8080/test.php" rel="external">example.com:8080/test.php</a>'), + array('http://www.example.com', + '<a href="http://www.example.com/" rel="external">http://www.example.com</a>'), + array('http://example.com/', + '<a href="http://example.com/" rel="external">http://example.com/</a>'), + array('http://example.com/path', + '<a href="http://example.com/path" rel="external">http://example.com/path</a>'), + array('http://example.com/path.html', + '<a href="http://example.com/path.html" rel="external">http://example.com/path.html</a>'), + array('http://example.com/path.html#fragment', + '<a href="http://example.com/path.html#fragment" rel="external">http://example.com/path.html#fragment</a>'), + array('http://example.com/path.php?foo=bar&bar=foo', + '<a href="http://example.com/path.php?foo=bar&bar=foo" rel="external">http://example.com/path.php?foo=bar&bar=foo</a>'), + array('http://example.com.', + '<a href="http://example.com/" rel="external">http://example.com</a>.'), + array('http://müllärör.de', + '<a href="http://müllärör.de/" rel="external">http://müllärör.de</a>'), + array('http://ﺱﺲﺷ.com', + '<a href="http://ﺱﺲﺷ.com/" rel="external">http://ﺱﺲﺷ.com</a>'), + array('http://сделаткартинки.com', + '<a href="http://сделаткартинки.com/" rel="external">http://сделаткартинки.com</a>'), + array('http://tūdaliņ.lv', + '<a href="http://tūdaliņ.lv/" rel="external">http://tūdaliņ.lv</a>'), + array('http://brændendekærlighed.com', + '<a href="http://brændendekærlighed.com/" rel="external">http://brændendekærlighed.com</a>'), + array('http://あーるいん.com', + '<a href="http://あーるいん.com/" rel="external">http://あーるいん.com</a>'), + array('http://예비교사.com', + '<a href="http://예비교사.com/" rel="external">http://예비교사.com</a>'), + array('http://example.com.', + '<a href="http://example.com/" rel="external">http://example.com</a>.'), + array('http://example.com?', + '<a href="http://example.com/" rel="external">http://example.com</a>?'), + array('http://example.com!', + '<a href="http://example.com/" rel="external">http://example.com</a>!'), + array('http://example.com,', + '<a href="http://example.com/" rel="external">http://example.com</a>,'), + array('http://example.com;', + '<a href="http://example.com/" rel="external">http://example.com</a>;'), + array('http://example.com:', + '<a href="http://example.com/" rel="external">http://example.com</a>:'), + array('\'http://example.com\'', + '\'<a href="http://example.com/" rel="external">http://example.com</a>\''), + array('"http://example.com"', + '"<a href="http://example.com/" rel="external">http://example.com</a>"'), + array('http://example.com', + '<a href="http://example.com/" rel="external">http://example.com</a>'), + array('(http://example.com)', + '(<a href="http://example.com/" rel="external">http://example.com</a>)'), + array('[http://example.com]', + '[<a href="http://example.com/" rel="external">http://example.com</a>]'), + array('<http://example.com>', + '<<a href="http://example.com/" rel="external">http://example.com</a>>'), + array('http://example.com/path/(foo)/bar', + '<a href="http://example.com/path/(foo)/bar" rel="external">http://example.com/path/(foo)/bar</a>'), + array('http://example.com/path/[foo]/bar', + '<a href="http://example.com/path/[foo]/bar" rel="external">http://example.com/path/[foo]/bar</a>'), + array('http://example.com/path/foo/(bar)', + '<a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>'), + //Not a valid url - urls cannot contain unencoded square brackets + array('http://example.com/path/foo/[bar]', + '<a href="http://example.com/path/foo/[bar]" rel="external">http://example.com/path/foo/[bar]</a>'), + array('Hey, check out my cool site http://example.com okay?', + 'Hey, check out my cool site <a href="http://example.com/" rel="external">http://example.com</a> okay?'), + array('What about parens (e.g. http://example.com/path/foo/(bar))?', + 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>)?'), + array('What about parens (e.g. http://example.com/path/foo/(bar)?', + 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>?'), + array('What about parens (e.g. http://example.com/path/foo/(bar).)?', + 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>.)?'), + //Not a valid url - urls cannot contain unencoded commas + array('What about parens (e.g. http://example.com/path/(foo,bar)?', + 'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" rel="external">http://example.com/path/(foo,bar)</a>?'), + array('Unbalanced too (e.g. http://example.com/path/((((foo)/bar)?', + 'Unbalanced too (e.g. <a href="http://example.com/path/((((foo)/bar)" rel="external">http://example.com/path/((((foo)/bar)</a>?'), + array('Unbalanced too (e.g. http://example.com/path/(foo))))/bar)?', + 'Unbalanced too (e.g. <a href="http://example.com/path/(foo))))/bar" rel="external">http://example.com/path/(foo))))/bar</a>)?'), + array('Unbalanced too (e.g. http://example.com/path/foo/((((bar)?', + 'Unbalanced too (e.g. <a href="http://example.com/path/foo/((((bar)" rel="external">http://example.com/path/foo/((((bar)</a>?'), + array('Unbalanced too (e.g. http://example.com/path/foo/(bar))))?', + 'Unbalanced too (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">http://example.com/path/foo/(bar)</a>)))?'), + array('example.com', + '<a href="http://example.com/" rel="external">example.com</a>'), + array('example.org', + '<a href="http://example.org/" rel="external">example.org</a>'), + array('example.co.uk', + '<a href="http://example.co.uk/" rel="external">example.co.uk</a>'), + array('www.example.co.uk', + '<a href="http://www.example.co.uk/" rel="external">www.example.co.uk</a>'), + array('farm1.images.example.co.uk', + '<a href="http://farm1.images.example.co.uk/" rel="external">farm1.images.example.co.uk</a>'), + array('example.museum', + '<a href="http://example.museum/" rel="external">example.museum</a>'), + array('example.travel', + '<a href="http://example.travel/" rel="external">example.travel</a>'), + array('example.com.', + '<a href="http://example.com/" rel="external">example.com</a>.'), + array('example.com?', + '<a href="http://example.com/" rel="external">example.com</a>?'), + array('example.com!', + '<a href="http://example.com/" rel="external">example.com</a>!'), + array('example.com,', + '<a href="http://example.com/" rel="external">example.com</a>,'), + array('example.com;', + '<a href="http://example.com/" rel="external">example.com</a>;'), + array('example.com:', + '<a href="http://example.com/" rel="external">example.com</a>:'), + array('\'example.com\'', + '\'<a href="http://example.com/" rel="external">example.com</a>\''), + array('"example.com"', + '"<a href="http://example.com/" rel="external">example.com</a>"'), + array('example.com', + '<a href="http://example.com/" rel="external">example.com</a>'), + array('(example.com)', + '(<a href="http://example.com/" rel="external">example.com</a>)'), + array('[example.com]', + '[<a href="http://example.com/" rel="external">example.com</a>]'), + array('<example.com>', + '<<a href="http://example.com/" rel="external">example.com</a>>'), + array('Hey, check out my cool site example.com okay?', + 'Hey, check out my cool site <a href="http://example.com/" rel="external">example.com</a> okay?'), + array('Hey, check out my cool site example.com.I made it.', + 'Hey, check out my cool site <a href="http://example.com/" rel="external">example.com</a>.I made it.'), + array('Hey, check out my cool site example.com.Funny thing...', + 'Hey, check out my cool site <a href="http://example.com/" rel="external">example.com</a>.Funny thing...'), + array('Hey, check out my cool site example.com.You will love it.', + 'Hey, check out my cool site <a href="http://example.com/" rel="external">example.com</a>.You will love it.'), + array('What about parens (e.g. example.com/path/foo/(bar))?', + 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>)?'), + array('What about parens (e.g. example.com/path/foo/(bar)?', + 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>?'), + array('What about parens (e.g. example.com/path/foo/(bar).)?', + 'What about parens (e.g. <a href="http://example.com/path/foo/(bar)" rel="external">example.com/path/foo/(bar)</a>.)?'), + array('What about parens (e.g. example.com/path/(foo,bar)?', + 'What about parens (e.g. <a href="http://example.com/path/(foo,bar)" rel="external">example.com/path/(foo,bar)</a>?'), + array('file.ext', + 'file.ext'), + array('file.html', + 'file.html'), + array('file.php', + 'file.php') + ); + } +} + diff --git a/tpl/index.php b/tpl/index.php index be375e75a..36a161144 100644 --- a/tpl/index.php +++ b/tpl/index.php @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html> +<?php echo '<?';?>xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title><?php echo section('title'); ?></title> |