From c76e45a0dfda5592353e74c16ff0a8ab1f65f6fe Mon Sep 17 00:00:00 2001 From: Jean-Christian Denis Date: Thu, 26 Aug 2021 01:06:22 +0200 Subject: [PATCH] initial commit --- LICENSE | 339 +++++++++++ _admin.php | 314 ++++++++++ _define.php | 13 + _install.php | 94 +++ _prepend.php | 78 +++ _public.php | 625 ++++++++++++++++++++ _uninstall.php | 67 +++ _widgets.php | 211 +++++++ default-templates/img/broken-link.png | Bin 0 -> 23613 bytes default-templates/kutrl.html | 87 +++ default-templates/kutrl404.html | 67 +++ icon-b.png | Bin 0 -> 1605 bytes icon.png | Bin 0 -> 713 bytes inc/class.kutrl.php | 104 ++++ inc/img/kutrl_logo.png | Bin 0 -> 18825 bytes inc/index.link.php | 153 +++++ inc/index.links.php | 267 +++++++++ inc/index.service.php | 92 +++ inc/index.setting.php | 164 ++++++ inc/lib.kutrl.activityreport.php | 38 ++ inc/lib.kutrl.log.php | 314 ++++++++++ inc/lib.kutrl.srv.php | 254 ++++++++ inc/lib.wiki.kutrl.php | 80 +++ inc/patch.dcminiurl.php | 135 +++++ inc/services/class.bilbolinks.service.php | 97 ++++ inc/services/class.bitly.service.php | 127 ++++ inc/services/class.custom.service.php | 120 ++++ inc/services/class.default.service.php | 91 +++ inc/services/class.googl.service.php | 80 +++ inc/services/class.isgd.service.php | 56 ++ inc/services/class.local.service.php | 270 +++++++++ inc/services/class.shortto.service.php | 56 ++ inc/services/class.supr.service.php | 120 ++++ inc/services/class.trim.service.php | 126 ++++ inc/services/class.yourls.service.php | 126 ++++ index.php | 70 +++ js/admin.js | 15 + js/main.js | 80 +++ js/service.js | 17 + js/setting.js | 17 + locales/en/help/help.html | 19 + locales/en/resources.php | 16 + locales/fr/help/help.html | 19 + locales/fr/main.lang.php | 522 +++++++++++++++++ locales/fr/main.po | 679 ++++++++++++++++++++++ locales/fr/resources.php | 16 + release.txt | 85 +++ 47 files changed, 6320 insertions(+) create mode 100644 LICENSE create mode 100644 _admin.php create mode 100644 _define.php create mode 100644 _install.php create mode 100644 _prepend.php create mode 100644 _public.php create mode 100644 _uninstall.php create mode 100644 _widgets.php create mode 100644 default-templates/img/broken-link.png create mode 100644 default-templates/kutrl.html create mode 100644 default-templates/kutrl404.html create mode 100644 icon-b.png create mode 100644 icon.png create mode 100644 inc/class.kutrl.php create mode 100644 inc/img/kutrl_logo.png create mode 100644 inc/index.link.php create mode 100644 inc/index.links.php create mode 100644 inc/index.service.php create mode 100644 inc/index.setting.php create mode 100644 inc/lib.kutrl.activityreport.php create mode 100644 inc/lib.kutrl.log.php create mode 100644 inc/lib.kutrl.srv.php create mode 100644 inc/lib.wiki.kutrl.php create mode 100644 inc/patch.dcminiurl.php create mode 100644 inc/services/class.bilbolinks.service.php create mode 100644 inc/services/class.bitly.service.php create mode 100644 inc/services/class.custom.service.php create mode 100644 inc/services/class.default.service.php create mode 100644 inc/services/class.googl.service.php create mode 100644 inc/services/class.isgd.service.php create mode 100644 inc/services/class.local.service.php create mode 100644 inc/services/class.shortto.service.php create mode 100644 inc/services/class.supr.service.php create mode 100644 inc/services/class.trim.service.php create mode 100644 inc/services/class.yourls.service.php create mode 100644 index.php create mode 100644 js/admin.js create mode 100644 js/main.js create mode 100644 js/service.js create mode 100644 js/setting.js create mode 100644 locales/en/help/help.html create mode 100644 locales/en/resources.php create mode 100644 locales/fr/help/help.html create mode 100644 locales/fr/main.lang.php create mode 100644 locales/fr/main.po create mode 100644 locales/fr/resources.php create mode 100644 release.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/LICENSE @@ -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/_admin.php b/_admin.php new file mode 100644 index 0000000..c6f5db3 --- /dev/null +++ b/_admin.php @@ -0,0 +1,314 @@ +addItem( + __('Links shortener'), + 'plugin.php?p=kUtRL','index.php?pf=kUtRL/icon.png', + preg_match('/plugin.php\?p=kUtRL(&.*)?$/',$_SERVER['REQUEST_URI']), + $core->auth->check('admin',$core->blog->id) +); + +# Admin behaviors +if ($core->blog->settings->kUtRL->kutrl_active) +{ + $core->addBehavior('adminPostHeaders',array('adminKutrl','adminPostHeaders')); + $core->addBehavior('adminPostFormSidebar',array('adminKutrl','adminPostFormSidebar')); + $core->addBehavior('adminAfterPostUpdate',array('adminKutrl','adminAfterPostUpdate')); // update existing short url + $core->addBehavior('adminAfterPostUpdate',array('adminKutrl','adminAfterPostCreate')); // create new short url + $core->addBehavior('adminAfterPostCreate',array('adminKutrl','adminAfterPostCreate')); + $core->addBehavior('adminBeforePostDelete',array('adminKutrl','adminBeforePostDelete')); + $core->addBehavior('adminPostsActionsCombo',array('adminKutrl','adminPostsActionsCombo')); + $core->addBehavior('adminPostsActions',array('adminKutrl','adminPostsActions')); +} + +$core->addBehavior('exportFull',array('backupKutrl','exportFull')); +$core->addBehavior('exportSingle',array('backupKutrl','exportSingle')); +$core->addBehavior('importInit',array('backupKutrl','importInit')); +$core->addBehavior('importSingle',array('backupKutrl','importSingle')); +$core->addBehavior('importFull',array('backupKutrl','importFull')); + +# Admin behaviors class +class adminKutrl +{ + public static function adminPostHeaders() + { + return dcPage::jsLoad('index.php?pf=kUtRL/js/admin.js'); + } + + public static function adminPostFormSidebar($post) + { + global $core; + $s = $core->blog->settings->kUtRL; + + if (!$s->kutrl_active || !$s->kutrl_admin_service) return; + + if (null === ($kut = kutrl::quickPlace('admin'))) return; + + if ($post) + { + $post_url = $post->getURL(); + $rs = $kut->isKnowUrl($post_url); + } + else + { + $post_url = ''; + $rs = false; + } + + echo + '

'.__('Short link').'

'. + '
'. + form::hidden(array('kutrl_old_post_url'),$post_url); + + if (!$rs) + { + if (empty($_POST['kutrl_old_post_url']) && $s->kutrl_admin_entry_default) + { + $chk = true; + } + else + { + $chk = !empty($_POST['kutrl_create']); + } + echo + '

'; + + if ($kut->allow_custom_hash) + { + echo + '

'. + '

'; + } + } + else + { + $count = $rs->counter; + if ($count == 0) + { + $title = __('never followed'); + } + elseif ($count == 1) + { + $title = __('followed one time'); + } + else + { + $title = sprintf(__('followed %s times'),$count); + } + $href = $kut->url_base.$rs->hash; + + echo + '

'. + '

'.$href.'

'; + } + echo '
'; + } + + public static function adminAfterPostUpdate($cur,$post_id) + { + global $core; + $s = $core->blog->settings->kUtRL; + + # Create: see adminAfterPostCreate + if (!empty($_POST['kutrl_create']) || !$s->kutrl_active) return; + + if (null === ($kut = kutrl::quickPlace('admin'))) return; + + if (empty($_POST['kutrl_old_post_url'])) return; + + $old_post_url = $_POST['kutrl_old_post_url']; + + if (!($rs = $kut->isKnowUrl($old_post_url))) return; + + $rs = $core->blog->getPosts(array('post_id'=>$post_id)); + $title = html::escapeHTML($rs->post_title); + + if ($rs->isEmpty()) return; + + $new_post_url = $rs->getURL(); + + # Delete + if (!empty($_POST['kutrl_delete'])) + { + $kut->remove($old_post_url); + } + # Update + else + { + if ($old_post_url == $new_post_url) return; + + $kut->remove($old_post_url); + + $rs = $kut->hash($new_post_url,$custom); // better to update (not yet implemented) + $url = $kut->url_base.$rs->hash; + + # ex: Send new url to messengers + if (!empty($rs)) + { + $core->callBehavior('adminAfterKutrlCreate',$core,$rs,$title); + } + } + } + + public static function adminAfterPostCreate($cur,$post_id) + { + global $core; + $s = $core->blog->settings->kUtRL; + + if (empty($_POST['kutrl_create']) || !$s->kutrl_active) return; + + if (null === ($kut = kutrl::quickPlace('admin'))) return; + + $rs = $core->blog->getPosts(array('post_id'=>$post_id)); + $title = html::escapeHTML($rs->post_title); + + if ($rs->isEmpty()) return; + + $custom = !empty($_POST['kutrl_create_custom']) && $kut->allow_custom_hash ? + $_POST['kutrl_create_custom'] : null; + + $rs = $kut->hash($rs->getURL(),$custom); + $url = $kut->url_base.$rs->hash; + + # ex: Send new url to messengers + if (!empty($rs)) + { + $core->callBehavior('adminAfterKutrlCreate',$core,$rs,$title); + } + } + + public static function adminBeforePostDelete($post_id) + { + global $core; + $s = $core->blog->settings->kUtRL; + + if (!$s->kutrl_active) return; + + if (null === ($kut = kutrl::quickPlace('admin'))) return; + + $rs = $core->blog->getPosts(array('post_id'=>$post_id)); + + if ($rs->isEmpty()) return; + + $kut->remove($rs->getURL()); + } + + public static function adminPostsActionsCombo($args) + { + global $core; + $s = $core->blog->settings->kUtRL; + + if (!$s->kutrl_active + || !$core->auth->check('admin',$core->blog->id)) return; + + $args[0][__('kUtRL')][__('create short link')] = 'kutrl_create'; + $args[0][__('kUtRL')][__('delete short link')] = 'kutrl_delete'; + } + + public static function adminPostsActions($core,$posts,$action,$redir) + { + if ($action != 'kutrl_create' + && $action != 'kutrl_delete') return; + + $s = $core->blog->settings->kUtRL; + + if (!$s->kutrl_active) return; + + if (null === ($kut = kutrl::quickPlace('admin'))) return; + + while ($posts->fetch()) + { + $url = $posts->getURL(); + + if ($action == 'kutrl_create') + { + $kut->hash($url); + } + + if ($action == 'kutrl_delete') + { + $kut->remove($url); + } + } + $core->blog->triggerBlog(); + http::redirect($redir.'&done=1'); + } +} + +# Import/export behaviors for Import/export plugin +class backupKutrl +{ + public static function exportSingle($core,$exp,$blog_id) + { + $exp->export('kutrl', + 'SELECT kut_id, blog_id, kut_service, kut_type, '. + 'kut_hash, kut_url, kut_dt, kut_password, kut_counter '. + 'FROM '.$core->prefix.'kutrl '. + "WHERE blog_id = '".$blog_id."' " + ); + } + + public static function exportFull($core,$exp) + { + $exp->exportTable('kutrl'); + } + + public static function importInit($bk,$core) + { + $bk->cur_kutrl = $core->con->openCursor($core->prefix.'kutrl'); + $bk->kutrl = new kutrlLog($core); + } + + public static function importSingle($line,$bk,$core) + { + if ($line->__name == 'kutrl') + { + # Do nothing if str/type exists ! + if (false === $bk->kutrl->select($line->kut_url,$line->kut_hash,$line->kut_type,$line->kut_service)) + { + $bk->kutrl->insert($line->kut_url,$line->kut_hash,$line->kut_type,$line->kut_service); + } + } + } + + public static function importFull($line,$bk,$core) + { + if ($line->__name == 'kutrl') + { + $bk->cur_kutrl->clean(); + + $bk->cur_kutrl->kut_id = (integer) $line->kut_id; + $bk->cur_kutrl->blog_id = (string) $line->blog_id; + $bk->cur_kutrl->kut_service = (string) $line->kut_service; + $bk->cur_kutrl->kut_type = (string) $line->kut_type; + $bk->cur_kutrl->kut_hash = (string) $line->kut_hash; + $bk->cur_kutrl->kut_url = (string) $line->kut_url; + $bk->cur_kutrl->kut_dt = (string) $line->miniurl_dt; + $bk->cur_kutrl->kut_counter = (integer) $line->kut_counter; + $bk->cur_kutrl->kut_password = (string) $line->kut_password; + + $bk->cur_kutrl->insert(); + } + } +} +?> \ No newline at end of file diff --git a/_define.php b/_define.php new file mode 100644 index 0000000..ff63dc5 --- /dev/null +++ b/_define.php @@ -0,0 +1,13 @@ +registerModule( /* Name */ "kUtRL", /* Description*/ "Use, create and serve short url on your blog", /* Author */ "JC Denis", /* Version */ '2011.03.24', /* Permissions */ 'admin' ); ?> \ No newline at end of file diff --git a/_install.php b/_install.php new file mode 100644 index 0000000..5c04d11 --- /dev/null +++ b/_install.php @@ -0,0 +1,94 @@ +plugins->moduleInfo('kUtRL','version'); +$old_version = $core->getVersion('kUtRL'); + +# Compare versions +if (version_compare($old_version,$new_version,'>=')) {return;} + +# Install or update +try +{ + if (version_compare(str_replace("-r","-p",DC_VERSION),'2.2-alpha','<')) + { + throw new Exception('kUtRL requires Dotclear 2.2'); + } + + # Table + $t = new dbStruct($core->con,$core->prefix); + $t->kutrl + ->kut_id('bigint',0,false) + ->blog_id('varchar',32,false) + ->kut_service('varchar',32,false,"'kUtRL'") + ->kut_type('varchar',32,false) + ->kut_hash('varchar',32,false) + ->kut_url('text',0,false) + ->kut_dt('timestamp',0,false,'now()') + ->kut_password('varchar',32,true) + ->kut_counter('bigint',0,false,0) + + ->primary('pk_kutrl','kut_id') + ->index('idx_kut_blog_id','btree','blog_id') + ->index('idx_kut_hash','btree','kut_hash') + ->index('idx_kut_service','btree','kut_service') + ->index('idx_kut_type','btree','kut_type'); + + $ti = new dbStruct($core->con,$core->prefix); + $changes = $ti->synchronize($t); + + # Settings + $core->blog->settings->addNamespace('kUtRL'); + $s = $core->blog->settings->kUtRL; + $s->put('kutrl_active',false,'boolean','Enabled kutrl plugin',false,true); + $s->put('kutrl_plugin_service','default','string','Service to use to shorten links on third part plugins',false,true); + $s->put('kutrl_admin_service','local','string','Service to use to shorten links on admin',false,true); + $s->put('kutrl_tpl_service','local','string','Service to use to shorten links on template',false,true); + $s->put('kutrl_wiki_service','local','string','Service to use to shorten links on contents',false,true); + $s->put('kutrl_allow_external_url',true,'boolean','Limited short url to current blog\'s url',false,true); + $s->put('kutrl_tpl_passive',true,'boolean','Return long url on kutrl tags if kutrl is unactivate',false,true); + $s->put('kutrl_tpl_active',false,'boolean','Return short url on dotclear tags if kutrl is active',false,true); + $s->put('kutrl_admin_entry_default',true,'boolean','Create short link an new entry by default',false,true); + # Settings for "local" service + $local_css = + ".shortenkutrlwidget input { border: 1px solid #CCCCCC; }\n". + ".dc-kutrl input { border: 1px solid #CCCCCC; margin: 10px; }"; + $s->put('kutrl_srv_local_protocols','http:,https:,ftp:,ftps:,irc:','string','Allowed kutrl local service protocols',false,true); + $s->put('kutrl_srv_local_public',false,'boolean','Enabled local service public page',false,true); + $s->put('kutrl_srv_local_css',$local_css,'string','Special CSS for kutrl local service',false,true); + $s->put('kutrl_srv_local_404_active',false,'boolean','Use special 404 page on unknow urls',false,true); + # Settings for "bilbolinks" service + $s->put('kutrl_srv_bilbolinks_base','http://tux-pla.net/','string','URL of bilbolinks service',false,true); + # Settings for "YOURLS" service + $s->put('kutrl_srv_yourls_base','','string','URL of YOURLS service',false,true); + $s->put('kutrl_srv_yourls_username','','string','User name to YOURLS service',false,true); + $s->put('kutrl_srv_yourls_password','','string','User password to YOURLS service',false,true); + + # Version + $core->setVersion('kUtRL',$new_version); + + # Get dcMiniUrl records as this plugin do the same + if ($core->plugins->moduleExists('dcMiniUrl')) + { + require_once dirname(__FILE__).'/inc/patch.dcminiurl.php'; + } + return true; +} +catch (Exception $e) +{ + $core->error->add($e->getMessage()); + return false; +} +?> \ No newline at end of file diff --git a/_prepend.php b/_prepend.php new file mode 100644 index 0000000..522a43f --- /dev/null +++ b/_prepend.php @@ -0,0 +1,78 @@ +addBehavior('kutrlService',create_function(null,'return array("bilbolinks","bilbolinksKutrlService");')); +$__autoload['bitlyKutrlService'] = dirname(__FILE__).'/inc/services/class.bitly.service.php'; +$core->addBehavior('kutrlService',create_function(null,'return array("bitly","bitlyKutrlService");')); +$__autoload['customKutrlService'] = dirname(__FILE__).'/inc/services/class.custom.service.php'; +$core->addBehavior('kutrlService',create_function(null,'return array("custom","customKutrlService");')); +$__autoload['defaultKutrlService'] = dirname(__FILE__).'/inc/services/class.default.service.php'; +$core->addBehavior('kutrlService',create_function(null,'return array("default","defaultKutrlService");')); +$__autoload['googlKutrlService'] = dirname(__FILE__).'/inc/services/class.googl.service.php'; +$core->addBehavior('kutrlService',create_function(null,'return array("googl","googlKutrlService");')); +$__autoload['isgdKutrlService'] = dirname(__FILE__).'/inc/services/class.isgd.service.php'; +$core->addBehavior('kutrlService',create_function(null,'return array("isgd","isgdKutrlService");')); +$__autoload['localKutrlService'] = dirname(__FILE__).'/inc/services/class.local.service.php'; +$core->addBehavior('kutrlService',create_function(null,'return array("local","localKutrlService");')); +$__autoload['shorttoKutrlService'] = dirname(__FILE__).'/inc/services/class.shortto.service.php'; +$core->addBehavior('kutrlService',create_function(null,'return array("shortto","shorttoKutrlService");')); +$__autoload['trimKutrlService'] = dirname(__FILE__).'/inc/services/class.trim.service.php'; +$core->addBehavior('kutrlService',create_function(null,'return array("trim","trimKutrlService");')); +$__autoload['yourlsKutrlService'] = dirname(__FILE__).'/inc/services/class.yourls.service.php'; +$core->addBehavior('kutrlService',create_function(null,'return array("yourls","yourlsKutrlService");')); +$__autoload['suprKutrlService'] = dirname(__FILE__).'/inc/services/class.supr.service.php'; +$core->addBehavior('kutrlService',create_function(null,'return array("supr","suprKutrlService");')); + +# Shorten url passed through wiki functions +$__autoload['kutrlWiki'] = dirname(__FILE__).'/inc/lib.wiki.kutrl.php'; +$core->addBehavior('coreInitWikiPost',array('kutrlWiki','coreInitWiki')); +$core->addBehavior('coreInitWikiComment',array('kutrlWiki','coreInitWiki')); +$core->addBehavior('coreInitWikiSimpleComment',array('kutrlWiki','coreInitWiki')); + +# Public page +$core->url->register('kutrl','go','^go(/(.*?)|)$',array('urlKutrl','redirectUrl')); + +# Add kUtRL events on plugin activityReport +if (defined('ACTIVITY_REPORT')) +{ + require_once dirname(__FILE__).'/inc/lib.kutrl.activityreport.php'; +} +?> \ No newline at end of file diff --git a/_public.php b/_public.php new file mode 100644 index 0000000..37ca675 --- /dev/null +++ b/_public.php @@ -0,0 +1,625 @@ +tpl->setPath($core->tpl->getPath(),dirname(__FILE__).'/default-templates'); + +$core->addBehavior('publicBeforeDocument',array('pubKutrl','publicBeforeDocument')); +$core->addBehavior('publicHeadContent',array('pubKutrl','publicHeadContent')); +$core->addBehavior('publicBeforeContentFilter',array('pubKutrl','publicBeforeContentFilter')); +$core->addBehavior('templateBeforeValue',array('pubKutrl','templateBeforeValue')); +$core->addBehavior('templateAfterValue',array('pubKutrl','templateAfterValue')); + +$core->tpl->addBlock('kutrlPageIf',array('tplKutrl','pageIf')); +$core->tpl->addBlock('kutrlMsgIf',array('tplKutrl','pageMsgIf')); + +$core->tpl->addValue('kutrlPageURL',array('tplKutrl','pageURL')); +$core->tpl->addValue('kutrlMsg',array('tplKutrl','pageMsg')); +$core->tpl->addValue('kutrlHumanField',array('tplKutrl','humanField')); +$core->tpl->addValue('kutrlHumanFieldProtect',array('tplKutrl','humanFieldProtect')); + +$core->tpl->addBlock('AttachmentKutrlIf',array('tplKutrl','AttachmentKutrlIf')); +$core->tpl->addValue('AttachmentKutrl',array('tplKutrl','AttachmentKutrl')); +$core->tpl->addBlock('MediaKutrlIf',array('tplKutrl','MediaKutrlIf')); +$core->tpl->addValue('MediaKutrl',array('tplKutrl','MediaKutrl')); +$core->tpl->addBlock('EntryAuthorKutrlIf',array('tplKutrl','EntryAuthorKutrlIf')); +$core->tpl->addValue('EntryAuthorKutrl',array('tplKutrl','EntryAuthorKutrl')); +$core->tpl->addBlock('EntryKutrlIf',array('tplKutrl','EntryKutrlIf')); +$core->tpl->addValue('EntryKutrl',array('tplKutrl','EntryKutrl')); +$core->tpl->addBlock('CommentAuthorKutrlIf',array('tplKutrl','CommentAuthorKutrlIf')); +$core->tpl->addValue('CommentAuthorKutrl',array('tplKutrl','CommentAuthorKutrl')); +$core->tpl->addBlock('CommentPostKutrlIf',array('tplKutrl','CommentPostKutrlIf')); +$core->tpl->addValue('CommentPostKutrl',array('tplKutrl','CommentPostKutrl')); + +class urlKutrl extends dcUrlHandlers +{ + # Redirect !!! local !!! service only + public static function redirectUrl($args) + { + global $core, $_ctx; + $s = $core->blog->settings->kUtRL; + + # Not active, go to default 404 + if (!$s->kutrl_active) + { + self::p404(); + return; + } + # Not a valid url, go to kutrl 404 + if (!preg_match('#^(|(/(.*?)))$#',$args,$m)) + { + self::kutrl404(); + return; + } + + $args = isset($m[3]) ? $m[3] : ''; + $_ctx->kutrl_msg = ''; + $_ctx->kutrl_hmf = hmfKutrl::create(); + $_ctx->kutrl_hmfp = hmfKutrl::protect($_ctx->kutrl_hmf); + + $kut = new localKutrlService($core); + + # Nothing on url + if ($m[1] == '/') + { + $_ctx->kutrl_msg = 'No link given.'; + } + # find suffix on redirect url + $suffix = ''; + if (preg_match('@^([^?/#]+)(.*?)$@',$args,$more)) + { + $args = $more[1]; + $suffix = $more[2]; + } + # No arg, go to kurtl page + if ($args == '') + { + self::pageKutrl($kut); + return; + } + # Not find, go to kutrl 404 + if (false === ($url = $kut->getUrl($args))) + { + //$_ctx->kutrl_msg = 'Failed to find short link.'; + //self::pageKutrl($kut); + + self::kutrl404(); + return; + } + # Removed (empty url), go to kutrl 404 + if (!$url) + { + self::kutrl404(); + return; + } + + $core->blog->triggerBlog(); + http::redirect($url.$suffix); + return; + } + + private static function pageKutrl($kut) + { + global $core, $_ctx; + $s = $core->blog->settings->kUtRL; + + # Not active, go to default 404 + if (!$s->kutrl_active) + { + self::p404(); + return; + } + # Public page not active, go to kutrl 404 + if (!$s->kutrl_srv_local_public) + { + self::kutrl404(); + return; + } + # Validation form + $url = !empty($_POST['longurl']) ? trim($core->con->escape($_POST['longurl'])) : ''; + if (!empty($url)) + { + $hmf = !empty($_POST['hmf']) ? $_POST['hmf'] : '!'; + $hmfu = !empty($_POST['hmfp']) ? hmfKutrl::unprotect($_POST['hmfp']) : '?'; + + $err = false; + if (!$err) + { + if ($hmf != $hmfu) + { + $err = true; + $_ctx->kutrl_msg = __('Failed to verify protected field.'); + } + } + if (!$err) + { + if (!$kut->testService()) + { + $err = true; + $_ctx->kutrl_msg = __('Service is not well configured.'); + } + } + if (!$err) + { + if (!$kut->isValidUrl($url)) + { + $err = true; + $_ctx->kutrl_msg = __('This string is not a valid URL.'); + } + } + if (!$err) + { + if (!$kut->isLongerUrl($url)) + { + $err = true; + $_ctx->kutrl_msg = __('This link is too short.'); + } + } + if (!$err) + { + if (!$kut->isProtocolUrl($url)) + { + $err = true; + $_ctx->kutrl_msg = __('This type of link is not allowed.'); + } + } + + if (!$err) + { + if (!$kut->allow_external_url && !$kut->isBlogUrl($url)) + { + $err = true; + $_ctx->kutrl_msg = __('Short links are limited to this blog URL.'); + } + } + if (!$err) + { + if ($kut->isServiceUrl($url)) + { + $err = true; + $_ctx->kutrl_msg = __('This link is already a short link.'); + } + } + if (!$err) + { + if (false !== ($rs = $kut->isKnowUrl($url))) + { + $err = true; + + $url = $rs->url; + $new_url = $kut->url_base.$rs->hash; + + $_ctx->kutrl_msg = sprintf( + __('Short link for %s is %s'), + html::escapeHTML($url), + ''.$new_url.'' + ); + } + } + if (!$err) + { + if (false === ($rs = $kut->hash($url))) + { + $err = true; + $_ctx->kutrl_msg = __('Failed to create short link.'); + } + else + { + $url = $rs->url; + $new_url = $kut->url_base.$rs->hash; + + $_ctx->kutrl_msg = sprintf( + __('Short link for %s is %s'), + html::escapeHTML($url), + ''.$new_url.'' + ); + $core->blog->triggerBlog(); + + # ex: Send new url to messengers + if (!empty($rs)) + { + $core->callBehavior('publicAfterKutrlCreate',$core,$rs,__('New public short URL')); + } + } + } + } + + $core->tpl->setPath($core->tpl->getPath(),dirname(__FILE__).'/default-templates'); + self::serveDocument('kutrl.html'); + return; + } + + protected static function kutrl404() + { + global $core; + + if (!$core->blog->settings->kUtRL->kutrl_srv_local_404_active) + { + self::p404(); + return; + } + + $core->tpl->setPath($core->tpl->getPath(),dirname(__FILE__).'/default-templates'); + $_ctx =& $GLOBALS['_ctx']; + $core = $GLOBALS['core']; + + header('Content-Type: text/html; charset=UTF-8'); + http::head(404,'Not Found'); + $core->url->type = '404'; + $_ctx->current_tpl = 'kutrl404.html'; + $_ctx->content_type = 'text/html'; + + echo $core->tpl->getData($_ctx->current_tpl); + + # --BEHAVIOR-- publicAfterDocument + $core->callBehavior('publicAfterDocument',$core); + exit; + } +} + +class pubKutrl +{ + # List of template tag which content URL that can be shortenn + public static $know_tags = array( + 'AttachmentURL', + 'CategoryURL', + 'MediaURL', + 'EntryAuthorURL', + 'EntryURL', + 'EntryCategoryURL', + 'CommentAuthorURL', + 'CommentPostURL' + ); + + # Disable URL shoretning on filtered tag + public static function templateBeforeValue($core,$tag,$attr) + { + if (!empty($attr['disable_kutrl']) && in_array($tag,pubKutrl::$know_tags)) + { + return ''; + } + return; + } + + # Re unable it after tag + public static function templateAfterValue($core,$tag,$attr) + { + if (!empty($attr['disable_kutrl']) && in_array($tag,pubKutrl::$know_tags)) + { + return ''; + } + return; + } + + # Replace long urls on the fly (on filter) for default tags + public static function publicBeforeContentFilter($core,$tag,$args) + { + # Unknow tag + if (!in_array($tag,pubKutrl::$know_tags)) return; + + # URL shortening is disabled by tag attribute + if (empty($GLOBALS['disable_kutrl'])) + { + # kUtRL is not activated + if (!$core->blog->settings->kUtRL->kutrl_active + || !$core->blog->settings->kUtRL->kutrl_tpl_active) return; + + global $_ctx; + + # Oups + if (!$_ctx->exists('kutrl')) return; + + # Existing + if (false !== ($kutrl_rs = $_ctx->kutrl->isKnowUrl($args[0]))) + { + $args[0] = $_ctx->kutrl->url_base.$kutrl_rs->hash; + } + # New + elseif (false !== ($kutrl_rs = $_ctx->kutrl->hash($args[0]))) + { + $args[0] = $_ctx->kutrl->url_base.$kutrl_rs->hash; + + # ex: Send new url to messengers + if (!empty($kutrl_rs)) + { + $core->callBehavior('publicAfterKutrlCreate',$core,$kutrl_rs,__('New public short URL')); + } + } + } + } + + public static function publicBeforeDocument($core) + { + global $_ctx; + $s = $core->blog->settings->kUtRL; + + # Passive : all kutrl tag return long url + $_ctx->kutrl_passive = (boolean) $s->kutrl_tpl_passive; + + if (!$s->kutrl_active || !$s->kutrl_tpl_service) return; + + if (null === ($kut = kutrl::quickPlace('tpl'))) return; + + $_ctx->kutrl = $kut; + } + + public static function publicHeadContent($core) + { + $css = $core->blog->settings->kUtRL->kutrl_srv_local_css; + if ($css) + { + echo + "\n \n". + "\n"; + } + } +} + +class tplKutrl +{ + public static function pageURL($attr) + { + $f = $GLOBALS['core']->tpl->getFilters($attr); + return 'blog->url.$core->url->getBase("kutrl")').'; ?>'; + } + + public static function pageIf($attr,$content) + { + $operator = isset($attr['operator']) ? self::getOperator($attr['operator']) : '&&'; + + if (isset($attr['is_active'])) + { + $sign = (boolean) $attr['is_active'] ? '' : '!'; + $if[] = $sign.'$core->blog->settings->kUtRL->kutrl_srv_local_public'; + } + + if (empty($if)) + { + return $content; + } + + return + "\n". + $content. + "\n"; + } + + public static function pageMsgIf($attr,$content) + { + $operator = isset($attr['operator']) ? self::getOperator($attr['operator']) : '&&'; + + if (isset($attr['has_message'])) + { + $sign = (boolean) $attr['has_message'] ? '!' : '='; + $if[] = '"" '.$sign.'= $_ctx->kutrl_msg'; + } + + if (empty($if)) + { + return $content; + } + + return + "\n". + $content. + "\n"; + } + + public static function pageMsg($attr) + { + return 'kutrl_msg; ?>'; + } + + public static function humanField($attr) + { + return "kutrl_hmf); ?>"; + } + + public static function humanFieldProtect($attr) + { + return + "kutrl_hmfp; ?>\" />". + "formNonce(); ?>"; + } + + public static function AttachmentKutrlIf($attr,$content) + { + return self::genericKutrlIf('$attach_f->file_url',$attr,$content); + } + + public static function AttachmentKutrl($attr) + { + return self::genericKutrl('$attach_f->file_url',$attr); + } + + public static function MediaKutrlIf($attr,$content) + { + return self::genericKutrlIf('$_ctx->file_url',$attr,$content); + } + + public static function MediaKutrl($attr) + { + return self::genericKutrl('$_ctx->file_url',$attr); + } + + public static function EntryAuthorKutrlIf($attr,$content) + { + return self::genericKutrlIf('$_ctx->posts->user_url',$attr,$content); + } + + public static function EntryAuthorKutrl($attr) + { + return self::genericKutrl('$_ctx->posts->user_url',$attr); + } + + public static function EntryKutrlIf($attr,$content) + { + return self::genericKutrlIf('$_ctx->posts->getURL()',$attr,$content); + } + + public static function EntryKutrl($attr) + { + return self::genericKutrl('$_ctx->posts->getURL()',$attr); + } + + public static function CommentAuthorKutrlIf($attr,$content) + { + return self::genericKutrlIf('$_ctx->comments->getAuthorURL()',$attr,$content); + } + + public static function CommentAuthorKutrl($attr) + { + return self::genericKutrl('$_ctx->comments->getAuthorURL()',$attr); + } + + public static function CommentPostKutrlIf($attr,$content) + { + return self::genericKutrlIf('$_ctx->comments->getPostURL()',$attr,$content); + } + + public static function CommentPostKutrl($attr) + { + return self::genericKutrl('$_ctx->comments->getPostURL()',$attr); + } + + protected static function genericKutrlIf($str,$attr,$content) + { + $operator = isset($attr['operator']) ? self::getOperator($attr['operator']) : '&&'; + + if (isset($attr['is_active'])) + { + $sign = (boolean) $attr['is_active'] ? '' : '!'; + $if[] = $sign.'$_ctx->exists("kutrl")'; + } + + if (isset($attr['passive_mode'])) + { + $sign = (boolean) $attr['passive_mode'] ? '' : '!'; + $if[] = $sign.'$_ctx->kutrl_passive'; + } + + if (isset($attr['has_kutrl'])) + { + $sign = (boolean) $attr['has_kutrl'] ? '!' : '='; + $if[] = '($_ctx->exists("kutrl") && false '.$sign.'== $_ctx->kutrl->select('.$str.',null,null,"kutrl"))'; + } + + if (empty($if)) + { + return $content; + } + return + "\n". + $content. + "\n"; + } + + protected static function genericKutrl($str,$attr) + { + $f = $GLOBALS['core']->tpl->getFilters($attr); + return + "preview) { \n". + " echo ".sprintf($f,$str)."; ". + "} else { \n". + # Disable + "if (!\$_ctx->exists('kutrl')) { \n". + # Passive mode + " if (\$_ctx->kutrl_passive) { ". + " echo ".sprintf($f,$str)."; ". + " } \n". + "} else { \n". + # Existing + " if (false !== (\$kutrl_rs = \$_ctx->kutrl->isKnowUrl(".$str."))) { ". + " echo ".sprintf($f,'$_ctx->kutrl->url_base.$kutrl_rs->hash')."; ". + " } \n". + # New + " elseif (false !== (\$kutrl_rs = \$_ctx->kutrl->hash(".$str."))) { ". + " echo ".sprintf($f,'$_ctx->kutrl->url_base.$kutrl_rs->hash')."; ". + + # ex: Send new url to messengers + " if (!empty(\$kutrl_rs)) { ". + " \$core->callBehavior('publicAfterKutrlCreate',\$core,\$kutrl_rs,__('New public short URL')); ". + " } \n". + + " } \n". + " unset(\$kutrl_rs); \n". + "} \n". + "} \n". + "?>\n"; + } + + protected static function getOperator($op) + { + switch (strtolower($op)) + { + case 'or': + case '||': + return '||'; + case 'and': + case '&&': + default: + return '&&'; + } + } +} + +class hmfKutrl +{ + public static $chars = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'; + + public static function create($len=6) + { + $res = ''; + $chars = self::$chars; + + for($i = 0;$i < $len; $i++) + { + $res .= $chars[rand(0,strlen($chars)-1)]; + } + + return $res; + } + + public static function protect($str) + { + $res = ''; + $chars = self::$chars; + + for($i = 0; $i < strlen($str);$i++) + { + $res .= $chars[rand(0,strlen($chars)-1)].$str[$i]; + } + + return $res; + } + + public static function unprotect($str) + { + $res = ''; + + for($i = 0; $i < strlen($str);$i++) + { + $i++; + $res .= $str[$i]; + } + + return $res; + } +} +?> \ No newline at end of file diff --git a/_uninstall.php b/_uninstall.php new file mode 100644 index 0000000..9e22e41 --- /dev/null +++ b/_uninstall.php @@ -0,0 +1,67 @@ +addUserAction( + /* type */ 'settings', + /* action */ 'delete_all', + /* ns */ 'kUtRL', + /* description */ __('delete all settings') +); + +$this->addUserAction( + /* type */ 'tables', + /* action */ 'delete', + /* ns */ 'kutrl', + /* description */ __('delete table') +); + +$this->addUserAction( + /* type */ 'plugins', + /* action */ 'delete', + /* ns */ 'kUtRL', + /* description */ __('delete plugin files') +); + +$this->addUserAction( + /* type */ 'versions', + /* action */ 'delete', + /* ns */ 'kUtRL', + /* description */ __('delete the version number') +); + + +# Delete only dc version and plugin files from pluginsBeforeDelete +# Keep table + +$this->addDirectAction( + /* type */ 'settings', + /* action */ 'delete_all', + /* ns */ 'kUtRL', + /* description */ sprintf(__('delete all %s settings'),'kUtRL') +); + +$this->addDirectAction( + /* type */ 'versions', + /* action */ 'delete', + /* ns */ 'kUtRL', + /* description */ sprintf(__('delete %s version number'),'kUtRL') +); + +$this->addDirectAction( + /* type */ 'plugins', + /* action */ 'delete', + /* ns */ 'kUtRL', + /* description */ sprintf(__('delete %s plugin files'),'kUtRL') +); +?> \ No newline at end of file diff --git a/_widgets.php b/_widgets.php new file mode 100644 index 0000000..31eb81f --- /dev/null +++ b/_widgets.php @@ -0,0 +1,211 @@ +addBehavior('initWidgets',array('widgetKutrl','adminShorten')); +$core->addBehavior('initWidgets',array('widgetKutrl','adminRank')); + +class widgetKutrl +{ + public static function adminShorten($w) + { + $w->create('shortenkutrl',__('Links shortener'), + array('widgetKutrl','publicShorten') + ); + $w->shortenkutrl->setting('title', + __('Title:'),__('Shorten link'),'text' + ); + $w->shortenkutrl->setting('homeonly', + __('Home page only'),1,'check' + ); + } + + public static function adminRank($w) + { + $w->create('rankkutrl',__('Top of short links'), + array('widgetKutrl','publicRank') + ); + $w->rankkutrl->setting('title', + __('Title:'),__('Top of short links'),'text' + ); + $w->rankkutrl->setting('text', + __('Text: (Use wildcard %rank%, %hash%, %url%, %count%, %counttext%)'),'%rank% - %url% - %counttext%','text' + ); + $w->rankkutrl->setting('urllen', + __('URL length (if truncate)'),20,'text' + ); + $w->rankkutrl->setting('type', + __('Type:'),'all','combo',array( + __('All') => '-', + __('Mini URL') => 'localnormal', + __('Custom URL') => 'localcustom', + __('Semi-custom') => 'localmix' + ) + ); + $w->rankkutrl->setting('mixprefix', + __('Semi-custom prefix: (only if you want limit to a particular prefix)'), + '','text' + ); + $w->rankkutrl->setting('sortby', + __('Sort by:'),'kut_counter','combo',array( + __('Date') => 'kut_dt', + __('Rank') => 'kut_counter', + __('Hash') => 'kut_hash' + ) + ); + $w->rankkutrl->setting('sort', + __('Sort:'),'desc','combo',array( + __('Ascending') => 'asc', + __('Descending') => 'desc' + ) + ); + $w->rankkutrl->setting('limit', + __('Limit:'),'10','text' + ); + $w->rankkutrl->setting('hideempty', + __('Hide no followed links'),0,'check' + ); + $w->rankkutrl->setting('homeonly', + __('Home page only'),1,'check' + ); + } + + public static function publicShorten($w) + { + global $core; + $s = $core->blog->settings->kUtRL; + + if (!$s->kutrl_active + || !$s->kutrl_srv_local_public + || $w->homeonly && $core->url->type != 'default' + || $core->url->type == 'kutrl') return; + + $hmf = hmfKutrl::create(); + $hmfp = hmfKutrl::protect($hmf); + + return + '
'. + ($w->title ? '

'.html::escapeHTML($w->title).'

' : ''). + '
'. + '

'. + '

'. + '

'. + form::hidden('hmfp',$hmfp). + $core->formNonce(). + '

'. + '
'. + '
'; + } + + public static function publicRank($w) + { + global $core; + $s = $core->blog->settings->kUtRL; + + if (!$s->kutrl_active + || $w->homeonly && $core->url->type != 'default') return; + + $type = in_array($w->type,array('localnormal','localmix','localcustom')) ? + "AND kut_type ='".$w->type."' " : + "AND kut_type ".$core->con->in(array('localnormal','localmix','localcustom'))." "; + + $hide = (boolean) $w->hideempty ? 'AND kut_counter > 0 ' : ''; + + $more = ''; + if ($w->type == 'localmix' && '' != $w->mixprefix) + { + $more = "AND kut_hash LIKE '".$core->con->escape($w->mixprefix)."%' "; + } + + $order = ($w->sortby && in_array($w->sortby,array('kut_dt','kut_counter','kut_hash'))) ? + $w->sortby.' ' : 'kut_dt '; + + $order .= $w->sort == 'desc' ? 'DESC' : 'ASC'; + + $limit = $core->con->limit(abs((integer) $w->limit)); + + $rs = $core->con->select( + 'SELECT kut_counter, kut_hash '. + "FROM ".$core->prefix."kutrl ". + "WHERE blog_id='".$core->con->escape($core->blog->id)."' ". + "AND kut_service = 'local' ". + $type.$hide.$more.'ORDER BY '.$order.$limit + ); + + if ($rs->isEmpty()) return; + + $content = ''; + $i = 0; + + while($rs->fetch()) + { + $i++; + $rank = ''.$i.''; + + $hash = $rs->kut_hash; + $url = $core->blog->url.$core->url->getBase('kutrl').'/'.$hash; + $cut_len = - abs((integer) $w->urllen); + + if (strlen($url) > $cut_len) + { + $url = '...'.substr($url,$cut_len); + } +/* + if (strlen($hash) > $cut_len) + { + $url = '...'.substr($hash,$cut_len); + } +//*/ + if ($rs->kut_counter == 0) + { + $counttext = __('never followed'); + } + elseif ($rs->kut_counter == 1) + { + $counttext = __('followed one time'); + } + else + { + $counttext = sprintf(__('followed %s times'),$rs->kut_counter); + } + + $content .= + '
  • '. + str_replace( + array('%rank%','%hash%','%url%','%count%','%counttext%'), + array($rank,$hash,$url,$rs->kut_counter,$counttext), + $w->text + ). + '
  • '; + + } + + if (!$content) return; + + return + '
    '. + ($w->title ? '

    '.html::escapeHTML($w->title).'

    ' : ''). + '
      '.$content.'
    '. + '
    '; + } +} +?> \ No newline at end of file diff --git a/default-templates/img/broken-link.png b/default-templates/img/broken-link.png new file mode 100644 index 0000000000000000000000000000000000000000..8fb604694e44c1abba1e013f46917bb08e45c5ce GIT binary patch literal 23613 zcmV*mKuN!eP)pF8FWQhbW?9;ba!ELWdKlNX>N2bPDNB8 zb~7$DE;UJt{NMlpTe?X^K~#8N?VSaJ0Er5z4z?1XV1)!dl z;rGjzFNf2g!|Hk>urz@c2&_P01p+G&Sb@L_1Xduh0-?GB?;hE0Ay?p6UH^GJ9tRfzU|sKbXBSrccn_@bp;D$3mC{lzdIh{1=uCS*syWwqXPzZ?AxYoJy*F( zF;^19D45OV%9Y+_$&xZ)AP@2WcxZ2Z$Ii83)~tOsU*CT1`nPIb*E5Wgd1M$_UCx{s zMk<#sT@sffMZ$oAJhZ#yA-(Z6YhQb`N6(gho42SX!zgNoQJ{cl7@0E%8%FZv30=N? zSpo*~knWR*?zUI1n)7JKPL29EZ&B0Lu2a^PDVyIyDc>;Crj^H#Q6x(i-{sDe$qgCO z&Aoegd%!>*x}EZn-SFF6SB2@>t5t#SJ(|10L%O(`vqrhO^TxRzJzKZ}GLUrXl6r=b zG=Y2RrSxv#z|QXQksa>KFHgF!zB&~!kcVu)JXE*4e*NOG3a186oHWQCI<(E5`SOHq zdJJI7)FG}=q3kYclK3ut`ed$im!@w2{>|>IuRnBO>-y@e58atFCjtiYQ0&|%$NhK$Kwh78Hwy!jK{=bwMz zzWw%NGmbC6I_bXn;<)?t)As@f@(}HrhvrtNP8$|+;^aX)jvwFee*5ifcj?kOciH{r zE?++De*gVz_vY$_E^po}E@i633aL7|Pd+*7zW@Fc&p6~|KKtx_clz`ZckEx%&t#AJ?8%W^G6xQC9&3>4CI3T{JXrzao4y>b(b(vY}c|?UH9?F zhuu#~0|xSt?3;(;HYZIU9C7Zv2|GUj;sba7 z{4Zt}{;$jKy!-v^*KW|@t}a2sST0ZA%x>eRW$x#nzi>bN@Tuh{pL}x6ojiHaz5l^p zcjV|!_s*fM0Rwp`cFsd^lR~E+pEP;MzRx~?FUUBq*oOXa(OtOkn_ISgmP?)@kxP~O zMK@#SXv;Z%{OL0#<`~COcjDv$^BRZV-RAb~-{f}gS`#pkhhXnK@VBUhJlyaRJ;t!X z&z<|}D$juzx$OQlZ;&HLdKdk z19{+g&qHv7z5BGOx_i%h_v^1;U1J=W$3=OHPh6=o1>BQQMR93V5Zt+It%X+Ke)Ez0 zL^%if2GH-Gy}^c|!lIixZD_zi9)kVzK;L5j0Ucv5e07RYsMGG!^}YMJqxP{sdnvNYdOjC6|>yrDMMVXT4fy7^#^+K z0ypZS4Zup2^E@sT=E^5?BF0Z&%gjyu!pMU=T730{k zbG2K$?lpOgVXj2Ue9BW28euV0rU0Aw!QMgl+1>f7BDearJ9K=x&Zf;PEL^&L`NCD! zaoJsxH<;^^CXMH!M2YBP#d=N#FkIo*X{)FM`vS+k~b(d0QI zMULPqR4C$3efW+g<i_Kfs zN4lqOI`C!g{}uSBUf_1`SghQMpiDph__@5tpF!S(y2oYx+Zk702Jw`V@<@>)X!Aei zmMooNur3Bgon!Z&b#C>Vg|2C{S}xj?kzGR7MzZHf`$@U-#agdkvntB%ey_mK1y^}PFUE^3$CHH@w|LZr`yH9)f{PRznaYTslxM=M!S%n-L3G|Wu z2R0jo+pT*`g?3a=@bi0L9L@4z@BMS4Qa-QjxehwikBfrk(8aBsc6(uu1j4-?fr z{Y+GsJ7>nFTeokC_;2q!@biHcxaAeNQ${j&T>mHQ)vxg0w(V~khw9?RUyO)|fqeAQ zQBg>;xM z3WU}Q+zBtzv{{V?&6?M=Q0doSzjT)_{;rR?;LiQ|o9ooMseAhAXck&M@x){9@yElv zCQWNuVt)FQBdT|7b0$EQz(sa3no zQgsNLM>u=-TZL7>n+I9DZmCO@IF3QO5h6Zr#(^hU_4<5+b&sFeXWS|g8iiD)ka0XM zS)bgKCr=%fcG@drQ_o*8Bk8*JZ^Sry>`?gs^d|0 z&Yk<-dOUvm>5MC0JdcG;zV`?pKAcM}QsSu(4_Si#?%{0;t!7H3A+L)a=XsYTX?)kI zOB2_nYf~lb&D2rYNF1&Du2t*W?@9t_f5%QumNaNsx!Uc1*TByOR^YC$K$E7`(w8cg z&&{4Q&gdfN&(rVmyO_CS%_I1UIo>1uW8qxGhEeZ_t18MB~4`}BG4Q%fQ^l$5W^=j!lb!rr#n%woL9QeT7UV&P5%FZiV z^d$%P>c<~HGXx7yk|SpZg;RJBCFaUK@E(yOMsTmp7_HXX58Sb1yIsEl9mK6lYN{nE zQ@yC%q`G8%GP^WsUNl-sxpD$PwM7Y|<%UAh-Rr0zY_HSKz>b zZI72PUuYMb8Uuk|LXY|E+2ch-jAq^gy64jf59YIv{)`x)>FG= zDH6D5&1<`H{qoBh>o+f4Bv+94@Q|y=6k0v{=%Y%+Q(7T$*RJ)hu0p7) z)k>O*35GLaVt?1ZeFK*#Pe!RHCNPOn^mqr$drY4;MAVa7P8wQ5yXuCnf!rzV`ya1r z;0NyO6*zwKP&kQ6$JY3P$?e)VINYpx4TV1qO&T;)rW9_-(5_Yk*|&d-ixKl#L#{mU z;e}TiNX1IU%|MXzY1pu`ZTkFDPz32}-m<29NlJ=H{-jEk$b_nUD(9Fsb+Gk-bXM|? zH)+(kYUmosef?AdU;aN|f!(_|hFiCO(Q|$Jwwc0)ez{R)H*io#w{80y>d_dc6F-w_ z=C%~MaOJAGs(UoICaB1fBL;bo@Ge|93?!`8L`ID4DPe{>Zt~=T;${^ROE#}E0>rh*qFrlBDKYzR#Ul%3pUAr~Y&kYI~$bY^o0$<>fUAxzZUAby@ zBq2#78@*h`RjXb?$|F_W$Wc9<=5<)UF=}*QDIccOiJ#b@+Uc(hH%fX`OU{GCE8jqB z*C}USXTgFAlJ@CnIY#W*&s$$e+_*8U8445&`O&)d%DY*!N4bfU2D)zDnz(-b+A6mh z=_V-@3p}3xXay9GJ{t0)zxnpdFdrO$H{7mW8y?%Rae1_*%VxxwGj~kP`3ojI_1dDT zr4}!FWzga!(+4bB`pSSsi>D2qzhL5!88b%>(nB3GYIN`6lO_)uqKDjn{=)GC=FT0{ zPa7zTuE#cRTyf8i5fk(pcK(9#1^e`EyRAXPN;a^DT!7@cM$J;Ly?V#z%pLFEdw-80 z*iM#YBPyLPeNxpz`n%^uNQ@FSWO5$fya$p#bPldqIa`IoxeB53yCTJMs-y5pd7Q{5 zR-HyQk_;J=>2pfEVZ*yCFB#yv_h_N_?dN9C8sP>EXs@oqvH=76kL-hy!{K#>+q7wU z#K}_z_n$U>_{hOSI=g=T+Z&2P>`dXRS@T*JL(>9UMH>e=5=HW&df<<6Z+#k)Ex=rxo2dI~i_#dqn_CUfP=7j{oQ z9o5*lkP<qO7LHjUTGH)e>((rJR(%PTB{r~Mp1{?qTh36GT)8rs z;&_f6X)Hd@nk|)~ENl)ERA>^wlIY12j}s~YRi;jz_{zoK`FDhwGi6d|T;5D}G81x) z9NEK7n?7{V#EJa}sOd-|YvaS-twq@K)~Ya5rVcLHrE3#23=FwW-Lj^(&M1N+s&P=0 zpomBFQl-kp+&FD;D&PZ@ixe)B)4iyFPn9yUN%sU-&L21QDpRIZPGm-7>q2R#$GmXi zmt5v7sZFdQQe>}Af>EVTlf?Du(^`2-cQYZLgK zpcPP$^y3mzt|Xzd9mh``bk(YrH6$fv%0xD>g)4dT1TMKY2^$5~aZXJTmlu=7b4gx| zXAIRBbtM)eL!!y4nJ8f#HT}fCawXEA6D8vRWLG zJQ#=2omh{+dtS2wZ@s-COz{%=l4Z@BYGTh`t#=)gZ~$^M^fSOTojY%e%bzd1OAtSn zcg2gPFd(KOE+mv(apJsi<>K$W4ns*MgF<6Ar8Z778NiD&q(u7n1TqjfN1!#aVm)sO zD(}I2(tB-&BZEtyE}1J|zKBsR#*FQ~Q&r>@-V%&j(G;^7@YWR+txSng{sv` zTTLTZ?u<6d;MLbA8=ZtcbG!$F)Fkn-Vn1iI2FX9-X;l4|XPc(7jU`sxc$wVyq^*85hleSde z&{=N3uUMzU#W|X&R?*pR?b^j|+qTt`=ij2!ako2l>K&7;L74+Z52R~2H4hwo%kA7L zZKt!@@xNr`IW)&?2?bWM=ItW|4LDe~%fczXoi zzD=8kQH9X-BoW1ZXOP?GWa9L$Tep%s_|99V<~M%AAc-yipzC|PV5)Nc@yEBKb$@f^ zLY;+Xo+=TA`Id9ksaxJE;JK8fqoqriAg4OH z82LoK`W5x}Bq)!#V#RaH`}Gk8WwOwyfeKqoy0|j@DpgAuQ&-{+f{c5b#{eC(1YBO_ z?}p)pw|KReU#_ZQJ7WR{ayLx4cJ1gm2*jTq9e`8^+JqiTug;0%)TKR#8plbMXWjj?HHLhYCXwRNa zR}9265-vQ-kM_IgQLb+=*BS~EoFP35M&f&vuhpCF8w!0AAAS6;b>|W8?b*9t1+%e2 zl`6PILW}6vCU%b*E4t8~LauAqChxat*D$aDKotsw*XAoJ!`NN7URld6`uA^Vgg=UH zjb;2mrEFZhKo+4%ojNs9uJDE#E_C$`8&;ZU$RtB7T0FOmD3Lr_^dQ;@)RFL(M>TFz z%|@kBC?s@}$b#N4jTsOK9>xNc%hcLgvqsC%hPsX&8(WBlVZg%zC8RhQGRMG^gA8`; z)K~_)*4V$FDRkuk35-9ui>5nj%)khWp;zWp-2%!35Beq1w2KtYsnffZjr{Vxhi4dG zxD_(g3h7Bg8pYKi7opcTG7vBHx;pIoRFa#g4IJ=7uV9aYp`0-3|2;OmlAiBj!+R>k z$!v!5)YDPq0W*k((deW?#+jmG3~JuI<{k_j_;}dxu5O}=Y{Q3jk)hOdMU*G7*#Q$s zmZ{5Z-m=1dFT;SQ&b@MPJ$khBUMB;hQtsC@FbwYsXJ{UEA3Oub0(-Ye(Oe3x@(A7y zub+*hkg>7N9>i-vWv8)<9HWpzDGVcfb}#wHn0PIaEP!jOqSx% zAmP7@TO{yNqDTxaUObn@;e;A5C|RaAp-$ZjDp+-wYhPgvK5XD`r5%gj><>SDVmSpD z`2`o7-oO06|MZiWe^?G8Nq_%myfDlA`wu^SZh!W)X+s*yac`=UsNrMctt+eIoL3S@+t8KM&|oft6o|t*(~!I{$cXH)|&R*k53=A@Cr06 zuxsz)3Mj^%RX~hX3SuO|oM2RSQ|tFim5S-#b6Dcake7M$C+cKdVyCVu&yvV|(^Sd9 z`I%ioa*qhX;Uhb3GkZeSO-_gan-!FcP>RV)x@946qK0rta6`Z(!=+G{f{FV^ciH*noq)d@m$WuVTrrII-h(#M+1`iROx=1_q+#`}QAEOq4eP>$38QLvB;_fYw28~urYCeG z9Hnpw@|Hv!yYI;&DEN{f%zbJ%{=<`>k=t->O`L*h)mNDA;;t?L#=&u`HDHh{ue2Zjwdo*WZNw1 z6iz_6YbcHq2`6+5PHd>y)CzivYXme3okYVb-2f5t1`Y14tBYt8U9_1y1kqMn>;Kgy zH@de%kntv%_RgWV%?Q5y@|1Zi|GJXM>s0kek$rG{pN$50eEep|Pqr#w9#M{Kes$QPew7BO(buqDnF|jop6OdiN}NF zZ+MR=VzS1F`J5%hb(DyY8$Ups{&m$=4q9l37x6|QpR~*wuQg!`&w+8^HSx}PZ0a$0YkvejM=Kzn(JNWSHG)OTL!}UyCT&Vf zMjv@33=bp3@BH~zF8}93zBY^*8F4%pi7p}rUR)pc%KP&7*s6P!uTWI<@sTzsgHQ}Z z@k1^vMoLqPlb#7oc#g+q7m+O3UHH5tY`+f98PuhO7lMJu+_+fll7 zKFfE=GvKOuhT%czumnK4psRq+g)|Jj#?L>WvD^;N$+&02Ef2{f@7ij`#toa6%CpZ5 z7|7kS+>M)-J|+QW70}9C0gD1xsZs^R#LZ?`-h8lfvlve(cb?43Q8E~eNj)Hy68WU! zWI+*$9Vdpx?05^pF;E{idf2cS#Uu9n6A>OW_!EZf6Y@x&1ZM=jKB6&NpL_l(OLn24 z5Yv+gKd0J9R-N)gw2>)XQsKYaNSNVw&^!rnuac#NZB zrNXXk*#efwQH{qNllx#4aMAMS&1&>@!opvFJ#(doM2#q9uJPqpC$5@ki}B+*Fb=Bu z4AL1sqIP4`2gNRbsnX=Aggy|Bu= zEVPOM3-xik{BR4A0>Z1OpJ8)9W4t8L9l|auJ7Vgx?!iw%vNYsU2if%pX@jPl$)s z6kr3qt|{sO#4rF1IK`pCU>FRl!7!lm)7IL)!^=i@@7ArY+bhGsW4x~MXLR-f1^@(9 ztybJZ(kfLwIy?v!^$ei$7ZpY&Oc2|08*;6K@4W4Pk(c=6kMFO9Q+N;?^{jA;ft?_B zgi|1C9$L56$c@xzK-};g{nZbWSnqwO_s2v1{)Y~457V`K>q2eXHnzon^2w-r*kxRU z232i(kw@!}V_Hpue2=Io_n~rxlkwumKAb3Vf}Kf{ytwYg7ZbmkFkyl>6C_BmE`I!Y zn_|a~y*_sASexR;jk7yWoY?!~#EyM1X3Q9eV#a*oaO^lSkLhQR#n%-#Ud-bf9`l7Z z{I8z3$}6}un~XvgXBFJ2zkm+)>(|Eh?F$L8o;3&+E0)_hP8l;Mvx+w-H{lW7s4a%5 zy>4_6dMG&A39}L2qxMcg zm0Sa_0UQp*3{r=Ye=H;S3!{k1kykN92d}|>L3*SYhvH@t9VmBv1K$aK);s>CL*WA! zEuI~*S+iPYM6Bzl6E9o7d|9%WDN`_Q!$vg{cJ9(T#^53So|-ysVx*O;mV~F$9tt0M z-M5%AV|twIIWkwsnKSb=^@n^8f(BzXcAV#|s!v};&6;H)CyEa>z)ctZTm2%6`FjdU zs-ZalCG!$&PEP$kGA6q1Iw&NfNsImsjP6OP8a^$6fPANZ{Thapd85r1nPK29I(2Sr zhEYwSRJZQ!uY^)7bYc$>5=Z!nH^Cr4$Qn1PYQ{oBj^F~h8;LhHmaNKsFgA*OY<`NU z&~uO@B?MV#RXw7SFZGy*Dy}Nv~iX5kb9tmi$P!*FlY&r{7{M<13G&@QB&Y=k(iT& zw`^70@{(rFtD6TYCX@OwPp<5j_Z=s0=<9f=J5dj0a2lw$E`AR2F91bTD zg4SwtwFcw3bWzmXSSKMyfT!rtv5_f>;1%c< z0i~j8i5Y`<42+WE<7&#PTerSxwUAJM7y)gpP+!u2{f4Jb9es4{xL)nqfSkbmaHn*IJaEiPpnG${s zBemX}Lg7pi5b*-!6&_~|ukr0wc?PH(2{=>{3KX_+pojur3#Ah%^;F`~ z{B6{zx_ez}@O$=dw4|NuUoU>hH+Wd{jZj_0JMd%6BI$CpNWFuXR7PZVf;W@~2C^puuS4qY@%@Td` zCX#j#H}tqd<#G6i5ipQYSld02(=G2Bf#pWdWCFB{UWE9OO^`pDdqkD*=-qvLl zs)q0Z;TB>DK+0aBF`NwH8SCdrDh`yv(lP>Ccd6&Vo<;Pajc6f2#{6uCjH#8(caTKr zNaY_*4DF&v1S1)(5&}q8o}Wd+^Op& z;2w8ulWlMegVxtJ#ing38b}jMl7+GbH7G^2P3|{MQ3%99sH+f? zP$PjZQ&L(}RNvuuAx(tfLDi}y%^=``u_0-iqR=NNNpyVFSgeTm>3iu0LT_t8W9IT0tkxa zFN9U}!NcmMo&-|?Pf6$qa@Dn4bCVvOEpUSG3w=J+9!B7e?!6Td%XS!^gT4qn0EiF` zMSe0&E<%lj9tlowde!~$0U%M2Jw})%B@l|1B;P`)Na867vavH|POZ4TnnARkg;W6x z_fS7OAr&wxG!?=h3UctRfKmrZzK7ypX5}`hlR$OJBxH(eI(3%sf@tv`$&sEB&oF>( zDfH3oMfB%n;zxYGWdLSw5Z{bctiFCiAyPxB=>hc3Xidfb$@Z?88hhn z(D#mqy!W1J;2VX?3Je_7Iis-rPfC@}YwTfIu5gw>X$Ub$h(T^>112FR;if5yTm;%V zy%-nTHqi$c>-QloFYG2gtxg$uBMe>JZGI_2jXULzBRp#=QnV37)s8nV{p zN#ScPSUAy`&OpX6eKA<+ZGETu)AB4?)2$Ja^HcI8ax3oBax8%g+#jq23ZBq>DhVi{ zijS8Im4|V^zscmuqaSO~pvF`8`+bD`{!N?LTv@twej`Ov7^IG2ecOulp_9OZtzJ__ zIVH`YP1G7pmrI@j`Ji+Xm!MXH3Ja$%HInib3hT8+%nPAjxOC|(JH?@kpA+PI@!~m# zG26u1;9nQr#Y<<6m`JE&+FnZL>Ft;<82LRJ4iK$Ja{R^6sk4935lI66sfvK7tf|Q( z6dthZJVi*qmO+t^UUrmC5QPAdYOX|FmG^HeR4h8ZS+fSI{B>EpWMQ=8#q*XfR48Zv z0tIqx&X_61arNAPPaOv}$5IMI;dzxQlmA0eX5N>m##;~4p19$?j33{3;JC4Us_Jw} zIC504=cNZ5MQBQ5sbORkq!(uFxIRxwUuoFfc{3h}0glU-&5co^LSd_#XUUq%s3Sn9 z>7{84PF0U#d#o$MP5ojf6ZD`lwa>{`)uPIY`3&Ji|}Gq1^iwa16tPY}Z?# z#+tRSsU|YT`e8uEsO{htRf+#rM9JALTQ*92E1UbkfqhSG+Pps2ij_+fuUxhC#qB$` z#yEBA)E#3s+{$;ltFM_cYgEjE1KYpfRb6isr$AB0jqfduq!FfdtHm9y5Me76>WUQ$ zpVaC2swDRZh)H^1&CU*4f70;Dg6Q^2s7@R5MN5V``ANpvNWi--wV7IrVxA*MdQZ+% zbP@PjP(3L8VN}#f(EcU4pEP-h;{GGr%uJQNa6^L#`5%{LJYT5qee{?<)|U>0n2I&^ z2s{JHx8H&roPg7~L;{XX5y~rcHEt7RyCJB^_o|b4w~a*rs-?)thyzvWzpYrY*zAT4 zYi7Kw8|;4nF!d1(pg0ARL&6G;0=Y>b&hg`Vs}rw-6{-4(V}wKv)nEQTbV%oYQU$Ck zVOFiHYM)>RvjbqxF?OZVZ9`3NAu0 zBtp>*KJ(@2pwQ`-?7}?C?`MBfUhtYwqas#RWQHKLdMQg<4>%ze;grvtvILw$Ur^(c z*U9L$`;y>eR7;jlH!>r6i{E7_gZrj@g(3?ZHLjcGe&4Hi{rk_EH#w#%rF+4`E678j zZUL-`z}Rf*vZBvANW{Bgf<1@p+O=z4&|sFvMtNXU(L3I_Nxl1O*u3KB1>_vGn$kl; zxCF-uvw>83oEvS=54^9Wqs(gKu8+8`A4VU|G(kWwdQj9@ET9Gw<7~?=z^pdMc{#m!ijm=Ye6+ zaag-{nQxS1EUaC-@=JGpV?8Jz*r;*!k~M3Uw$@GxT7(SfBw-u~BhOZ4b+n3R=s0Z^ zjT^mOl1}K;RCh@#h5avEyLRW<&(H*8s)`qGq4MD`boN}NxB&X@p zC13k*zsKGF`JzSh48b!H^Fs+iXbl5Efn<^7J?Z8{r39&=?AbF&4McPc$)F;n@b0_Y zE$ks_zH|ww_MFUsL_FA7E?zvZO@2nF{APKY9wrYOQ}q#s(?p39!NxIT5Y!y}sk5{d zqm)S42E*0}g*uS@7F-}(3WRhjE?+TQc}s6gy16Ji)~#FqYvmY=s#hzQ=Weg(1NnK2 zm(G1gRnw1<&%rBjrB+NHH5zp6$Mol9BHcx|9_`t4##?(SE}n{P<0iEX_Cx+YOU6_! z?zaU}n{GLXei#N-@yOb=Y+k$r6JA%ZdDX%f!kE4LHdzDFr=K0OHGta$LPZ^dJmK8Ib0N2n8!)^$Uy+V}k|E+Tkq-qptN96dHZQ z@GI95BSsJ%rMNXHflNU^r^rW+h`tpvKNtswQNKZjuPat6wya{Mk_GR#^}jE^ub|H5 z&0EwqB;@1M?<$V1V?;YnIEFrfF-_r(og^Ccz6 zxtYp1Rbsz#%qAu;VB|qM6${bQVjdp?Z3tRZcYwx0jRD~W#wE}W3|EPcLGlWZt?LVK zOFeJl4b1etg(nm0NrVP_i|ZQ)vPM9?pn~*w^i>ZC6?qIJs7dN8p;CJ)RVr2Vep%!D z_ItK!T`#xj0&?XG%quKgKHJ!-Gz9T*d7%Eu*11bF3oTBcK5E3bp~HGuQ7dQ8bb?2- zExd0RL0|l-r=!i}bSIqj#pp6>JOH65f^Q_(I<1Xv!n_a^61~Z>@)R-%F1&>`Q3WO4 zer|ES5bAnf!~ZuvpHPhV1X+U_O!BQUYoiK#0u3u@`{!% z>sv=0SUeAy(~c93uwd@IiB`EKX?#bc(Q4H$ZzpSZ4bhawQiuEM#;^IZHEUIUS|R0D z?|vWgj3lfl2#wYj%}%M+*i^4xMIn4;Od}XW2x$<$3=wPPhfg;Oq5hwN@PBw4`rye! z)Fbkh_*lCuR4Q5QeqQ7I?z`{YxjBM(NV^d=qyB+f9`QEron+N#tMb6uDC8WUOZ$mL zm83COu1rF(a+n7xUOZo^`)&cQ`N9bkCQPJIXm`j>&7Z9g{n@A%LUCviF4?6)m*GWo z_rkJZLFt=q{^0))8De>VCf|!5{V7d#$_~~2n<`aHF05XoeE$1=ZEx%Q|JTh^9Xh=H z60`}5T^}f?0I31JLRFB2k_Yc^7K8<$G?1X9jt-rHT!qSW?mQXJ%$PYP#%(RaJ@!Lc zvu2JHAwq;Vd@q73W47$6jhsjqAPKjhL)@-+xSj!FXf#_P4Wyp$cU9nP)VFW9B=^|z z+?}uEljZ(v1Cb<5fAoxCeBu>aw{BnwC+LkAj|b-;l`566v zU*;osXXWnBrz~H-^wH;^f3BH6?Poj9RRo2jMJ!Zs$&=*C}O0m!z)h8 zK7HC>k-X>i?b%eKN#>ayh@2|5mVyOyth@(q_ul#n*OUCYn;xZa-}adpqQZ;}I@p*~K^rOX zCe`zh;T56;Xe^;Ui>INYTwD{3Xt_+Tp5+uNUu=GFt#0Uko0Thwat!B6j9cxI%pfZl?dGzNrRzL+s4W6&6 ziA^nf^=UIl(wQq;wXVHhtkR9rQrfI0olU~Iwu8HB^N>lRx<%2N*Xs>vZw!UheiSfBI9nxHgtWN$H9 zYwDptX+7D9F?W!D6r3Q0*2D|4W+Dn~$llPB%I`>LLX(h6$j2H5u%&b7W+h*LeR0$~ z@eD%iJ?q!6978?oT_hcP3a%D&LGt9uVtp3ODWO*6*E=u}-;;#QO8`5N(LvA+y<3>U z!0L1)s&D;pzg+Sr&b|2E6{5g4q?zD=&GGmlMw#@hT$%z!gLWWmJ zysTU`*GP)A+%l&kkJSBWQBAK-yao-bNl$jY2zATc{Dl+LH_^#v=h10LjfH-1#0==b z0}{rQAjGCNT@z!|II+)b)G9M_|AAeP-^qKvmG_=LW87n!K-=J@m(re~?tzl~3o?*w zLa-R916dPEQM`g@5FRZhTsRqujcW^rad{2&l=%xL1r5d7V#G!fLrp@cVuhj$Zsj`# zUh^NUK#dw@mZRK($T8h=Yy_X27_xfKNyw(yC)5gou||@KsiNj6_(*gdBCpxIPX}w> zq1|(Wn5Vv;YuXl)7{T;-$devz+@yZ||KeT)pLq)_pi1p7)cVka9XPm~Jx~f(gjbx5A@h$S znxe$)@z!nXSS*dw2?{3})F4G=dEWd+} zD-{Ew?|Y~`4zt4<{!+4JzD~FB^+WX~O`Fw>C#lf|oa}Svjx&|RapMQsDUZ4dd?=5X z<}nK%ebl3s0Da?a{b8T{8;49 zBpIX9Aoii>#K1^I*r06VNkDCAs=`nZXdX36!pu?(L2;0rgalp*{dZ-{70VaOw|m{U z6HpLcT&H%-7-uun3q!cc=w-skHfg|s!_lm81EvEyczJ*(IEFp(t#q(JGBbkKNfGkPKk<397hu9rM$tW5M0tXlb z9wfIa#&|6*s2hk2Oq|%yO`kT{6x5l0QMX<>s>^vm%|@s8Lm`hWpiX)Vo85 zSFl(Si9?Z(j-Q=>ymM2H9Uj0ha z!7VL8=r^QmbwbJXLd&zr&ETdnn~-@SGiQym-gHE!{V~ZXj_^JdQE3-|m9TrydJ}sX zKB9+(lWJ-HvUrJnokRPc0`C_}E3k9t#t5Uw_Rb?Ou$qm|sh%uZJhh58vF0f951*VE z(m;|(E2^qgyXk|5y<59Zd7HR|QCVWIx(QyRm_(i#830#_S$rhquQFCQV{{UEjWK+%$z+=*7}!jkaykqIoBU(yc$TZQHuAUAi?((5-v(xp+X7 zLD||%| z1M@GafB4N&AxU_|+=wy286u`;>~CR-Gpno3Q8Yu^AJ#8WFX0-ksmOAsPMt`v%VA0< z)C`z2QN2b9GX|7Ti0}FQ?CRl8nlypB4@K9 z7;I{^ZcFS9TSYbA6v;WtCjB+80Np8Mjxa1wC>jB` z56x@vz93b24JbHhilIw|ap+(;(x_4Ghk}L^8qe#ly~pOwt0IpY-6x+;%r#6Z{aQL! z=gvvo?D7TaIQ?of@h}hs(opb2ARxGV(4$DC(n;U5XKULWJ9ezGVqrlQ{i?|ukbl5Q zqHn~Kbum*h80g>(SD|{&TnoHS+jb3XTrjx@MuZ{YNub%6*6VUBfoEQZau5A`3PZDU z=g#oW>NTsP-0p()>D%rZ$)ZkVV9hB_ZAEzm@#tO3Etn?z+M=2A0^dlU=Qo8~??}Y0 zi2)86JoyIDwj@c_Co|TNDAW!8kP1z(&%f{uW0!?o&0Lm9?TGK`B?lrPc#E3PCviSh8KHO>c+Jk#UDHaazn@jiiP(; zjF~zK`A1pFZRXVAOT?c+yP$njSR>Y_@;-dzkof;}iF);J{glGAhN?dtK6Y%kZR(2` z|IkT)N}h1EP&hG?&wL_fs`p>jbpGj@Jru8h14fg=qm2sTcl%-GHTW`IojeSyv zp%FEI{cieJRjQP*4nE**axlCGLeJ>%GLb1{p24K1kAsvXL8stT5qiF?*JI4whYF0} zRIXfNQqyJ)Kq=Ic3#pPl7%dE(SCmKDbzrmahR48N)cv}4&2|Bb3pkNFI zG>b_^gjFzI(fMUS3%Lh*2a}nQ_(aYWev?1fPjg2k^_gB_UIa`2;XGx}mRg@VJm$@o zZ~FAHkI5_5)(Ej<&^Qou`~B>97E1nc?so~gu@;_QE?)HXC+{owXk);FsgUS1V<{|z zGd4+kF3J-p_uE=9*oNs)^@Vy78lp#Bt5)f+Wf-$1+MWJp-#768 zcWDKdE}Qj~La8FEL9C%5_1zW2U}Dj=zt|v`++P>|P*2AOg+LXpPQmCb!Wt$kF`EvG z1>wy&C7B2|GZy&JyUd!{XeT=|K%D%Xn1p8ZLl9QcvrShbQ}kmd^+slhWbWTi)7mL)yu@bt}WlFwzTwT8>;QJt{x^ z_^E|cCJ}w<>X{tB{QA9A>ZjV^ONyf88hjR0TiZze92Nj{8R{G8etjim>a)+^Gboq> zA~XHyn`zmyu7L|okE)>=8-j*s)U8`3FiPMq?faX4%z}kepB**2R~t!#o?`I(ci*3u z$3PP{IL8P!56>_zTsW&xX`N2=@-B`VpWqZdt-J#yiGrQ9Ts2@arR&)FWvRFKRKaYd zVfcI1CxUTIn?BM!4M-P^Tr`?65ajw3CW>PNAfadgZ7x_aL505|lHVL+XcV3X2~Nfe zkOV`i$8%sD7(_h12k#4#CQ7-zhGbv;6Yt+>F~r$QqmX8qKnNQY!|+Q3Ag(j0=C2Fq zgf4B8cPlFu^=Hk)k@L}LSBWFS?9S1@n!7_e{O5G>xgOH<`0X@cf} zl=-?!m6B7sb#I^irVlLeg1fT!|-)sp-d3=aIypCvN@@Z^zPltRNPm-KF8whH&?%EVFX;Q=arP#Q38(g zIeqBREGAAGD51HTMx`LJoi%%$F;?3uX~l@BqaX}ztk@vqFlkPm;)GMcsKow|0C`Ue zjLiH~3va(`SGH1>k}>4%hIG+L0Fbrg$M?G*RgbuM$@3ciMM%Z8rjJh_Qy$aK?#&Z+ zi)7&xGO2jXAz~9VNfr=-q#L6Eu|gjVd81WYgWh5q;4ugRHBjiw>eWlXR;yO!tas+LlrZpPSP!XONTLLj+9=`uw` z@ffOxpX`$wBSK&ta&k zU?>`yoWoz76)P7%nlE40l2xh}JI!a3Br~Xz!X9Xpa~FfW#|6the)#cAGgLYxqYAPm zPr_r-|ILt1AXsV@Z?2wi-IREWgPJ%?9pf$0MM%;ycG_Vf9@4|2@eYToRVy2)XWVqh z-lZ3a77=sY`2L;Lhw&kC@%JiBoj;FZ+}LCMssDdlXic^1rH!7A$MBWPF_5xli|WK5 zDjIi5Pa0FFHgpkQ%SPr@2lC9O(5s@Rs@9c)V1DHrp|I}+*7Y|O!fyXxd$W-&b#=XM<$1@&u(u};0N-I~aAB*0J) zE?Tt6G=6jE&XPV`_Ou&Xi7?4HGK_TP%6T@(l35&=FaN1E`O`)y{~fkvqRW>rbhL7nQVo?4 zMZ8Oo{{H@$zWq965u})L%9If)R<2wa=fHuTkwWv1w83Iem@uG==2)LP5i~NGQ0ecX zl;JYEE|^Ezw0V`PwxukTiV`(gV*d%Lla#MeQgbebpqya??n5^9`1t_yD;U}mTck=c7+0J~c?s`B-!@vd zB+=9kzElkX(K!r$hejx*OqnD|;`0$}U+t*2T_`B=wKgC1EL}Z5nqWkHH{=XJrNmT3K9k+2C9=7gDUAg zWr$0ySjNb)OFFeNQs#XFf7TWX_M%266H-wCWYQ}YXC^RFQ-GR5Edg4_)M>+AM2!RB zJFuBsiO@$85u+j;BHSyfN;_-aUcK%^G!v0(JarhjQBXrJYoZhA-H4HW%#cGWsdHaY zMnLJ>sl|7?LO@O8HBSM2t#wwX-vrlBhCyRf9EF+?5{s9}^=+9l1z%OaMw+{}sUD;c zDytYBItW}zS805$a#R)i64|0%3?uvl7Q!}#rv}w~Y=YfNJZa5-Y#kg2%r0J$9 zMyE~$8OC3K%Q5@}6r{>Cj5TYQ$TJiL4!vR+k84wKYF4dU&P|#$OjYl~Hj~0Lj2mB| zNYLGZTX`J~Kvc+cOPrvml6DZOZ6e?SfBx~u_wpEoHGZgfNvHP#3&#jcIDzZbsUT9{ z4jW6xWL6t4quhzS0oe3_=;7o?J}DB#l&+eX3)}ZuL9e_ItY9knJv2eLo#-0~925~K zK#gVf>ZSJHt6pDVd4}Ht3sg*=*)! z?cI8`=sJ4LfW-c1uUt7ln#O0fHIJc90||~$>hD7!F%Hi#&ML-Uy?T*RG&tQY#|VM!A_`_#TBqADhdDSibPAXdmfaesPCfBPna+$s8IL2g+WYbg{I+okHgj{0m?;=!skbe7R5$MH*HqU z&@2$L%JLd@6-rUC1X)8xgg$t>^ybW&WaE!JXaHt(g-_nF8}DK*K+E!}{~fQxGeAg! z(2vA>wsH@c!_fB;oB%X{;*vjKmiNn*D^~Zt_m19|vrYcZeh&4YWq^D#F;F?>h_&G) z*`*>_qJ(h_QcR*SgW?z`6e!QK8(>Cy$P$QCk`Lc+2}%?g@AG+3u>K5ZC3l)ACU@J{Yvae?Qbe_rIp?1D4X zTd(-kX+w=-g2(vv*RQS=-$H&z>MlS2_=U~LqvHr0koAq8V2JJ=BZk(q=<@*EHU<)t&-X4PqC1&iI9ph#*LK!yz%C1HoHU!R}eLZ zxqjZ@OO)p+ev+f$#pli)^ zhxHACMLDH_I@zoSU+OgH&s_XF|4)+2YdNWigZmHYXs6h``Qs#1&{ZtiR)QCIDbc(h zDg|u$mtyxHgxJitx!jBjW-PGPF|;AT^{Z7cC%9G~qgXyeq;Ab)1RG9_7%|jeE>VG_ z2ZU}7mI3#*G0L0}X=8$FtNXryrvi0CR0HDTf)g+O_%xaf~B z<9BKsNs`7Dbe-QA!ILKsw8MzR8|axH6AS}*nJ_ZGJe^p@KjbfveQw=)HLiH{hjl-P z`h8;ngd!}siKeL!f|HIU4>%Fjgp1_Xr-Wlf7!9GqL{Y6lQl?KI>Ydh8N6SN;4T|Y8 zjPp8mzjI5M&KBb~uQlwTcIekJph(~`diUul!zo~!p$`4 zsl$F9IIy#Aa=cDY-9PMCLNgL0~5X2Y#kN>1zcen!rU8&cD-{Sf_3U%ll@| z81B(rlrvHAWX3yQi8bSSs88jGmvUxYTd`uPT%o=(0&nQ80R7nu7ETs7XRTFUkDoYT zJ;=0eE_`*0`l?%N6hWO#X%^N^U*6V68J)VZ0$gCxeJ{ihxyX3Usw%$QN`$dP?Y;s={xgC9!$ ze~;n&kjv^T`B>HIw&ocTPa7d*>+IPd^a2WY@*sj<&kNy#W)7nm8VI2k#W=bWp=+?| zNy=NcuBH3Ss!cuFD)z6xG2cR{x$`Di1-q))wj4er?&Mh1X&_ZnGs%=Wg^1mS%s(4~@x;v_6l zBCkyyqIctZ@|OS4Sj=Ng80QWh-fJhn3Xo>Lggx~k({F2;SK|VVpa+{` z9iH!!?j87-x#1pab=1;4(ClCXpEJj&;c5IJ!|KQ}^$On231Dyts@c-SV@YVs4FTMXjU1q$YBvts3v z+it$P!&~aXdV4CzVEG<7jyeFW(csIA!Kv&LhyQ9YC#=w^(?+TQh1l~`in&X>wvvni z${aDWmnnrHfrDlgc?+H*OO`Y?6VIoFKc~8cCeg}xV&1anh#516d5ptB9%Gz5232E{ z?_aJ;zW<(O1pkYIraymGE9nyJCWPom-Gp8e#vxq1c-AuxCFR`f72^n+qmFSA>5h%$}oFqpg0!|`n=jm_&c#U zRbD&_51Yakbdi;-=Brmbmw6F-ok>zhj}iAtA(vRH3O{06GD^b|Fl2^|sVrUxsX@Mz zo{5`F%0?`m`eMLHt($(#$Pgw zojW&J@ekC8HdHnt&@9O^F+FlNbnTr}NJMDl=N#~|Q1_sZWb3xqtzu4(2^zcz!=b13 z=9`P{XP~DLQej*egMVQ(cohteP>X6giN3Ej1yzJ@KHiJgU_6GN%P*-@CtFvjaNc`P z2>Zdbgd5xy@ML&Fk<=ncxJ9Hh<-rvz=V~KQkt^yLP8ee&s111=66+-z!sd=HEQs1=boLdq2@=2=A@PXkrm@At;Q%v&fQ0UG8IVHSzk zm+`jSBc|;p8OEZ;Q=KGyj1~edMQSQPrca-IbD6Rw@;+1>@gBa}t4?tY2Hq3UFZBwL zAx=6Bfw&y+!SCcFT!avunrvK4WVMN*Zb!IAujTU*+Ae(o>u8YY#xI$lKF*21R;c1C-_K=(j}%1#pXnv(0Zi>YZB{Iv413JgqLq5n!f7BjvXVz+97mm zpo(aCjCDbE4Bs$rRL2O0Y%fUA;K0Fc7TTdtb+usx%>WmL!&foq{DnRIL%9R*?VG-Ktnc*;bA%&^4?s1ckqD#klTc!B;89LaLKP-4 z6_+w$b~PEX5y&@yg=1>-K%pSaVS^D$4Ie&CbdB9sS-#pZzPfdfap6zB#}1=#Bv&Pz zI!$mWgxcFB~4liT6si6fsj{BY*nOjC@2zMJ*v&Y$=;#k%QBpFl9y?q zFld`LBZe{1?F#Z3|0Ksi0_V`XJFPHD*WT05c;W?Dhf={F>24e!H5@yliCZ#|dyg~v?Lj3EmXKoplx~RU5cMtChVHg^c6T)K< z2Kv*I{%LhSq!AGYiA8+w#TOF~T(DqXG}deN>Ng_Qs#POzlqgY}isCXNL4pJuo__kN zlk&ha9E``i^s_%iix%~C?AWpPB}$ZNNz|xOG%zz^?EwS&1r$T>7o!OIJ)eA<{2-!j zV1(3|kqzvLd=1@nK(&&&7cHW)=`A7L%&1^mR;i4~2%0NSu7OYjm2-yEFhhb6jqn86 zw`cDb_HW3}Smj#~=mn$2|G(thQ!}B%@9>e`3eOr_#T&{BhT$uk_=XX3#@@d;?av8K zTUD}T@xX?@A2)Ppe`hEXcno+&`PD~Fg_#;f6ysXy3aT~}z8G1~w4Y*%`rx&a(!W8n zN#8@q&P|L4KU|P!x$+9t)QLA#GyA5AW3*?_2IDfBVO-Tn6|w^E|2fDoE}g$(7}Qg2 zteP3d6AIhBz6lSm4IVyutKrmjUUx2f^k=seDN-nRXs>bL{qExxs9346X~!~xjJ8_@ z0q82kSV&CSkk5)#XgwhcpNRJ{8gdq-I1ztF!5pQJf`xMG-ztdoSI3HKbinP|v(ZR| zp2tuf<%S+3SpSL~nSsA_y73}fpy zuOH**pU+r_;f)Q$>&c)EmmI?kqf--8K78hxC(T3QG00;=+hZt)*qbd|mVh?Yo!VRv z!uv8s35}<05?2<*!CGs9O!*ygWx;E4}^U-1}TE2=-M<2DT9ikA`WdLl)NgNr z6HmB->bDn4d4udj$1oyCj{I@TlqqVSJb66qU0Tk-$2@2&P)I}L;Z32Uf!C;6tCY>u zqvrtvABMukpi3wv6bnC)pfiT?r!jnKu)6k_2h)0uR(~0%>hO_WUh*Ae7zj|q8ukps zN2Lg*{%wvCM&2m$>Ey|iH4GTWgSK1$gZn`alWI5@Xcl2r?K)-5i!fXUW1;67kp2$BDU5?&5c;j5x%&-MUK5qq zWyrtpmrkujudVN#U8kYT1yepGYoPJ zAFd6(4;@`yf84Pd z27=31!YTD-gb1f@uZu6(i1hP6#Elzg(4avBp1*fiDDX`l>J^|n5Lg!@K~opGP!eu` z)GLX!V&+Df`# zq{X;Gq>%o>TOiXJMYIqvzd&2+dL4YXmt)Y+kvw@K>q9A9ws@hya|o=!J-Y&N;y!0G zJ@J%l+&H)J6bL!-Z~yY%1JyyqAE2=mESTfO@#6ue?LE6W0$=QrM=0zC)67Ak!kPkL zutEG=!+6BrFRer!I(KO%@wgfqTh`u2B4^8fT^y*UD3?vY1o)vmEs@ano5odz!eK7kGN8=AEOL+qp|i%?~WD=B9|A z@bsf$Bn+3*<;$N-+E;muT5_wN2AJ5J<{HuZ-7{oJCIyu&f&TDYe>{O#{)a2@KR3g{ U-SH)lkN^Mx07*qoM6N<$f?Vy;d;kCd literal 0 HcmV?d00001 diff --git a/default-templates/kutrl.html b/default-templates/kutrl.html new file mode 100644 index 0000000..0827236 --- /dev/null +++ b/default-templates/kutrl.html @@ -0,0 +1,87 @@ + + + + + + + + {{tpl:BlogName encode_html="1"}}<tpl:PaginationIf start="0"> - {{tpl:lang page}} {{tpl:PaginationCurrent}}</tpl:PaginationIf> + + + + + + + - {{tpl:lang page}} {{tpl:PaginationCurrent}}" /> + + + + + + + + + + + + + + + + + + {{tpl:include src="_head.html"}} + + + +
    + +{{tpl:include src="_top.html"}} + +
    + +
    +
    + +
    +

    {{tpl:lang Links shortener}}

    + +

    {{tpl:kutrlMsg}}

    +
    + + +
    +

    +

    +

    + + {{tpl:kutrlHumanFieldProtect}} +

    +
    +
    +
    + +
    +
    + + + +
    + +{{tpl:include src="_footer.html"}} +
    + + \ No newline at end of file diff --git a/default-templates/kutrl404.html b/default-templates/kutrl404.html new file mode 100644 index 0000000..74adc9c --- /dev/null +++ b/default-templates/kutrl404.html @@ -0,0 +1,67 @@ + + + + + + + + {{tpl:lang Document not found}} - {{tpl:BlogName encode_html="1"}} + + + + + + + + + + + + + + + + {{tpl:include src="_head.html"}} + + + +
    +{{tpl:include src="_top.html"}} + +
    + +
    +
    + +
    +

    {{tpl:lang URL not found}}

    +
    + +
    +

    {{tpl:lang The URL you are looking for does not exist.}}

    +

    broken link

    + +

    {{tpl:lang Create your own short URL}}

    +
    +
    + +
    +
    + + + +
    + +{{tpl:include src="_footer.html"}} +
    + + \ No newline at end of file diff --git a/icon-b.png b/icon-b.png new file mode 100644 index 0000000000000000000000000000000000000000..0359bb810b1951a63995b964fc59e5ecf96235dc GIT binary patch literal 1605 zcmbu;`BPH|5C`x_lnOymDPEmcM7*W6R8%Z&4VX*_Bq0IPf}#>RLKH+pxD{SNKvb>> zNE9VNga{%emLmcht|$s}gFv`%0>~vVCvE7z(9g_wcV=hyx7|1b9<2-i4h{f-F2)Aw zpzeFuqqRX@k(|3Cb=yd>!dU?TFHuKH)&zhJ00HZSLLd-UR#w*5)@RS2MIw2w}ngWbmq_+ zG8-6VyJ^KnLymzdYyg!Fu!e!DFt9bdjVkOnDx8)SuOr24s|dOfaSKG$lRD}{Bm=q2 zchLDg5Wz^~Xxw=Dmp&hJ`L&Y@GJ@~t%)Ne&8|ax8Ld^-kQWWI&D)=%l)V(a?I){p? z^|x;Z!x9;^XOaH73|d}nSYBLcQA_|Y{$@EykTGeanKxx12r?Lq$jHd3sHo`Z=$M$8 z*x1-RckaZ+#U&&pBqk=_i^@z&N@6mZDJdzbsi_YiK1@$fXR%n1A3uJ?dXkZm@$~7_ zwD>|+ynxMSXJ=>U~^cpO1}=jZIBW z&CSi9-VL<2wzjpkwYRr-baZrfc6N1jb$567^z`)h_V)GliN)gn{{Df1fx*GSR-vM| zN!Ht>>~E6}cPoc_R>u026aA{8p`qd7;gOM%(b3Vdv9a;-@rj9v$;rv7sj2Dd>6w`s zi9|9xJ3Bu=FPT`K8IygHC>9nL78e(nmX?;6mseI+zJC3>y1EKM(DH&(DwWD)GPzu? zP$=Y5m0Ye=Q>iJ})YOOle^?+hSJnI4*KJ(<0YI;2JsR5&+k)!hZaT`D?&#-54{jn)e)j!9$0jG^UP&B!8X6nN4(n#bllIpwCK3xN2_E~+c|!WH z2Q80Hq-dDaLYz*)1wm0 z@$JGAvp0^HHi#RHgdDg(Y1wn&lF{y#Q|^&N_%ZjtuTtR*gxC$!q|MJWZP%UlpBa-r=xJyWcqy(l|cL*2VUH7Z#PY? z+qe9qf-O1Dxqmgq%D`pzfN}pP2~cq8@3V)v26GKTCYP%n<}b8-Jaj%1-g>f(?Ycx7 z|1oXjn_59YE4}y*d#CoC*y&&!_v@O5-}P&(Gw<}LZZ}ky!cDaHe1Ve1bprChL0F}1 zACGu6>_`KV=FJ&{N9ME6Z?(|8QDnJ=(b`XRxpzvRf|*#;DF0(*2bfhR9q*ph>Ilth z`~K>yoo6z0J2cnafBGl*Oqj?IJclm2DM*7|2(^N~#lCDgp#9OL_Y_wDXJ6(ZiDu9p zexxFt7(nLlVg#Y45Ddyu@U(wt%Ww|b_nPsA9;2=II-TzE4rb8ST&q%P|3p5Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02g#cSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@HN8cn$p8QXJ4r-AR5(v#U;u*u|Nk>$!yq|a@-R7uXV0E- zK7IOB@#DvjMqj>sF$ZFcPoF-SzIpRT3rGtC4Q2t#i@bgNR{Qhk&!#|ihA&>ckOzvh zfeqQPVS{i%K|$N($&(-L+O_NJfddD=E?TtcLw$Yyn#{~hf1pB+qM{=AhK7divu4fu z(%aj6H#*MA`sKe zA7rBiqHJ1R&2@Li@Bd2uZgQg=zyVA|)hm{*`RB&`2^@`48n^n8lk&fR|I{PgI$pYQ zJpB%hIY=6Sx`5@xi4%caw{HFBA-NB1fREsVhfi+(XNM~0Ieh47q?^R9pPmxOjzaaI z1R~S-@86|>$$Du|%YwhI9FHJ|h~9h@Z+c=uob}Ef?&61kx^dn8p67c|8D;=-N(ALb z7GOqF+PQOQ^Tgg+_abcO|MrmH@z+)O$X_?{z25`0*X^HFe^?IL@9+$TfHF0?fX2200000NkvXXu0mjfjsHh) literal 0 HcmV?d00001 diff --git a/inc/class.kutrl.php b/inc/class.kutrl.php new file mode 100644 index 0000000..cfbade9 --- /dev/null +++ b/inc/class.kutrl.php @@ -0,0 +1,104 @@ +getBehaviors('kutrlService'); + + if (empty($list)) return array(); + + $service = array(); + foreach($list as $k => $callback) + { + try + { + list($service_id,$service_class) = call_user_func($callback); + $services[(string) $service_id] = (string) $service_class; + } + catch (Exception $e) {} + } + return $services; + } + + # Silently try to load a service according to its id + # Return null on error else service on success + public static function quickService($id='') + { + global $core; + + try + { + if (empty($id)) { + return null; + } + $services = self::getServices($core); + if (isset($services[$id])) { + return new $services[$id]($core); + } + } + catch(Exception $e) { } + + return null; + } + + # Silently try to load a service according to its place + # Return null on error else service on success + public static function quickPlace($place='plugin') + { + global $core; + + try + { + if (!in_array($place,array('tpl','wiki','admin','plugin'))) { + return null; + } + $id = $core->blog->settings->kUtRL->get('kutrl_'.$place.'_service'); + if (!empty($id)) { + return self::quickService($id); + } + } + catch(Exception $e) { } + + return null; + } + + # Silently try to reduce url (using 'plugin' place) + # return long url on error else short url on success + public static function quickReduce($url,$custom=null,$place='plugin') + { + global $core; + + try + { + $srv = self::quickPlace($place); + if (empty($srv)) { + return $url; + } + $rs = $srv->hash($url,$custom); + if (empty($rs)) { + return $url; + } + + return $srv->url_base.$rs->hash; + } + catch(Exception $e) { } + + return $url; + } +} +?> \ No newline at end of file diff --git a/inc/img/kutrl_logo.png b/inc/img/kutrl_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..def9fa8a9762c07fe869d672c632cae6a88fe231 GIT binary patch literal 18825 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 literal 0 HcmV?d00001 diff --git a/inc/index.link.php b/inc/index.link.php new file mode 100644 index 0000000..d2bd1a1 --- /dev/null +++ b/inc/index.link.php @@ -0,0 +1,153 @@ +con->escape($_POST['str'])); + $hash = empty($_POST['custom']) ? null : $_POST['custom']; + + if (empty($url)) + throw new Exception(__('There is nothing to shorten.')); + + if (!$kut->testService()) + throw new Exception(__('Service is not well configured.')); + + if (null !== $hash && !$kut->allow_custom_hash) + throw new Exception(__('This service does not allowed custom hash.')); + + if (!$kut->isValidUrl($url)) + throw new Exception(__('This link is not a valid URL.')); + + if (!$kut->isLongerUrl($url)) + throw new Exception(__('This link is too short.')); + + if (!$kut->isProtocolUrl($url)) + throw new Exception(__('This type of link is not allowed.')); + + if (!$kut->allow_external_url && !$kut->isBlogUrl($url)) + throw new Exception(__('Short links are limited to this blog URL.')); + + if ($kut->isServiceUrl($url)) + throw new Exception(__('This link is already a short link.')); + + if (null !== $hash && false !== ($rs = $kut->isKnowHash($hash))) + throw new Exception(__('This custom short url is already taken.')); + + if (false !== ($rs = $kut->isKnowUrl($url))) + { + $url = $rs->url; + $new_url = $kut->url_base.$rs->hash; + $msg = + '

    '. + sprintf(__('Short link for %s is %s'), + ''.html::escapeHTML($url).'', + ''.$new_url.'' + ).'

    '; + } + else + { + if (false === ($rs = $kut->hash($url,$hash))) + { + if ($kut->error->flag()) + { + throw new Exception($kut->error->toHTML()); + } + throw new Exception(__('Failed to create short link. This could be caused by a service failure.')); + } + else + { + $url = $rs->url; + $new_url = $kut->url_base.$rs->hash; + $msg = + '

    '. + sprintf(__('Short link for %s is %s'), + ''.html::escapeHTML($url).'', + ''.$new_url.'' + ).'

    '; + + # ex: Send new url to messengers + if (!empty($rs)) + { + $core->callBehavior('adminAfterKutrlCreate',$core,$rs,__('New short URL')); + } + } + } + } + catch (Exception $e) + { + $core->error->add($e->getMessage()); + } +} + +echo ' + +kUtRL, '.__('Links shortener').''.$header.' + +

    kUtRL'. +' › '.__('Links').''. +' › '.__('New link'). +'

    '.$msg; + +if (null === $kut) +{ + echo '

    '.__('You must set an admin service.').'

    '; +} +else +{ + echo ' + '; +} +dcPage::helpBlock('kUtRL'); +echo $footer.''; +?> \ No newline at end of file diff --git a/inc/index.links.php b/inc/index.links.php new file mode 100644 index 0000000..8a87b0d --- /dev/null +++ b/inc/index.links.php @@ -0,0 +1,267 @@ +rs->isEmpty()) + echo '

    '.__('No short link').'

    '; + + else { + $pager = new pager($page,$this->rs_count,$nb_per_page,10); + + $pager->base_url = $url; + + $html_block = + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + '%s'. + '
    '.__('Hash').''.__('Link').''.__('Date').''.__('Service').'
    '; + + echo '

    '.__('Page(s)').' : '.$pager->getLinks().'

    '; + $blocks = explode('%s',$html_block); + echo $blocks[0]; + + $this->rs->index(((integer)$page - 1) * $nb_per_page); + $iter = 0; + while ($iter < $nb_per_page) { + + echo $this->line($url,$iter); + + if ($this->rs->isEnd()) + break; + else + $this->rs->moveNext(); + + $iter++; + } + echo $blocks[1]; + echo '

    '.__('Page(s)').' : '.$pager->getLinks().'

    '; + } + } + + private function line($url,$loop) + { + $type = $this->rs->kut_type; + $hash = $this->rs->kut_hash; + + if (null !== ($o = kutrl::quickService($this->rs->kut_type))) + { + $type = ''.$o->name.''; + $hash = ''.$hash.''; + } + + return + ''."\n". + ''. + form::checkbox(array('entries['.$loop.']'),$this->rs->kut_id,0). + ''. + ''. + $hash. + "\n". + ''. + ''.$this->rs->kut_url.''. + "\n". + ''. + dt::dt2str(__('%Y-%m-%d %H:%M'),$this->rs->kut_dt,$this->core->auth->getInfo('user_tz')). + "\n". + ''. + $type. + "\n". + ''."\n"; + } +} + +# Logs class +$log = new kutrlLog($core); + +# Filters +$show_filters = false; +$urlsrv = !empty($_GET['urlsrv']) ? $_GET['urlsrv'] : ''; +$sortby = !empty($_GET['sortby']) ? $_GET['sortby'] : 'kut_dt'; +$order = !empty($_GET['order']) ? $_GET['order'] : 'desc'; + +$page = !empty($_GET['page']) ? (integer) $_GET['page'] : 1; +$nb_per_page = 30; +if (!empty($_GET['nb']) && (integer) $_GET['nb'] > 0) { + if ($nb_per_page != $_GET['nb']) $show_filters = true; + $nb_per_page = (integer) $_GET['nb']; +} + +# Combos +$sortby_combo = array( + __('Date') => 'kut_dt', + __('Long link') => 'kut_url', + __('Short link') => 'kut_hash' +); + +$order_combo = array( + __('Descending') => 'desc', + __('Ascending') => 'asc' +); + +$services_combo = array(); +foreach(kutrl::getServices($core) as $service_id => $service) +{ + $o = new $service($core); + $services_combo[__($o->name)] = $o->id; +} +$ext_services_combo = array_merge(array(__('Disabled')=>''),$services_combo); +$lst_services_combo = array_merge(array('-'=>''),$services_combo); + +# Params for list +$params = array(); +$params['limit'] = array((($page-1)*$nb_per_page),$nb_per_page); + +if ($sortby != '' && in_array($sortby,$sortby_combo)) +{ + if ($urlsrv != '' && in_array($urlsrv,$lst_services_combo)) + $params['kut_type'] = $urlsrv; + + if ($order != '' && in_array($order,$order_combo)) + $params['order'] = $sortby.' '.$order; + + if ($sortby != 'kut_dt' || $order != 'desc' || $urlsrv != '') + $show_filters = true; +} + +$pager_base_url = + $p_url. + '&tab=list'. + '&urlsrv='.$urlsrv. + '&sortby='.$sortby. + '&order='.$order. + '&nb='.$nb_per_page. + '&page=%s'; + + +# Delete links from list +if ($action == 'deletelinks') +{ + try + { + foreach($_POST['entries'] as $k => $id) + { + $rs = $log->getLogs(array('kut_id'=>$id)); + if ($rs->isEmpty()) continue; + + if (null === ($o = kutrl::quickService($rs->kut_type))) continue; + $o->remove($rs->kut_url); + } + + $core->blog->triggerBlog(); + http::redirect($p_url.'&part=links&urlsrv='.$urlsrv.'&sortby='.$sortby.'&order='.$order.'&nb='.$nb_per_page.'&page='.$page.'&msg='.$action); + } + catch (Exception $e) { + $core->error->add($e->getMessage()); + } +} + +# Get links and pager +try +{ + $list_all = $log->getLogs($params); + $list_counter = $log->getLogs($params,true)->f(0); + $list_current = new kutrlLinksList($core,$list_all,$list_counter,$pager_base_url); +} +catch (Exception $e) +{ + $core->error->add($e->getMessage()); +} + +if (!$show_filters) { + $header .= dcPage::jsLoad('js/filter-controls.js'); +} + +echo ' + +kUtRL, '.__('Links shortener').''. +"\n\n". +$header.' + +

    kUtRL'. +' › '.__('Links'). +' - '.__('New link').''. +'

    '.$msg; + +if (!$show_filters) { + echo '

    '. + __('Filters').'

    '; +} + +echo ' +
    +
    '.__('Filters').' +
    +
    + +
    +
    + + +
    +
    +

    + +'. +form::hidden(array('p'),'kUtRL'). +form::hidden(array('part'),'links').' +

    +
    +
    +
    +
    +
    +
    '; + +$list_current->display($page,$nb_per_page,$pager_base_url); + +echo ' +
    +

    +

    +'. +form::hidden(array('action'),'deletelinks'). +form::hidden(array('urlsrv'),$urlsrv). +form::hidden(array('sortby'),$sortby). +form::hidden(array('order'),$order). +form::hidden(array('page'),$page). +form::hidden(array('nb'),$nb_per_page). +form::hidden(array('p'),'kUtRL'). +form::hidden(array('part'),'links'). +$core->formNonce().' +

    +
    +
    '; + +dcPage::helpBlock('kUtRL'); +echo $footer.''; +?> \ No newline at end of file diff --git a/inc/index.service.php b/inc/index.service.php new file mode 100644 index 0000000..9fb236a --- /dev/null +++ b/inc/index.service.php @@ -0,0 +1,92 @@ + $service) + { + $o = new $service($core); + $o->saveSettings(); + } + $core->blog->triggerBlog(); + http::redirect($p_url.'&part=service§ion='.$section.'&msg='.$action); + } + catch (Exception $e) + { + $core->error->add($e->getMessage()); + } +} + +echo ' + +kUtRL, '.__('Links shortener').''.$header. +dcPage::jsLoad('index.php?pf=kUtRL/js/service.js'). +"\n". +' + +

    kUtRL'. +' › '.__('Links').''. +' › '.__('Services'). +' - '.__('New link').''. +'

    '.$msg.' +
    '; + +foreach(kutrl::getServices($core) as $service_id => $service) +{ + $o = new $service($core); + + echo '
    '.$o->name.''; + + if (!empty($msg)) + { + echo '

    '.( + $o->testService() ? + $img_green.' '.sprintf(__('%s API is well configured and runing.'),$o->name) : + $img_red.' '.sprintf(__('Failed to test %s API.'),$o->name) + ).'

    '; + //if ($o->error->flag()) { + echo $o->error->toHTML(); + //} + } + + if (!empty($o->home)) + { + echo '

    '.sprintf(__('Learn more about %s.'),$o->name).'

    '; + } + + $o->settingsForm(); + + echo '
    '; +} + +echo ' +
    +

    '. +$core->formNonce(). +form::hidden(array('p'),'kUtRL'). +form::hidden(array('part'),'service'). +form::hidden(array('action'),'saveservice'). +form::hidden(array('section'),$section).' +

    +
    '; +dcPage::helpBlock('kUtRL'); +echo $footer.''; +?> \ No newline at end of file diff --git a/inc/index.setting.php b/inc/index.setting.php new file mode 100644 index 0000000..11ca5d0 --- /dev/null +++ b/inc/index.setting.php @@ -0,0 +1,164 @@ +kutrl_active; +$s_plugin_service = (string) $s->kutrl_plugin_service; +$s_admin_service = (string) $s->kutrl_admin_service; +$s_tpl_service = (string) $s->kutrl_tpl_service; +$s_wiki_service = (string) $s->kutrl_wiki_service; +$s_allow_external_url = (boolean) $s->kutrl_allow_external_url; +$s_tpl_passive = (boolean) $s->kutrl_tpl_passive; +$s_tpl_active = (boolean) $s->kutrl_tpl_active; +$s_admin_entry_default = (string) $s->kutrl_admin_entry_default; + +if ($default_part == 'setting' && $action == 'savesetting') +{ + try { + $s_active = !empty($_POST['s_active']); + $s_admin_service = $_POST['s_admin_service']; + $s_plugin_service = $_POST['s_plugin_service']; + $s_tpl_service = $_POST['s_tpl_service']; + $s_wiki_service = $_POST['s_wiki_service']; + $s_allow_external_url = !empty($_POST['s_allow_external_url']); + $s_tpl_passive = !empty($_POST['s_tpl_passive']); + $s_tpl_active = !empty($_POST['s_tpl_active']); + $s_admin_entry_default = !empty($_POST['s_admin_entry_default']); + + $s->put('kutrl_active',$s_active); + $s->put('kutrl_plugin_service',$s_plugin_service); + $s->put('kutrl_admin_service',$s_admin_service); + $s->put('kutrl_tpl_service',$s_tpl_service); + $s->put('kutrl_wiki_service',$s_wiki_service); + $s->put('kutrl_allow_external_url',$s_allow_external_url); + $s->put('kutrl_tpl_passive',$s_tpl_passive); + $s->put('kutrl_tpl_active',$s_tpl_active); + $s->put('kutrl_admin_entry_default',$s_admin_entry_default); + + $core->blog->triggerBlog(); + + http::redirect($p_url.'&part=setting&msg='.$action.'§ion='.$section); + } + catch (Exception $e) { + $core->error->add($e->getMessage()); + } +} + +$services_combo = array(); +foreach(kutrl::getServices($core) as $service_id => $service) +{ + $o = new $service($core); + $services_combo[__($o->name)] = $o->id; +} +$ext_services_combo = array_merge(array(__('Disabled')=>''),$services_combo); +$lst_services_combo = array_merge(array('-'=>''),$services_combo); + +echo ' + +kUtRL, '.__('Links shortener').''.$header. +dcPage::jsLoad('index.php?pf=kUtRL/js/setting.js'). +"\n". +' + +

    kUtRL'. +' › '.__('Links').''. +' › '.__('Settings'). +' - '.__('New link').''. +'

    '.$msg.' +
    + +
    '. __('Plugin activation').' +

    +
    + +
    '. __('General rules').' +

    +

    '.__('Not only link started with this blog URL could be shortened.').'

    +

    +

    '.__('If this extension is disabled and the passive mode is enabled, "kutrl" tags (like EntryKurl) will display long urls instead of nothing on templates.').'

    +

    +

    '.__('If the active mode is enabled, all know default template tags (like EntryURL) will display short urls instead of long ones on templates.').'
    '. +__('You can disable URL shortening for a specific template tag by adding attribute disable_kutrl="1" to it.').'

    +

    +

    '.__('This can be changed on page of creation/edition of an entry.').'

    +
    + +
    '. __('Default services').' +

    +

    '.__('Service to use in this admin page and on edit page of an entry.').'

    +

    +

    '.__('Service to use on third part plugins.').'

    +

    +

    '.__('Shorten links automatically when using template value like "EntryKutrl".').'

    +

    +

    '.__('Shorten links automatically found in contents using wiki synthax.').'

    +
    + +
    +

    '. +$core->formNonce(). +form::hidden(array('p'),'kUtRL'). +form::hidden(array('part'),'setting'). +form::hidden(array('action'),'savesetting'). +form::hidden(array('section'),$section).' +

    +
    '; +dcPage::helpBlock('kUtRL'); +echo $footer.''; +?> \ No newline at end of file diff --git a/inc/lib.kutrl.activityreport.php b/inc/lib.kutrl.activityreport.php new file mode 100644 index 0000000..a1c279d --- /dev/null +++ b/inc/lib.kutrl.activityreport.php @@ -0,0 +1,38 @@ +activityReport->addGroup('kutrl',__('Plugin kUtRL')); + +# from BEHAVIOR kutrlAfterCreateShortUrl in kUtRL/inc/lib.kutrl.srv.php +$core->activityReport->addAction( + 'kutrl', + 'create', + __('Short link creation'), + __('New short link of type "%s" and hash "%s" was created.'), + 'kutrlAfterCreateShortUrl', + array('kutrlActivityReportBehaviors','kutrlCreate') +); + +class kutrlActivityReportBehaviors +{ + public static function kutrlCreate($rs) + { + $logs = array($rs->type,$rs->hash); + + $GLOBALS['core']->activityReport->addLog('kutrl','create',$logs); + } +} +?> \ No newline at end of file diff --git a/inc/lib.kutrl.log.php b/inc/lib.kutrl.log.php new file mode 100644 index 0000000..974eede --- /dev/null +++ b/inc/lib.kutrl.log.php @@ -0,0 +1,314 @@ +core = $core; + $this->table = $core->prefix.'kutrl'; + $this->blog = $core->con->escape($core->blog->id); + $this->con = $core->con; + } + + public function nextId() + { + return $this->con->select( + 'SELECT MAX(kut_id) FROM '.$this->table + )->f(0) + 1; + } + + public function insert($url,$hash,$type,$service='kutrl') + { + $cur = $this->con->openCursor($this->table); + $this->con->writeLock($this->table); + + try { + $cur->kut_id = $this->nextId(); + $cur->blog_id = $this->blog; + $cur->kut_url = (string) $url; + $cur->kut_hash = (string) $hash; + $cur->kut_type = (string) $type; + $cur->kut_service = (string) $service; + $cur->kut_dt = date('Y-m-d H:i:s'); + $cur->kut_counter = 0; + + $cur->insert(); + $this->con->unlock(); + + return array( + 'id' => $cur->kut_id, + 'url' => $url, + 'hash' => $hash, + 'type' => $type, + 'service' => $service, + 'counter '=> 0 + ); + } + catch (Exception $e) + { + $this->con->unlock(); + throw $e; + } + return false; + } + + public function select($url=null,$hash=null,$type=null,$service='kutrl') + { + //$this->con->writeLock($this->table); + + $req = + 'SELECT kut_id as id, kut_hash as hash, kut_url as url, '. + 'kut_type as type, kut_service as service, kut_counter as counter '. + 'FROM '.$this->table.' '. + "WHERE blog_id = '".$this->blog."' ". + "AND kut_service = '".$this->con->escape($service)."' "; + + if (null !== $url) + $req .= "AND kut_url = '".$this->con->escape($url)."' "; + + if (null !== $hash) + $req .= "AND kut_hash = '".$this->con->escape($hash)."' "; + + if (null !== $type) { + if (is_array($type)) { + $req .= "AND kut_type '".$this->con->in($type)."' "; + } + else { + $req .= "AND kut_type = '".$this->con->escape($type)."' "; + } + } + + $req .= 'ORDER BY kut_dt DESC '.$this->con->limit(1); + + $rs = $this->con->select($req); + //$this->con->unlock(); + + return $rs->isEmpty() ? false : $rs; + } + + public function clear($id) + { + $id = (integer) $id; + + $cur = $this->con->openCursor($this->table); + $this->con->writeLock($this->table); + + try + { + $cur->kut_url = ''; + $cur->kut_dt = date('Y-m-d H:i:s'); + $cur->kut_counter = 0; + + $cur->update( + "WHERE blog_id='".$this->blog."' ". + "AND kut_id='".$id."' " + ); + $this->con->unlock(); + + return true; + } + catch (Exception $e) + { + $this->con->unlock(); + throw $e; + } + return false; + } + + public function delete($id) + { + $id = (integer) $id; + + return $this->con->execute( + 'DELETE FROM '.$this->table.' '. + "WHERE blog_id='".$this->blog."' ". + "AND kut_id='".$id."' " + ); + } + + public function counter($id,$do='get') + { + $id = (integer) $id; + + $rs = $this->con->select( + 'SELECT kut_counter '. + 'FROM '.$this->table.' '. + "WHERE blog_id='".$this->blog."' ". + "AND kut_id='".$id."' " + ); + + $counter = $rs->isEmpty() ? 0 : $rs->kut_counter; + + if ('get' == $do) + { + return $counter; + } + elseif ('up' == $do) + { + $counter += 1; + } + elseif ('reset' == $do) + { + $counter = 0; + } + else + { + return 0; + } + + $cur = $this->con->openCursor($this->table); + $this->con->writeLock($this->table); + + $cur->kut_counter = (integer) $counter; + $cur->update( + "WHERE blog_id='".$this->blog."' ". + "AND kut_id='".$id."'" + ); + $this->con->unlock(); + + return $counter; + } + + public function getLogs($p,$count_only=false) + { + if ($count_only) + { + $r = 'SELECT count(S.kut_id) '; + } + else + { + $content_req = ''; + + if (!empty($p['columns']) && is_array($p['columns'])) + { + $content_req .= implode(', ',$p['columns']).', '; + } + $r = + 'SELECT S.kut_id, S.kut_type, S.kut_hash, S.kut_url, '. + $content_req.'S.kut_dt '; + } + + $r .= 'FROM '.$this->table.' S '; + + if (!empty($p['from'])) + { + $r .= $p['from'].' '; + } + $r .= "WHERE S.blog_id = '".$this->blog."' "; + + if (isset($p['kut_service'])) + { + $r .= "AND kut_service='".$this->con->escape($p['kut_service'])."' "; + } + else + { + $r .= "AND kut_service='kutrl' "; + } + + if (isset($p['kut_type'])) + { + if (is_array($p['kut_type']) && !empty($p['kut_type'])) + { + $r .= 'AND kut_type '.$this->con->in($p['kut_type']); + } + elseif ($p['kut_type'] != '') + { + $r .= "AND kut_type = '".$this->con->escape($p['kut_type'])."' "; + } + } + + if (isset($p['kut_id'])) + { + if (is_array($p['kut_id']) && !empty($p['kut_id'])) + { + $r .= 'AND kut_id '.$this->con->in($p['kut_id']); + } + elseif ($p['kut_id'] != '') + { + $r .= "AND kut_id = '".$this->con->escape($p['kut_id'])."' "; + } + } + + if (isset($p['kut_hash'])) + { + if (is_array($p['kut_hash']) && !empty($p['kut_hash'])) + { + $r .= 'AND kut_hash '.$this->con->in($p['kut_hash']); + } + elseif ($p['kut_hash'] != '') + { + $r .= "AND kut_hash = '".$this->con->escape($p['kut_hash'])."' "; + } + } + + if (isset($p['kut_url'])) + { + if (is_array($p['kut_url']) && !empty($p['kut_url'])) + { + $r .= 'AND kut_url '.$this->con->in($p['kut_url']); + } + elseif ($p['kut_url'] != '') + { + $r .= "AND kut_url = '".$this->con->escape($p['kut_url'])."' "; + } + } + + if (!empty($p['kut_year'])) + { + $r .= + 'AND '.$this->con->dateFormat('kut_dt','%Y').' = '. + "'".sprintf('%04d',$p['kut_year'])."' "; + } + + if (!empty($p['kut_month'])) + { + $r .= + 'AND '.$this->con->dateFormat('kut_dt','%m').' = '. + "'".sprintf('%02d',$p['kut_month'])."' "; + } + + if (!empty($p['kut_day'])) + { + $r .= + 'AND '.$this->con->dateFormat('kut_dt','%d').' = '. + "'".sprintf('%02d',$p['kut_day'])."' "; + } + + if (!empty($p['sql'])) + { + $r .= $p['sql'].' '; + } + + if (!$count_only) + { + $r .= empty($p['order']) ? + 'ORDER BY kut_dt DESC ' : + 'ORDER BY '.$this->con->escape($p['order']).' '; + } + + if (!$count_only && !empty($p['limit'])) + { + $r .= $this->con->limit($p['limit']); + } + + return $this->con->select($r); + } +} +?> \ No newline at end of file diff --git a/inc/lib.kutrl.srv.php b/inc/lib.kutrl.srv.php new file mode 100644 index 0000000..f4f53a8 --- /dev/null +++ b/inc/lib.kutrl.srv.php @@ -0,0 +1,254 @@ +core = $core; + $this->settings = $core->blog->settings->kUtRL; + $this->log = new kutrlLog($core); + $this->error = new dcError(); + $this->error->setHTMLFormat('%s',"%s\n"); + + $this->init(); + + // Force setting + $allow_external_url = $this->settings->kutrl_allow_external_url; + $this->config['$allow_external_url'] = null === $allow_external_url ? + true : $allow_external_url; + + $this->config = array_merge( + array( + 'id' => 'undefined', + 'name' => 'undefined', + 'home' => '', + + 'allow_external_url' => true, + 'allow_custom_hash' => false, + 'allow_protocols' => array('http://'), + + 'url_test' => 'http://dotclear.jcdenis.com/go/kUtRL', + 'url_api' => '', + 'url_base' => '', + 'url_min_len' => 0 + ), + $this->config + ); + } + + # Magic get for config values + public function __get($k) + { + return isset($this->config[$k]) ? $this->config[$k] : null; + } + + # Additionnal actions on child start + protected function init() + { + // + } + + # Save settings from admin page + public function saveSettings() + { + return null; + } + + # Settings form for admin page + public function settingsForm() + { + echo + '

    '. + __('There is nothing to configure for this service.'). + '

    '; + } + + # Test if service is well configured + public function testService() + { + return null; + } + + # Test if an url is valid + public function isValidUrl($url) + { + return (boolean) filter_var($url,FILTER_VALIDATE_URL); + } + + # Test if an url contents know prefix + public function isServiceUrl($url) + { + return strpos($url,$this->url_base) === 0; + } + + # Test if an url is long enoutgh + public function isLongerUrl($url) + { + return ((integer) $this->url_min_len >= $url); + } + + # Test if an url protocol (eg: http://) is allowed + public function isProtocolUrl($url) + { + foreach($this->allow_protocols as $protocol) + { + if (empty($protocol)) continue; + + if (strpos($url,$protocol) === 0) return true; + } + return false; + } + + # Test if an url is from current blog + public function isBlogUrl($url) + { + $base = $this->core->blog->url; + $url = substr($url,0,strlen($base)); + + return $url == $base; + } + + # Test if an url is know + public function isKnowUrl($url) + { + return $this->log->select($url,null,$this->id,'kutrl'); + } + + # Test if an custom short url is know + public function isKnowHash($hash) + { + return $this->log->select(null,$hash,$this->id,'kutrl'); + } + + # Create hash from url + public function hash($url,$hash=null) + { + $url = trim($this->core->con->escape($url)); + if ('undefined' === $this->id) + { + return false; + } + if ($hash && !$this->allow_custom_hash) + { + return false; + } + if ($this->isServiceUrl($url)) + { + return false; + } + if (!$this->isLongerUrl($url)) + { + return false; + } + if (!$this->allow_external_url && $this->isBlogUrl($url)) + { + return false; + } + if ($hash && false !== ($rs = $this->isKnowHash($hash))) + { + return false; + } + if (false === ($rs = $this->isKnowUrl($url))) + { + if (false === ($rs = $this->createHash($url,$hash))) + { + return false; + } + + $this->log->insert($rs->url,$rs->hash,$rs->type,'kutrl'); + $this->core->blog->triggerBlog(); + + + # --BEHAVIOR-- kutrlAfterCreateShortUrl + $this->core->callBehavior('kutrlAfterCreateShortUrl',$rs); + + + } + return $rs; + } + + # Create a hash for a given url (and its custom hash) + public function createHash($url,$hash=null) + { + return false; + } + + # Remove an url from list of know urls + public function remove($url) + { + if (!($rs = $this->isKnowUrl($url))) return false; + echo 'la'; + $this->deleteUrl($url); + $this->log->delete($rs->id); + return true; + } + + # Delete url on service (second argument really delete urls) + public function deleteUrl($url,$delete=false) + { + return null; + } + + # Retrieve long url from hash + public function getUrl($hash) + { + return false; + } + + # Post request + public static function post($server,$data,$verbose=true,$get=false,$headers=array()) + { + $url = (string) $server; + $client = netHttp::initClient($url,$url); + $client->setUserAgent('kUtRL - http://kutrl.fr'); + $client->setPersistReferers(false); + + if (is_array($headers) && !empty($headers)) + { + foreach($headers as $header) + { + $client->setMoreHeader($header); + } + } + + if ($get) + { + $client->get($url,$data); + } + else + { + $client->post($url,$data); + } + + if (!$verbose && $client->getStatus() != 200) + { + return false; + } + + if ($verbose) + { + return $client->getContent(); + } + return true; + } +} +?> \ No newline at end of file diff --git a/inc/lib.wiki.kutrl.php b/inc/lib.wiki.kutrl.php new file mode 100644 index 0000000..96ee948 --- /dev/null +++ b/inc/lib.wiki.kutrl.php @@ -0,0 +1,80 @@ +blog->settings->kUtRL; + + # Do nothing on comment preview and post preview + if (!empty($_POST['preview']) + || !empty($GLOBALS['_ctx']) && $GLOBALS['_ctx']->preview + || !$s->kutrl_active) return; + + if (null === ($kut = kutrl::quickPlace('wiki'))) return; + + foreach($kut->allow_protocols as $protocol) + { + $wiki2xhtml->registerFunction( + 'url:'.$protocol, + array('kutrlWiki','transform') + ); + } + } + + public static function transform($url,$content) + { + global $core; + $s = $core->blog->settings->kUtRL; + + if (!$s->kutrl_active) return; + + if (null === ($kut = kutrl::quickPlace('wiki'))) return array(); + + # Test if long url exists + $is_new = false; + $rs = $kut->isKnowUrl($url); + if (!$rs) + { + $is_new = true; + $rs = $kut->hash($url); + } + + if (!$rs) + { + return array(); + } + else + { + $res = array(); + $testurl = strlen($rs->url) > 35 ? substr($rs->url,0,35).'...' : $rs->url; + $res['url'] = $kut->url_base.$rs->hash; + $res['title'] = sprintf(__('%s (Shorten with %s)'),$rs->url,__($kut->name)); + if ($testurl == $content) $res['content'] = $res['url']; + + # ex: Send new url to messengers + if (!empty($rs)) + { + $core->callBehavior('wikiAfterKutrlCreate',$core,$rs,__('New short URL')); + } + + return $res; + } + } +} +?> \ No newline at end of file diff --git a/inc/patch.dcminiurl.php b/inc/patch.dcminiurl.php new file mode 100644 index 0000000..42a7561 --- /dev/null +++ b/inc/patch.dcminiurl.php @@ -0,0 +1,135 @@ +parseRecords()) +{ + try + { + $core->plugins->deactivateModule('dcMiniUrl'); + } + catch (Exception $e) + { + //$core->error->add($e->getMessage()); + } +} + +class dcMiniUrl2kUtRL +{ + public $core; + + public $k_tb; + public $m_tb; + + public function __construct($core) + { + $this->core = $core; + $this->con = $core->con; + $this->k_tb = $core->prefix.'kutrl'; + $this->m_tb = $core->prefix.'miniurl'; + } + + public function parseRecords() + { + $rs = $this->con->select( + 'SELECT * FROM '.$this->m_tb.' ' + ); + + while ($rs->fetch()) + { + if ($rs->miniurl_type == 'customurl' || $rs->miniurl_type == 'miniurl') + { + if ($this->exists($rs)) continue; + + $this->insertKutrl($rs); + $this->insertLocal($rs); + } + else + { + $this->insertOther($rs); + } + } + return true; + } + + private function insertKutrl($rs) + { + $cur = $this->common($rs); + $cur->kut_service = 'kutrl'; + $cur->kut_type = 'local'; + $cur->kut_counter = 0; + $cur->kut_password = null; + + $cur->insert(); + $this->con->unlock(); + } + + private function insertLocal($rs) + { + $cur = $this->common($rs); + $cur->kut_service = 'local'; + $cur->kut_type = $rs->miniurl_type == 'customurl' ? + 'localcustom' : 'localnormal'; + + $cur->insert(); + $this->con->unlock(); + } + + private function insertOther($rs) + { + $cur = $this->common($rs); + + $cur->insert(); + $this->con->unlock(); + } + + private function common($rs) + { + $cur = $this->con->openCursor($this->k_tb); + $this->con->writeLock($this->k_tb); + $cur->kut_id = $this->nextId(); + $cur->blog_id = $rs->blog_id; + $cur->kut_service = 'unknow'; + $cur->kut_type = $rs->miniurl_type; + $cur->kut_hash = $rs->miniurl_id; + $cur->kut_url = $rs->miniurl_str; + $cur->kut_dt = $rs->miniurl_dt; + $cur->kut_counter = $rs->miniurl_counter; + $cur->kut_password = $rs->miniurl_password; + + return $cur; + } + + private function exists($rs) + { + $chk = $this->con->select( + 'SELECT kut_hash FROM '.$this->k_tb.' '. + "WHERE blog_id = '".$rs->blog_id."' ". + "AND kut_service = 'local' ". + "AND kut_hash = '".$rs->miniurl_id."' " + ); + return !$chk->isEmpty(); + } + + private function nextId() + { + return $this->con->select( + 'SELECT MAX(kut_id) FROM '.$this->k_tb.' ' + )->f(0) + 1; + } +} +?> \ No newline at end of file diff --git a/inc/services/class.bilbolinks.service.php b/inc/services/class.bilbolinks.service.php new file mode 100644 index 0000000..f1eac24 --- /dev/null +++ b/inc/services/class.bilbolinks.service.php @@ -0,0 +1,97 @@ + 'bilbolinks', + 'name' => 'BilboLinks', + 'home' => 'http://www.tux-planet.fr/bilbobox/' + ); + + protected function init() + { + $base = (string) $this->settings->kutrl_srv_bilbolinks_base; + if (!empty($base) && substr($base,-1,1) != '/') $base .= '/'; + + $this->config['url_api'] = $base.'api.php'; + $this->config['url_base'] = $base; + $this->config['url_min_len'] = 25; + } + + public function saveSettings() + { + $base = ''; + if (!empty($_POST['kutrl_srv_bilbolinks_base'])) + { + $base = $_POST['kutrl_srv_bilbolinks_base']; + if (substr($base,-1,1) != '/') $base .= '/'; + } + + $this->settings->put('kutrl_srv_bilbolinks_base',$base); + } + + public function settingsForm() + { + echo + '

    '. + '

    '. + __('This is the root URL of the "bilbolinks" service you want to use. Ex: "http://tux-pla.net/".'). + '

    '; + } + + public function testService() + { + if (empty($this->url_base)) + { + $this->error->add(__('Service is not well configured.')); + return false; + } + + $arg = array('longurl' => urlencode($this->url_test)); + if (!self::post($this->url_api,$arg,true,true)) + { + $this->error->add(__('Service is unavailable.')); + return false; + } + return true; + } + + public function createHash($url,$hash=null) + { + $arg = array('longurl' => $url); + + if (!($response = self::post($this->url_api,$arg,true,true))) + { + $this->error->add(__('Service is unavailable.')); + return false; + } + if ($response == 'You are too speed!') + { + $this->error->add(__('Service rate limit exceeded.')); + return false; + } + + $rs = new ArrayObject(); + $rs->hash = str_replace($this->url_base,'',$response); + $rs->url = $url; + $rs->type = $this->id; + + return $rs; + } +} +?> \ No newline at end of file diff --git a/inc/services/class.bitly.service.php b/inc/services/class.bitly.service.php new file mode 100644 index 0000000..235a978 --- /dev/null +++ b/inc/services/class.bitly.service.php @@ -0,0 +1,127 @@ + 'bitly', + 'name' => 'bit.ly', + 'home' => 'http://bit.ly', + + 'url_api' => 'http://api.bit.ly/v3/', + 'url_base' => 'http://bit.ly/', + 'url_min_len' => 25 + ); + + private $args = array( + 'format' => 'xml', + 'login' => '', + 'apiKey' => '', + 'history' => 0 + ); + + protected function init() + { + $this->args['login'] = $this->settings->kutrl_srv_bitly_login; + $this->args['apiKey'] = $this->settings->kutrl_srv_bitly_apikey; + $this->args['history'] = $this->settings->kutrl_srv_bitly_history ? 1 : 0; + } + + public function saveSettings() + { + $this->settings->put('kutrl_srv_bitly_login',$_POST['kutrl_srv_bitly_login']); + $this->settings->put('kutrl_srv_bitly_apikey',$_POST['kutrl_srv_bitly_apikey']); + $this->settings->put('kutrl_srv_bitly_history',isset($_POST['kutrl_srv_bitly_history'])); + } + + public function settingsForm() + { + echo + '

    '. + '

    '. + sprintf(__('This is your login to sign up to %s'),$this->config['name']). + '

    '. + '

    '. + '

    '. + sprintf(__('This is your personnal %s API key. You can find it on your account page.'),$this->config['name']). + '

    '. + '

    '. + '

    '. + __('This publish all short links on your bit.ly public page.'). + '

    '; + } + + public function testService() + { + if (empty($this->args['login']) || empty($this->args['apiKey'])) + { + $this->error->add(__('Service is not well configured.')); + return false; + } + + $args = $this->args; + $args['hash'] = 'WP9vc'; + if (!($response = self::post($this->url_api.'expand',$args,true))) + { + $this->error->add(__('Failed to call service.')); + return false; + } + + $rsp = simplexml_load_string($response); + + $err_msg = (string) $rsp->status_txt; + if ($err_msg != 'OK') { + $err_no = (integer) $rsp->status_code; + $this->error->add(sprintf(__('An error occured with code %s and message "%s"'),$err_no,$err_msg)); + return false; + } + return true; + } + + public function createHash($url,$hash=null) + { + $args = $this->args; + $args['longUrl'] = $url; + + if (!($response = self::post($this->url_api.'shorten',$args,true))) + { + $this->error->add(__('Failed to call service.')); + return false; + } + + $rsp = simplexml_load_string($response); + + $err_msg = (string) $rsp->status_txt; + if ($err_msg != 'OK') { + $err_no = (integer) $rsp->status_code; + $this->error->add(sprintf(__('An error occured with code %s and message "%s"'),$err_no,$err_msg)); + return false; + } + + $rs = new ArrayObject(); + $rs->hash = (string) $rsp->data[0]->hash; + $rs->url = (string) $rsp->data[0]->long_url; + $rs->type = $this->id; + + return $rs; + } +} +?> \ No newline at end of file diff --git a/inc/services/class.custom.service.php b/inc/services/class.custom.service.php new file mode 100644 index 0000000..449d25b --- /dev/null +++ b/inc/services/class.custom.service.php @@ -0,0 +1,120 @@ + 'custom', + 'name' => 'Custom' + ); + + protected function init() + { + $config = unserialize(base64_decode($this->settings->kutrl_srv_custom)); + if (!is_array($config)) + { + $config = array(); + } + + $this->config['url_api'] = !empty($config['url_api']) ? $config['url_api'] : ''; + $this->config['url_base'] = !empty($config['url_base']) ? $config['url_base'] : ''; + $this->config['url_param'] = !empty($config['url_param']) ? $config['url_param'] : ''; + $this->config['url_encode'] = !empty($config['url_api']); + + $this->config['url_min_length'] = strlen($this->url_base) + 2; + } + + public function saveSettings() + { + $config = array( + 'url_api' => $_POST['kutrl_srv_custom_url_api'], + 'url_base' => $_POST['kutrl_srv_custom_url_base'], + 'url_param' => $_POST['kutrl_srv_custom_url_param'], + 'url_encode' => !empty($_POST['kutrl_srv_custom_url_encode']) + ); + $this->settings->put('kutrl_srv_custom',base64_encode(serialize($config))); + } + + public function settingsForm() + { + $default = array( + 'url_api' => '', + 'url_base' => '', + 'url_param' => '', + 'url_encode' => true + ); + $config = unserialize(base64_decode($this->settings->kutrl_srv_custom)); + if (!is_array($config)) + { + $config = array(); + } + $config = array_merge($default,$config); + + echo + '

    '.__('You can set a configurable service.').'
    '. + __('It consists on a simple query to an URL with only one param.').'
    '. + __('It must respond with a http code 200 on success.').'
    '. + __('It must returned the short URL (or only hash) in clear text.').'

    ' . + '

    '. + '

    '.__('Full path to API of the URL shortener. ex: "http://is.gd/api.php"').'

    '. + '

    '. + '

    '.__('Common part of the short URL. ex: "http://is.gd/"').'

    '. + '

    '. + '

    '.__('Param of the query. ex: "longurl"').'

    '. + '

    '; + } + + public function testService() + { + if (empty($this->url_api)) return false; + + $url = $this->url_encode ? urlencode($this->url_test) : $this->url_test; + $arg = array($this->url_param => $url); + if (!self::post($this->url_api,$arg,true,true)) + { + $this->error->add(__('Service is unavailable.')); + return false; + } + return true; + } + + public function createHash($url,$hash=null) + { + $enc = $this->url_encode ? urlencode($url) : $url; + $arg = array($this->url_param => $enc); + + if (!($response = self::post($this->url_api,$arg,true,true))) + { + $this->error->add(__('Service is unavailable.')); + return false; + } + + $rs = new ArrayObject(); + $rs->hash = str_replace($this->url_base,'',$response); + $rs->url = $url; + $rs->type = $this->id; + + return $rs; + } +} +?> \ No newline at end of file diff --git a/inc/services/class.default.service.php b/inc/services/class.default.service.php new file mode 100644 index 0000000..1f71056 --- /dev/null +++ b/inc/services/class.default.service.php @@ -0,0 +1,91 @@ +config = array( + 'id' => 'default', + 'name' => 'Default', + 'home' => '', + + 'url_api' => SHORTEN_SERVICE_API, + 'url_base' => SHORTEN_SERVICE_BASE, + 'url_min_len' => strlen(SHORTEN_SERVICE_BASE) + 2, + + 'url_param' => SHORTEN_SERVICE_PARAM, + 'url_encode' => SHORTEN_SERVICE_ENCODE + ); + } + + public function settingsForm() + { + echo + '

    '. + __('There is nothing to configure for this service.'). + '

    '. + '

    '.__('This service is set to:').'

    '. + '
    '. + '
    '.__('Service name:').'
    '. + '
    '.SHORTEN_SERVICE_NAME.'
    '. + '
    '.__('Full API URL:').'
    '. + '
    '.SHORTEN_SERVICE_API.'
    '. + '
    '.__('Query param:').'
    '. + '
    '.SHORTEN_SERVICE_PARAM.'
    '. + '
    '.__('Short URL domain:').'
    '. + '
    '.SHORTEN_SERVICE_BASE.'
    '. + '
    '.__('Encode URL:').'
    '. + '
    '.(SHORTEN_SERVICE_ENCODE ? __('yes') : __('no')).'
    '. + '
    '; + } + + public function testService() + { + $url = $this->url_encode ? urlencode($this->url_test) : $this->url_test; + $arg = array($this->url_param => urlencode($this->url_test)); + + if (!self::post($this->url_api,$arg,true,true)) + { + $this->error->add(__('Service is unavailable.')); + return false; + } + return true; + } + + public function createHash($url,$hash=null) + { + $enc = $this->url_encode ? urlencode($url) : $url; + $arg = array($this->url_param => $url); + + if (!($response = self::post($this->url_api,$arg,true,true))) + { + $this->error->add(__('Service is unavailable.')); + return false; + } + + $rs = new ArrayObject(); + $rs->hash = str_replace($this->url_base,'',$response); + $rs->url = $url; + $rs->type = $this->id; + + return $rs; + } +} +?> \ No newline at end of file diff --git a/inc/services/class.googl.service.php b/inc/services/class.googl.service.php new file mode 100644 index 0000000..5a175e1 --- /dev/null +++ b/inc/services/class.googl.service.php @@ -0,0 +1,80 @@ + 'AIzaSyDE1WfOMdnrnX8p51jSmVodenaNk385asc' + ); + private $headers = array('Content-Type: application/json'); + + protected function init() + { + $this->url_base = 'http://goo.gl/'; + $this->url_min_length = 20; + } + + public function testService() + { + $args = $this->args; + $args['shortUrl'] = $this->url_base.'PLovn'; + if (!($response = self::post($this->url_api,$args,true,true,$this->headers))) + { + $this->error->add(__('Failed to call service.')); + return false; + } + + $rsp = json_decode($response); + + if (empty($rsp->status)) { + $this->error->add(__('An error occured')); + return false; + } + return true; + } + + public function createHash($url,$hash=null) + { + $args = $this->args; + $args['longUrl'] = $url; + $args = json_encode($args); + + if (!($response = self::post($this->url_api,$args,true,false,$this->headers))) + { + $this->error->add(__('Failed to call service.')); + return false; + } + + $rsp = json_decode($response); + + if (empty($rsp->id)) { + $this->error->add(__('An error occured')); + return false; + } + + $rs = new ArrayObject(); + $rs->hash = str_replace($this->url_base,'',$rsp->id); + $rs->url = $rsp->longUrl; + $rs->type = $this->id; + + return $rs; + } +} +?> \ No newline at end of file diff --git a/inc/services/class.isgd.service.php b/inc/services/class.isgd.service.php new file mode 100644 index 0000000..62ef7e6 --- /dev/null +++ b/inc/services/class.isgd.service.php @@ -0,0 +1,56 @@ + 'isgd', + 'name' => 'is.gd', + 'home' => 'http://is.gd/', + + 'url_api' => 'http://is.gd/api.php', + 'url_base' => 'http://is.gd/', + 'url_min_length' => 25 + ); + + public function testService() + { + $arg = array('longurl' => urlencode($this->url_test)); + if (!self::post($this->url_api,$arg,true,true)) + { + $this->error->add(__('Service is unavailable.')); + return false; + } + return true; + } + + public function createHash($url,$hash=null) + { + $arg = array('longurl' => $url); + + if (!($response = self::post($this->url_api,$arg,true,true))) + { + $this->error->add(__('Service is unavailable.')); + return false; + } + + $rs = new ArrayObject(); + $rs->hash = str_replace($this->url_base,'',$response); + $rs->url = $url; + $rs->type = $this->id; + + return $rs; + } +} +?> \ No newline at end of file diff --git a/inc/services/class.local.service.php b/inc/services/class.local.service.php new file mode 100644 index 0000000..6334a32 --- /dev/null +++ b/inc/services/class.local.service.php @@ -0,0 +1,270 @@ + 'local', + 'name' => 'kUtRL', + 'home' => 'http://kutrl.fr', + + 'allow_custom_hash' => true + ); + + protected function init() + { + $protocols = (string) $this->settings->kutrl_srv_local_protocols; + $this->config['allow_protocols'] = empty($protocols) ? array() : explode(',',$protocols); + + $this->config['url_base'] = $this->core->blog->url.$this->core->url->getBase('kutrl').'/'; + $this->config['url_min_len'] = strlen($this->url_base) + 2; + + } + + public function saveSettings() + { + $this->settings->put('kutrl_srv_local_protocols',$_POST['kutrl_srv_local_protocols'],'string'); + $this->settings->put('kutrl_srv_local_public',isset($_POST['kutrl_srv_local_public']),'boolean'); + $this->settings->put('kutrl_srv_local_css',$_POST['kutrl_srv_local_css'],'string'); + $this->settings->put('kutrl_srv_local_404_active',isset($_POST['kutrl_srv_local_404_active']),'boolean'); + } + + public function settingsForm() + { + echo + '
    '. + + '

    '.__('Settings:').'

    '. + '

    '. + + '

    '. + __('Use comma seperated list like: "http:,https:,ftp:"'). + '

    '. + + '

    '. + + '

    '. + form::textarea('kutrl_srv_local_css',50,3,html::escapeHTML($this->settings->kutrl_srv_local_css),'',2). + '

    '. + '

    '.__('You can add here special cascading style sheet. Body of page has class "dc-kutrl" and widgets have class "shortenkutrlwidget" and "rankkutrlwidget".').'

    '. + + '

    '. + '

    '.__('If this is not activated, the default 404 page of the theme will be display.').'

    '. + + '
    '. + + '

    '.__('Note:').'

    '. + '

    '. + __('This service use your own Blog to shorten and serve URL.').'
    '. + sprintf(__('This means that with this service short links start with "%s".'),$this->url_base). + '

    '. + '

    '. + __("You can use Dotclear's plugin called myUrlHandlers to change short links prefix on your blog."); + + if (preg_match('/index\.php/',$this->url_base)) + { + echo + '

    '. + __("We recommand that you use a rewrite engine in order to remove 'index.php' from your blog's URL."). + '
    '. + __("You can find more about this on the Dotclear's documentation."). + '

    '; + } + echo + '

    '. + '

    '.__('There are two templates delivered with kUtRL, if you do not use default theme, you may adapt them to yours.').'
    '. + __('Files are in plugin directory /default-templates, just copy them into your theme and edit them.').'

    '. + + '
    '; + } + + public function testService() + { + if (!empty($this->allow_protocols)) + { + return true; + } + else { + $this->error->add(__('Service is not well configured.')); + return false; + } + } + + public function createHash($url,$hash=null) + { + # Create response object + $rs = new ArrayObject(); + $rs->type = 'local'; + $rs->url = $url; + + # Normal link + if ($hash === null) + { + $type = 'localnormal'; + $rs->hash = $this->next($this->last('localnormal')); + } + + # Mixed custom link + elseif (preg_match('/^([A-Za-z0-9]{2,})\!\!$/',$hash,$m)) + { + $type = 'localmix'; + $rs->hash = $m[1].$this->next(-1,$m[1]); + } + + # Custom link + elseif (preg_match('/^[A-Za-z0-9\.\-\_]{2,}$/',$hash)) + { + if (false !== $this->log->select(null,$hash,null,'local')) + { + $this->error->add(__('Custom short link is already taken.')); + return false; + } + $type = 'localcustom'; + $rs->hash = $hash; + } + + # Wrong char in custom hash + else + { + $this->error->add(__('Custom short link is not valid.')); + return false; + } + + # Save link + try { + $this->log->insert($rs->url,$rs->hash,$type,$rs->type); + return $rs; + } + catch (Exception $e) + { + $this->error->add(__('Failed to save link.')); + } + return false; + } + + protected function last($type) + { + return + false === ($rs = $this->log->select(null,null,$type,'local')) ? + -1 : $rs->hash; + } + + protected function next($last_id,$prefix='') + { + if ($last_id == -1) + { + $next_id = 0; + } + else + { + for($x = 1; $x <= strlen($last_id); $x++) + { + $pos = strlen($last_id) - $x; + + if ($last_id[$pos] != 'z') + { + $next_id = $this->increment($last_id,$pos); + break; + } + } + + if (!isset($next_id)) + { + $next_id = $this->append($last_id); + } + } + + return + false === $this->log->select(null,$prefix.$next_id,null,'local') ? + $next_id : $this->next($next_id,$prefix); + } + + protected function append($id) + { + $id = str_split($id); + for ($x = 0; $x < count($id); $x++) + { + $id[$x] = 0; + } + return implode($id).'0'; + } + + protected function increment($id,$pos) + { + $id = str_split($id); + $char = $id[$pos]; + + if (is_numeric($char)) + { + $new_char = $char < 9 ? $char + 1 : 'a'; + } + else + { + $new_char = chr(ord($char) + 1); + } + $id[$pos] = $new_char; + + if ($pos != (count($id) - 1)) + { + for ($x = ($pos + 1); $x < count($id); $x++) + { + $id[$x] = 0; + } + } + + return implode($id); + } + + public function getUrl($hash) + { + if (false === ($rs = $this->log->select(null,$hash,null,'local'))) + { + return false; + } + if (!$rs->url) //previously removed url + { + return false; + } + + $this->log->counter($rs->id,'up'); + return $rs->url; + } + + public function deleteUrl($url,$delete=false) + { + if (false === ($rs = $this->log->select($url,null,null,'local'))) + { + return false; + } + if ($delete) + { + $this->log->delete($rs->id); + } + else + { + $this->log->clear($rs->id,''); + } + return true; + } +} +?> \ No newline at end of file diff --git a/inc/services/class.shortto.service.php b/inc/services/class.shortto.service.php new file mode 100644 index 0000000..8559608 --- /dev/null +++ b/inc/services/class.shortto.service.php @@ -0,0 +1,56 @@ + 'shortto', + 'name' => 'short.to', + 'home' => 'http://short.to', + + 'url_api' => 'http://short.to/s.txt', + 'url_base' => 'http://short.to/', + 'url_min_len' => 25 + ); + + public function testService() + { + $arg = array('url' => urlencode($this->url_test)); + if (!self::post($this->url_api,$arg,true,true)) + { + $this->error->add(__('Service is unavailable.')); + return false; + } + return true; + } + + public function createHash($url,$hash=null) + { + $arg = array('url' => $url); + + if (!($response = self::post($this->url_api,$arg,true,true))) + { + $this->error->add(__('Service is unavailable.')); + return false; + } + + $rs = new ArrayObject(); + $rs->hash = str_replace($this->url_base,'',$response); + $rs->url = $url; + $rs->type = $this->id; + + return $rs; + } +} +?> \ No newline at end of file diff --git a/inc/services/class.supr.service.php b/inc/services/class.supr.service.php new file mode 100644 index 0000000..3858d84 --- /dev/null +++ b/inc/services/class.supr.service.php @@ -0,0 +1,120 @@ + 'supr', + 'name' => 'su.pr StumbleUpon', + 'home' => 'http://su.pr', + + 'url_api' => 'http://su.pr/api/', + 'url_base' => 'http://su.pr/', + 'url_min_len' => 23 + ); + + private $args = array( + 'version' => '1.0', + 'format' => 'xml', + 'login' => '', + 'apiKey' => '' + ); + + protected function init() + { + $this->args['login'] = $this->settings->kutrl_srv_supr_login; + $this->args['apiKey'] = $this->settings->kutrl_srv_supr_apikey; + } + + public function saveSettings() + { + $this->settings->put('kutrl_srv_supr_login',$_POST['kutrl_srv_supr_login']); + $this->settings->put('kutrl_srv_supr_apikey',$_POST['kutrl_srv_supr_apikey']); + } + + public function settingsForm() + { + echo + '

    '. + '

    '. + sprintf(__('This is your login to sign up to %s'),$this->config['name']). + '

    '. + '

    '. + '

    '. + sprintf(__('This is your personnal %s API key. You can find it on your account page.'),$this->config['name']). + '

    '; + } + + public function testService() + { + if (empty($this->args['login']) || empty($this->args['apiKey'])) + { + $this->error->add(__('Service is not well configured.')); + return false; + } + + $args = $this->args; + $arg['longUrl'] = $this->url_test; + if (!($response = self::post($this->url_api.'shorten',$args,true))) + { + $this->error->add(__('Failed to call service.')); + return false; + } + + $rsp = simplexml_load_string($response); + + $status = (string) $rsp->statusCode; + if ($status != 'OK') { + $err_no = (integer) $rsp->errorCode; + $err_msg = (integer) $rsp->errorMessage; + $this->error->add(sprintf(__('An error occured with code %s and message "%s"'),$err_no,$err_msg)); + return false; + } + return true; + } + + public function createHash($url,$hash=null) + { + $args = $this->args; + $args['longUrl'] = $url; + + if (!($response = self::post($this->url_api.'shorten',$args,true))) + { + $this->error->add(__('Failed to call service.')); + return false; + } + + $rsp = simplexml_load_string($response); + + $status = (string) $rsp->statusCode; + if ($status != 'OK') { + $err_no = (integer) $rsp->errorCode; + $err_msg = (integer) $rsp->errorMessage; + $this->error->add(sprintf(__('An error occured with code %s and message "%s"'),$err_no,$err_msg)); + return false; + } + + $rs = new ArrayObject(); + $rs->hash = (string) $rsp->results[0]->nodeKeyVal->hash; + $rs->url = (string) $rsp->results[0]->nodeKeyVal->nodeKey; + $rs->type = $this->id; + + return $rs; + } +} +?> \ No newline at end of file diff --git a/inc/services/class.trim.service.php b/inc/services/class.trim.service.php new file mode 100644 index 0000000..fe2c755 --- /dev/null +++ b/inc/services/class.trim.service.php @@ -0,0 +1,126 @@ + 'trim', + 'name' => 'tr.im', + 'home' => 'http://tr.im', + + 'url_api' => 'http://api.tr.im/v1/', + 'url_base' => 'http://tr.im/', + 'url_min_len' => 25 + ); + + private $args = array( + 'username' => '', + 'password' => '' + ); + + private $api_rate_time = 0; + + protected function init() + { + $this->args['username'] = $this->settings->kutrl_srv_trim_username; + $this->args['password'] = $this->settings->kutrl_srv_trim_password; + + $this->api_rate_time = (integer) $this->settings->kutrl_srv_trim_apiratetime; + } + + public function saveSettings() + { + $this->settings->put('kutrl_srv_trim_username',$_POST['kutrl_srv_trim_username']); + $this->settings->put('kutrl_srv_trim_password',$_POST['kutrl_srv_trim_password']); + } + + public function settingsForm() + { + echo + '

    '. + '

    '. + __('This is your login to sign up to tr.im.'). + '

    '. + '

    '. + '

    '. + __('This is your password to sign up to tr.im.'). + '

    '; + } + + public function testService() + { + if (empty($this->args['username']) || empty($this->args['password'])) + { + $this->error->add(__('Service is not well configured.')); + return false; + } + if (time() < $this->api_rate_time + 300) // bloc service within 5min on API rate limit + { + $this->error->add(__('Prevent service rate limit.')); + return false; + } + if (!($rsp = self::post($this->url_api.'verify.xml',$this->args,true,true))) + { + $this->error->add(__('Service is unavailable.')); + return false; + } + $r = simplexml_load_string($rsp); + + if ($r['code'] == 200) + { + return true; + } + $this->error->add(__('Authentication to service failed.')); + return false; + } + + public function createHash($url,$hash=null) + { + $arg = $this->args; + $arg['url'] = $url; + + if (!($rsp = self::post($this->url_api.'trim_url.xml',$arg,true,true))) + { + $this->error->add(__('Service is unavailable.')); + return false; + } + + $r = simplexml_load_string($rsp); + + # API rate limit + if ($r['code'] == 425) + { + $this->settings->put('kutrl_srv_trim_apiratetime',time()); + + $this->error->add(__('Service rate limit exceeded.')); + return false; + } + if (isset($r->trimpath)) + { + $rs = new ArrayObject(); + $rs->hash = $r->trimpath; + $rs->url = $url; + $rs->type = $this->id; + + return $rs; + } + $this->error->add(__('Unreadable service response.')); + return false; + } +} +?> \ No newline at end of file diff --git a/inc/services/class.yourls.service.php b/inc/services/class.yourls.service.php new file mode 100644 index 0000000..4c847dc --- /dev/null +++ b/inc/services/class.yourls.service.php @@ -0,0 +1,126 @@ + 'yourls', + 'name' => 'YOURLS', + 'home' => 'http://yourls.org' + ); + + private $args = array( + 'username' => '', + 'password' => '', + 'format' => 'xml', + 'action' => 'shorturl' + ); + + protected function init() + { + $this->args['username'] = $this->settings->kutrl_srv_yourls_username; + $this->args['password'] = $this->settings->kutrl_srv_yourls_password; + + $base = (string) $this->settings->kutrl_srv_yourls_base; + //if (!empty($base) && substr($base,-1,1) != '/') $base .= '/'; + + $this->config['url_api'] = $base; + $this->config['url_base'] = $base; + $this->config['url_min_len'] = strlen($base)+3; + } + + public function saveSettings() + { + $this->settings->put('kutrl_srv_yourls_username',$_POST['kutrl_srv_yourls_username']); + $this->settings->put('kutrl_srv_yourls_password',$_POST['kutrl_srv_yourls_password']); + $this->settings->put('kutrl_srv_yourls_base',$_POST['kutrl_srv_yourls_base']); + } + + public function settingsForm() + { + echo + '

    '. + '

    '. + __('This is the URL of the YOURLS service you want to use. Ex: "http://www.smaller.org/api.php".'). + '

    '. + '

    '. + '

    '. + __('This is your user name to sign up to this YOURLS service.'). + '

    '. + '

    '. + '

    '. + __('This is your password to sign up to this YOURLS service.'). + '

    '; + } + + public function testService() + { + if (empty($this->url_api)) + { + $this->error->add(__('Service is not well configured.')); + return false; + } + + $args = $this->args; + $args['url'] = $this->url_test; + + if (!($response = self::post($this->url_api,$this->args,true))) + { + $this->error->add(__('Service is unavailable.')); + return false; + } + $rsp = @simplexml_load_string($response); + + if ($rsp && $rsp->status == 'success') + { + return true; + } + $this->error->add(__('Authentication to service failed.')); + return false; + } + + public function createHash($url,$hash=null) + { + $args = $this->args; + $args['url'] = $url; + + if (!($response = self::post($this->url_api,$args,true))) + { + $this->error->add(__('Service is unavailable.')); + return false; + } + + $rsp = @simplexml_load_string($response); + + if ($rsp && $rsp->status == 'success') + { + $rs = new ArrayObject(); + $rs->hash = $rsp->url[0]->keyword; + $rs->url = $url; + $rs->type = $this->id; + + return $rs; + } + $this->error->add(__('Unreadable service response.')); + return false; + } +} +?> \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..cf2e720 --- /dev/null +++ b/index.php @@ -0,0 +1,70 @@ +blog->settings->kUtRL; + +# Default values +$show_filters = false; +$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : ''; +$section = isset($_REQUEST['section']) ? $_REQUEST['section'] : ''; +$img_green = 'ok'; +$img_red = 'fail'; + +$header = +dcPage::jsLoad('index.php?pf=kUtRL/js/main.js'). +'\n". +''; + +$footer = '

    +'.__('Settings').' - +'.__('Services').' - +kUtRL - '.$core->plugins->moduleInfo('kUtRL','version').'  +'.__('kUtRL').' +

    +

    + kUtRL, '.__('Links shortener').' | http://kutrl.fr +

    +'; + +# Messages +$msg = isset($_REQUEST['msg']) ? $_REQUEST['msg'] : ''; +$msg_list = array( + 'savesetting' => __('Configuration successfully saved'), + 'saveservice' => __('Services successfully updated'), + 'createlink' => __('Link successfully shorten'), + 'deletelinks' => __('Links successfully deleted') +); +if (isset($msg_list[$msg])) { + $msg = sprintf('

    %s

    ',$msg_list[$msg]); +} + +# Pages +$start_part = $s->kutrl_active ? 'links' : 'setting'; +$default_part = isset($_REQUEST['part']) ? $_REQUEST['part'] : $start_part; + +if (!file_exists(dirname(__FILE__).'/inc/index.'.$default_part.'.php')) { + $default_part = 'setting'; +} +include dirname(__FILE__).'/inc/index.'.$default_part.'.php'; + +?> \ No newline at end of file diff --git a/js/admin.js b/js/admin.js new file mode 100644 index 0000000..e8101b4 --- /dev/null +++ b/js/admin.js @@ -0,0 +1,15 @@ +/* -- BEGIN LICENSE BLOCK ---------------------------------- + * This file is part of kUtRL, a plugin for Dotclear 2. + * + * Copyright (c) 2009-2011 JC Denis and contributors + * jcdenis@gdwd.com + * + * Licensed under the GPL version 2.0 license. + * A copy of this license is available in LICENSE file or at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * -- END LICENSE BLOCK ------------------------------------*/ + +$(function(){ + /* toogle admin form sidebar */ + $('#kutrl-form-title').toggleWithLegend($('#kutrl-form-content'),{cookie:'dcx_kutrl_admin_form_sidebar'}); +}); \ No newline at end of file diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..8d0d5c9 --- /dev/null +++ b/js/main.js @@ -0,0 +1,80 @@ +/* -- BEGIN LICENSE BLOCK ---------------------------------- + * This file is part of kUtRL, a plugin for Dotclear 2. + * + * Copyright (c) 2009-2011 JC Denis and contributors + * jcdenis@gdwd.com + * + * Licensed under the GPL version 2.0 license. + * A copy of this license is available in LICENSE file or at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * -- END LICENSE BLOCK ------------------------------------*/ + +$(function(){ + /* tools */ + dotclear.jcTools = new jcToolsBox(); + /* wait message */ + $('#content').submit(function(){dotclear.jcTools.waitMessage();return true;}); +}); + +function jcToolsBox(){} + +jcToolsBox.prototype={ + text_wait:'Please wait', + section:'', + + formFieldsetToMenu:function(form){ + var This=this; + var section=$(form).children('fieldset[id='+This.section+']').attr('id'); + var hidden_section=$(form).children('input[name=section]').attr('value'); + var formMenu=$(form).children('p.formMenu').get(0); + if (formMenu==undefined) { + $(form).prepend($('

    ').addClass('formMenu')); + } + $(form).children('fieldset').each(function(){ + var Fieldset=this; + $(Fieldset).hide(); + var title=$(Fieldset).children('legend').text(); + var menu=$('').text(title).addClass('button').attr('tabindex','2').click( + function(){ + var fieldset_visible=$(form).children('fieldset:visible'); + if (fieldset_visible==undefined){ + $(Fieldset).slideDown('slow');$(form).children('input[type=submit]').show(); + }else{ + $(fieldset_visible).fadeOut('fast',function(){$(Fieldset).fadeIn('fast');$(form).children('input[type=submit]').show();}) + } + if (hidden_section==undefined){ + $(form).children('input[name=section]').remove(); + $(form).append($('').attr('name','section').attr('value',$(Fieldset).attr('id'))); + } + $('.message').fadeOut('slow',function(){$(this).slideUp('slow',function(){$(this).remove();})}); + } + ); + $(form).children('.formMenu').append(menu).append(' '); + }); + if (section!=undefined){ + $(form).children('fieldset[id='+section+']').show(); + }else{ + $(form).children('fieldset:first').show(); + } + }, + + waitMessage:function(){ + var This=this; + var content=$('div[id=content]'); + if (content!=undefined){ + $(content).hide(); + }else{ + $('input').hide();$('select').hide(); + content=$('body'); + } + var text=$('

    ').addClass('message').text(This.text_wait); + This.blinkItem(text); + var box=$('
    ').attr('style','margin: 60px auto auto;width:200px;').append(text); + $(content).before($(box)); + }, + + blinkItem:function(item){ + var This=this; + $(item).fadeOut('slow',function(){$(this).fadeIn('slow',function(){This.blinkItem(this);})}); + } +} \ No newline at end of file diff --git a/js/service.js b/js/service.js new file mode 100644 index 0000000..ec78b52 --- /dev/null +++ b/js/service.js @@ -0,0 +1,17 @@ +/* -- BEGIN LICENSE BLOCK ---------------------------------- + * This file is part of kUtRL, a plugin for Dotclear 2. + * + * Copyright (c) 2009-2011 JC Denis and contributors + * jcdenis@gdwd.com + * + * Licensed under the GPL version 2.0 license. + * A copy of this license is available in LICENSE file or at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * -- END LICENSE BLOCK ------------------------------------*/ + +$(function() { + var kutrlForm=$('#service-form'); + if (kutrlForm!=undefined){ + dotclear.jcTools.formFieldsetToMenu(kutrlForm); + } +}); \ No newline at end of file diff --git a/js/setting.js b/js/setting.js new file mode 100644 index 0000000..b450278 --- /dev/null +++ b/js/setting.js @@ -0,0 +1,17 @@ +/* -- BEGIN LICENSE BLOCK ---------------------------------- + * This file is part of kUtRL, a plugin for Dotclear 2. + * + * Copyright (c) 2009-2011 JC Denis and contributors + * jcdenis@gdwd.com + * + * Licensed under the GPL version 2.0 license. + * A copy of this license is available in LICENSE file or at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * -- END LICENSE BLOCK ------------------------------------*/ + +$(function() { + var kutrlForm=$('#setting-form'); + if (kutrlForm!=undefined){ + dotclear.jcTools.formFieldsetToMenu(kutrlForm); + } +}); \ No newline at end of file diff --git a/locales/en/help/help.html b/locales/en/help/help.html new file mode 100644 index 0000000..bebff89 --- /dev/null +++ b/locales/en/help/help.html @@ -0,0 +1,19 @@ + + + + + kUtRL + + + +

    If you want some help or contribute to the plugin kUtRL, follow these links.

    + + + + \ No newline at end of file diff --git a/locales/en/resources.php b/locales/en/resources.php new file mode 100644 index 0000000..8f6f377 --- /dev/null +++ b/locales/en/resources.php @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/locales/fr/help/help.html b/locales/fr/help/help.html new file mode 100644 index 0000000..e3fa7e1 --- /dev/null +++ b/locales/fr/help/help.html @@ -0,0 +1,19 @@ + + + + + kUtRL + + + +

    Si vous souhaitez plus d'aide ou apporter votre contribution à l'extension kUtRL, voici quelques liens utiles.

    + + + + \ No newline at end of file diff --git a/locales/fr/main.lang.php b/locales/fr/main.lang.php new file mode 100644 index 0000000..c6c7e81 --- /dev/null +++ b/locales/fr/main.lang.php @@ -0,0 +1,522 @@ + \ No newline at end of file diff --git a/locales/fr/main.po b/locales/fr/main.po new file mode 100644 index 0000000..6c58a92 --- /dev/null +++ b/locales/fr/main.po @@ -0,0 +1,679 @@ +# Language: Français +# Module: kUtRL - 2011.03.24 +# Date: 2011-03-24 16:34:14 +# Translated with translater 1.5 + +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Project-Id-Version: kUtRL 2011.03.24\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2011-03-24T16:34:14+00:00\n" +"Last-Translator: JC Denis\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Transfer-Encoding: 8bit\n" + +#: _admin.php:19 +#: _widgets.php:22 +#: inc/index.link.php:105 +#: inc/index.links.php:200 +#: inc/index.service.php:38 +#: inc/index.setting.php:70 +#: index.php:44 +#: index.php:45 +msgid "Links shortener" +msgstr "Réducteur de liens" + +#: _admin.php:73 +#: inc/index.links.php:117 +msgid "Short link" +msgstr "Lien court" + +#: _admin.php:90 +msgid "Create short link" +msgstr "Créer un lien court" + +#: _admin.php:96 +#: inc/index.link.php:129 +msgid "Custom short link:" +msgstr "Lien court personnalisé :" + +#: _admin.php:106 +#: _widgets.php:178 +msgid "never followed" +msgstr "jamais suivi" + +#: _admin.php:110 +#: _widgets.php:182 +msgid "followed one time" +msgstr "suivi une fois" + +#: _admin.php:114 +#: _widgets.php:186 +msgid "followed %s times" +msgstr "suivi %s fois" + +#: _admin.php:121 +#: _admin.php:225 +msgid "delete short link" +msgstr "effacer un lien court" + +#: _admin.php:224 +msgid "create short link" +msgstr "créer un lien court" + +#: _public.php:143 +msgid "Failed to verify protected field." +msgstr "Impossible de vérifier le champs de protection." + +#: _public.php:151 +#: inc/index.link.php:34 +#: inc/services/class.bilbolinks.service.php:61 +#: inc/services/class.bitly.service.php:76 +#: inc/services/class.local.service.php:108 +#: inc/services/class.supr.service.php:67 +#: inc/services/class.trim.service.php:69 +#: inc/services/class.yourls.service.php:78 +msgid "Service is not well configured." +msgstr "Le service n'est pas correctement configuré." + +#: _public.php:159 +msgid "This string is not a valid URL." +msgstr "Cette chaine n'est pas un lien valide." + +#: _public.php:167 +#: inc/index.link.php:43 +msgid "This link is too short." +msgstr "Ce lien est trop court." + +#: _public.php:175 +#: inc/index.link.php:46 +msgid "This type of link is not allowed." +msgstr "Ce type de lien n'est pas autorisé." + +#: _public.php:184 +#: inc/index.link.php:49 +msgid "Short links are limited to this blog URL." +msgstr "Les liens courts sont limités à l'URL de ce blog." + +#: _public.php:192 +#: inc/index.link.php:52 +msgid "This link is already a short link." +msgstr "Ce lien est dèjà un lien court." + +#: _public.php:205 +#: _public.php:224 +#: inc/index.link.php:63 +#: inc/index.link.php:84 +msgid "Short link for %s is %s" +msgstr "Le lien court pour %s est %s" + +#: _public.php:216 +msgid "Failed to create short link." +msgstr "Impossible de créer le lien court." + +#: _public.php:233 +#: _public.php:337 +#: _public.php:557 +msgid "New public short URL" +msgstr "Nouvelle URL courte" + +#: _public.php:429 +#: _widgets.php:106 +msgid "Rewrite \"%s\" in next field to show that you are not a robot:" +msgstr "Recopier \"%s\" dans le champs suivant pour montrer que vous n'êtes pas une machine :" + +#: _widgets.php:26 +msgid "Shorten link" +msgstr "Réduire un lien" + +#: _widgets.php:35 +#: _widgets.php:39 +msgid "Top of short links" +msgstr "Top des liens courts" + +#: _widgets.php:42 +msgid "Text: (Use wildcard %rank%, %hash%, %url%, %count%, %counttext%)" +msgstr "Texte : (Ustiliser les jokers %rank%, %hash%, %url%, %count%, %counttext%)" + +#: _widgets.php:45 +msgid "URL length (if truncate)" +msgstr "Longueur de l'URL (si tronqué)" + +#: _widgets.php:49 +msgid "All" +msgstr "Tous" + +#: _widgets.php:50 +msgid "Mini URL" +msgstr "Mini URL" + +#: _widgets.php:51 +msgid "Custom URL" +msgstr "URL personnalisé" + +#: _widgets.php:52 +msgid "Semi-custom" +msgstr "Semi-personnalisé" + +#: _widgets.php:56 +msgid "Semi-custom prefix: (only if you want limit to a particular prefix)" +msgstr "Préfixe des liens semi-personnalisé : (Seulement si vous voulez limiter à un préfixe particulier)" + +#: _widgets.php:60 +msgid "Sort by:" +msgstr "Trier par :" + +#: _widgets.php:62 +msgid "Rank" +msgstr "Rang" + +#: _widgets.php:63 +#: inc/index.links.php:34 +msgid "Hash" +msgstr "Hash" + +#: _widgets.php:73 +msgid "Limit:" +msgstr "Limite :" + +#: _widgets.php:76 +msgid "Hide no followed links" +msgstr "Cacher les liens non suivis" + +#: _widgets.php:102 +#: inc/index.link.php:122 +msgid "Long link:" +msgstr "Lien long :" + +#: _widgets.php:109 +msgid "Create" +msgstr "Créer" + +#: inc/index.link.php:31 +msgid "There is nothing to shorten." +msgstr "I n'y a rien à réduire." + +#: inc/index.link.php:37 +msgid "This service does not allowed custom hash." +msgstr "Ce service n'accepte pas les liens court personnalisés." + +#: inc/index.link.php:40 +msgid "This link is not a valid URL." +msgstr "Ce lien n'est pas valide." + +#: inc/index.link.php:55 +msgid "This custom short url is already taken." +msgstr "Ce lien court personnalisé est déjà pris." + +#: inc/index.link.php:76 +msgid "Failed to create short link. This could be caused by a service failure." +msgstr "Impossible de créé un lien court. Ceci peut être causé par un problème du service." + +#: inc/index.link.php:92 +#: inc/lib.wiki.kutrl.php:73 +msgid "New short URL" +msgstr "Nouvelle URL courte" + +#: inc/index.link.php:109 +#: inc/index.links.php:208 +#: inc/index.service.php:48 +#: inc/index.setting.php:80 +msgid "New link" +msgstr "Nouveau lien" + +#: inc/index.link.php:114 +msgid "You must set an admin service." +msgstr "Vous devez définir un service admin." + +#: inc/index.link.php:121 +msgid "Shorten link using service \"%s\"" +msgstr "Raccourcir un lien en utilisant le service \"%s\"" + +#: inc/index.link.php:131 +msgid "Only if you want a custom short link." +msgstr "Uniquement si vous souhaitez un lien court personnalisé." + +#: inc/index.link.php:136 +msgid "You can use \"bob!!\" if you want a semi-custom link, it starts with \"bob\" and \"!!\" will be replaced by an increment value." +msgstr "Vous pouvez utiliser \"bob!!\" si vous souhaitez un lien semi-personnalisé, il commencera par \"bob\" et \"!!\" sera remplacé par une valeur incrémentale." + +#: inc/index.links.php:23 +msgid "No short link" +msgstr "Pas de lien court" + +#: inc/index.links.php:37 +msgid "Service" +msgstr "Service" + +#: inc/index.links.php:116 +msgid "Long link" +msgstr "Lien long" + +#: inc/index.links.php:131 +#: inc/index.setting.php:65 +msgid "Disabled" +msgstr "Désactiver" + +#: inc/index.links.php:221 +msgid "Service:" +msgstr "Service :" + +#: inc/index.links.php:251 +msgid "Delete selected short links" +msgstr "Effacer les liens sélectionnés" + +#: inc/index.service.php:47 +#: index.php:40 +msgid "Services" +msgstr "Services" + +#: inc/index.service.php:62 +msgid "%s API is well configured and runing." +msgstr "L'API %s est correctement configurée et est fonctionnelle." + +#: inc/index.service.php:63 +msgid "Failed to test %s API." +msgstr "Impossible de tester l'API %s." + +#: inc/index.service.php:72 +msgid "homepage" +msgstr "page d'accueil" + +#: inc/index.service.php:72 +msgid "Learn more about %s." +msgstr "En savoir plus à propos de %s." + +#: inc/index.setting.php:79 +#: index.php:39 +msgid "Settings" +msgstr "Paramètres" + +#: inc/index.setting.php:84 +msgid "Plugin activation" +msgstr "Activation de l'extension" + +#: inc/index.setting.php:87 +msgid "Enable plugin" +msgstr "Activer l'extension" + +#: inc/index.setting.php:90 +msgid "General rules" +msgstr "Réglages" + +#: inc/index.setting.php:93 +msgid "Allow short link for external URL" +msgstr "Autoriser les liens court sur des URLs externes" + +#: inc/index.setting.php:94 +msgid "Not only link started with this blog URL could be shortened." +msgstr "Ne pas limiter la création de liens courts aux liens commençant par l'URL du blog." + +#: inc/index.setting.php:97 +msgid "Passive mode" +msgstr "Mode passif" + +#: inc/index.setting.php:98 +msgid "If this extension is disabled and the passive mode is enabled, \"kutrl\" tags (like EntryKurl) will display long urls instead of nothing on templates." +msgstr "Si cette extension est désactivée et que le mode passif est activé, les balises \"kutrl\" (comme EntryKutrl) afficheront les liens longs au lieu de rien." + +#: inc/index.setting.php:101 +msgid "Active mode" +msgstr "Mode actif" + +#: inc/index.setting.php:102 +msgid "If the active mode is enabled, all know default template tags (like EntryURL) will display short urls instead of long ones on templates." +msgstr "Si le mode actif est acitvé, les balises de thème (comme EntryURL) afficheront les liens court au lieu des longs." + +#: inc/index.setting.php:103 +msgid "You can disable URL shortening for a specific template tag by adding attribute disable_kutrl=\"1\" to it." +msgstr "Vous pouvez désactiver la réduction d'URL pour une balise de template spécifique en lui ajoutant l'attribut disable_kutrl=\"1\"" + +#: inc/index.setting.php:106 +msgid "Create short link for new entries" +msgstr "Créer un lien court pour les nouveaux billets" + +#: inc/index.setting.php:107 +msgid "This can be changed on page of creation/edition of an entry." +msgstr "Ceci peut être changé sur la page d'édition / création d'un billet." + +#: inc/index.setting.php:110 +msgid "Default services" +msgstr "Services par défaut" + +#: inc/index.setting.php:117 +msgid "Administration:" +msgstr "Administration :" + +#: inc/index.setting.php:120 +msgid "Service to use in this admin page and on edit page of an entry." +msgstr "Service à utiliser sur cette page d'administration ou sur la page d'édition d'un billet." + +#: inc/index.setting.php:127 +msgid "Extensions:" +msgstr "Extensions :" + +#: inc/index.setting.php:130 +msgid "Service to use on third part plugins." +msgstr "Service à utiliser par les plugins tiers" + +#: inc/index.setting.php:137 +msgid "Templates:" +msgstr "Templates :" + +#: inc/index.setting.php:140 +msgid "Shorten links automatically when using template value like \"EntryKutrl\"." +msgstr "Réduit automatiquement les liens des templates utilisant les balises telles que \"EntryKutrl\"." + +#: inc/index.setting.php:147 +msgid "Contents:" +msgstr "Contenus :" + +#: inc/index.setting.php:150 +msgid "Shorten links automatically found in contents using wiki synthax." +msgstr "Réduit automatiquement les liens des contenus utilisant la syntax wiki." + +#: inc/lib.kutrl.activityreport.php:17 +msgid "Plugin kUtRL" +msgstr "Extension kUtRL" + +#: inc/lib.kutrl.activityreport.php:23 +msgid "Short link creation" +msgstr "Création de lien court" + +#: inc/lib.kutrl.activityreport.php:24 +msgid "New short link of type \"%s\" and hash \"%s\" was created." +msgstr "Un nouveau lien court de type \"%s\" et de hash \"%s\" a été créé." + +#: inc/lib.kutrl.srv.php:81 +#: inc/services/class.default.service.php:42 +msgid "There is nothing to configure for this service." +msgstr "Il n'y a rien à configurer pour ce service." + +#: inc/lib.wiki.kutrl.php:67 +msgid "%s (Shorten with %s)" +msgstr "% (réduit avec %s)" + +#: inc/services/class.bilbolinks.service.php:49 +#: inc/services/class.yourls.service.php:54 +msgid "Url of the service:" +msgstr "URL du service :" + +#: inc/services/class.bilbolinks.service.php:53 +msgid "This is the root URL of the \"bilbolinks\" service you want to use. Ex: \"http://tux-pla.net/\"." +msgstr "Ceci est l'URL du service bilbolinks que vous souhaitez utiliser. Ex: \"http://tux-pla.net/\"." + +#: inc/services/class.bilbolinks.service.php:68 +#: inc/services/class.bilbolinks.service.php:80 +#: inc/services/class.custom.service.php:95 +#: inc/services/class.custom.service.php:108 +#: inc/services/class.default.service.php:66 +#: inc/services/class.default.service.php:79 +#: inc/services/class.isgd.service.php:32 +#: inc/services/class.isgd.service.php:44 +#: inc/services/class.shortto.service.php:32 +#: inc/services/class.shortto.service.php:44 +#: inc/services/class.trim.service.php:79 +#: inc/services/class.trim.service.php:99 +#: inc/services/class.yourls.service.php:87 +#: inc/services/class.yourls.service.php:107 +msgid "Service is unavailable." +msgstr "Le service n'est pas disponible." + +#: inc/services/class.bilbolinks.service.php:85 +#: inc/services/class.trim.service.php:110 +msgid "Service rate limit exceeded." +msgstr "La limitation d'envoie au service est atteinte." + +#: inc/services/class.bitly.service.php:51 +#: inc/services/class.supr.service.php:49 +#: inc/services/class.trim.service.php:51 +#: inc/services/class.yourls.service.php:60 +msgid "Login:" +msgstr "Identifiant :" + +#: inc/services/class.bitly.service.php:55 +#: inc/services/class.supr.service.php:53 +msgid "This is your login to sign up to %s" +msgstr "C'est votre identifiant pour vous connecter sur %s" + +#: inc/services/class.bitly.service.php:57 +#: inc/services/class.supr.service.php:55 +msgid "API Key:" +msgstr "Clé API :" + +#: inc/services/class.bitly.service.php:61 +#: inc/services/class.supr.service.php:59 +msgid "This is your personnal %s API key. You can find it on your account page." +msgstr "C'est votre clé personnelle de l'API %s. Vous pouvez la trouver sur la page de votre compte." + +#: inc/services/class.bitly.service.php:65 +msgid "Publish history" +msgstr "Publier l'historique" + +#: inc/services/class.bitly.service.php:68 +msgid "This publish all short links on your bit.ly public page." +msgstr "Ceci publie tous vos liens sur votre page public bit.ly" + +#: inc/services/class.bitly.service.php:84 +#: inc/services/class.bitly.service.php:106 +#: inc/services/class.googl.service.php:40 +#: inc/services/class.googl.service.php:61 +#: inc/services/class.supr.service.php:75 +#: inc/services/class.supr.service.php:98 +msgid "Failed to call service." +msgstr "Impossible d'appeler le service." + +#: inc/services/class.bitly.service.php:93 +#: inc/services/class.bitly.service.php:115 +#: inc/services/class.supr.service.php:85 +#: inc/services/class.supr.service.php:108 +msgid "An error occured with code %s and message \"%s\"" +msgstr "Une erreur est survenu avec le code \"%s\" et le message \"%s\"" + +#: inc/services/class.custom.service.php:65 +msgid "You can set a configurable service." +msgstr "Vous pouvez configurer un service particulier." + +#: inc/services/class.custom.service.php:66 +msgid "It consists on a simple query to an URL with only one param." +msgstr "Il effectue une simple requête à une URL avec un seul paramètre." + +#: inc/services/class.custom.service.php:67 +msgid "It must respond with a http code 200 on success." +msgstr "Il doit répondre avec un code HTTP de 200 en cas de succès" + +#: inc/services/class.custom.service.php:68 +msgid "It must returned the short URL (or only hash) in clear text." +msgstr "Il doit retourner l'URL courte (ou seulement le hash) en texte clair." + +#: inc/services/class.custom.service.php:69 +msgid "API URL:" +msgstr "URL de l'API:" + +#: inc/services/class.custom.service.php:72 +msgid "Full path to API of the URL shortener. ex: \"http://is.gd/api.php\"" +msgstr "Chemin complet vers l'API. ex: \"http://is.gd.php\"" + +#: inc/services/class.custom.service.php:73 +#: inc/services/class.default.service.php:52 +msgid "Short URL domain:" +msgstr "Domaine des URLs courtes:" + +#: inc/services/class.custom.service.php:76 +msgid "Common part of the short URL. ex: \"http://is.gd/\"" +msgstr "Partie commune aux URLs courtes. ex: \"http://is.gd/\"" + +#: inc/services/class.custom.service.php:77 +msgid "API URL param:" +msgstr "Paramètre de l'URL" + +#: inc/services/class.custom.service.php:80 +msgid "Param of the query. ex: \"longurl\"" +msgstr "Paramètre de la raquête. ex: \"longurl\"" + +#: inc/services/class.custom.service.php:83 +msgid "Encode URL" +msgstr "Encoder l'URL à raccourcir" + +#: inc/services/class.default.service.php:44 +msgid "This service is set to:" +msgstr "Ce service est règlé comme suit:" + +#: inc/services/class.default.service.php:48 +msgid "Full API URL:" +msgstr "URL complet vers l'API:" + +#: inc/services/class.default.service.php:50 +msgid "Query param:" +msgstr "Paramètre de la requête:" + +#: inc/services/class.default.service.php:54 +msgid "Encode URL:" +msgstr "Encodage des URLs à raccourcir:" + +#: inc/services/class.local.service.php:48 +msgid "Settings:" +msgstr "Paramètres :" + +#: inc/services/class.local.service.php:50 +msgid "Allowed protocols:" +msgstr "Protocoles autorisés :" + +#: inc/services/class.local.service.php:55 +msgid "Use comma seperated list like: \"http:,https:,ftp:\"" +msgstr "Utiliser une virgule pour séparer la liste des protocoles. Ex: \"http:,https:,ftp:\"" + +#: inc/services/class.local.service.php:60 +msgid "Enable public page for visitors to shorten links" +msgstr "Activer la page publique pour que les visiteurs puissent réduire des liens" + +#: inc/services/class.local.service.php:63 +msgid "CSS:" +msgstr "CSS :" + +#: inc/services/class.local.service.php:66 +msgid "You can add here special cascading style sheet. Body of page has class \"dc-kutrl\" and widgets have class \"shortenkutrlwidget\" and \"rankkutrlwidget\"." +msgstr "Vous pouvez ajouter des styles ici. La balise \"body\" a la class \"dc-kutrl\" et les widgets ont les class \"shortenkutrlwidget\" et \"rankkutrlwidget\"." + +#: inc/services/class.local.service.php:70 +msgid "Enable special 404 error public page for unknow urls" +msgstr "Activer la page spéciale d'erreur 404 pour les liens inconnus" + +#: inc/services/class.local.service.php:72 +msgid "If this is not activated, the default 404 page of the theme will be display." +msgstr "Si cette option est désactivée, la page d'erreur 404 par défaut du thème sera utilisée." + +#: inc/services/class.local.service.php:76 +msgid "Note:" +msgstr "Note :" + +#: inc/services/class.local.service.php:78 +msgid "This service use your own Blog to shorten and serve URL." +msgstr "Ce service utilise votre propre blog pour réduire et servir des liens." + +#: inc/services/class.local.service.php:79 +msgid "This means that with this service short links start with \"%s\"." +msgstr "Cela signifie qu'avec ce service vos liens courts commencent par \"%s\"." + +#: inc/services/class.local.service.php:82 +msgid "You can use Dotclear's plugin called myUrlHandlers to change short links prefix on your blog." +msgstr "Vous pouvez utiliser l'extension myUrlHandlers pour Dotclear afin de changer le prefix de vos liens courts depuis votre blog." + +#: inc/services/class.local.service.php:88 +msgid "We recommand that you use a rewrite engine in order to remove 'index.php' from your blog's URL." +msgstr "Nous vous recommandons d'utiliser la réécriture d'URL pour supprimer 'index.php de l'URL de votre blog." + +#: inc/services/class.local.service.php:90 +msgid "You can find more about this on the Dotclear's documentation." +msgstr "Vous trouverez plus d'information à ce sujet dans la documentation Dotclear." + +#: inc/services/class.local.service.php:95 +msgid "There are two templates delivered with kUtRL, if you do not use default theme, you may adapt them to yours." +msgstr "Il y a deux templates livrés avec kUtRL, si vous n'utilisez pas le thème par défaut, vous devrez peut-être les adapter au votre." + +#: inc/services/class.local.service.php:96 +msgid "Files are in plugin directory /default-templates, just copy them into your theme and edit them." +msgstr "Les fichiers sont dans le répertoire /default-templates du plugin, copiez les dans votre thème et modifiez les." + +#: inc/services/class.local.service.php:139 +msgid "Custom short link is already taken." +msgstr "Le lien court personnalisé est déjà pris." + +#: inc/services/class.local.service.php:149 +msgid "Custom short link is not valid." +msgstr "Le lien court personnalisé n'est pas valide." + +#: inc/services/class.local.service.php:160 +msgid "Failed to save link." +msgstr "Impossible d'enregistrer le lien." + +#: inc/services/class.trim.service.php:55 +msgid "This is your login to sign up to tr.im." +msgstr "Ceci est votre login d'inscription sur tr.im." + +#: inc/services/class.trim.service.php:61 +msgid "This is your password to sign up to tr.im." +msgstr "Ceci est votre mot de passe d'inscription sur tr.im." + +#: inc/services/class.trim.service.php:74 +msgid "Prevent service rate limit." +msgstr "Prévention de la limitation d'envoie du service." + +#: inc/services/class.trim.service.php:88 +#: inc/services/class.yourls.service.php:96 +msgid "Authentication to service failed." +msgstr "Authentification au service échoué." + +#: inc/services/class.trim.service.php:122 +#: inc/services/class.yourls.service.php:122 +msgid "Unreadable service response." +msgstr "La réponse du service n'est pas lisible." + +#: inc/services/class.yourls.service.php:58 +msgid "This is the URL of the YOURLS service you want to use. Ex: \"http://www.smaller.org/api.php\"." +msgstr "Ceci est l'URL du service YOURLS que vous voulez utiliser. Ex: \"http://www.smaller.org/api.php\"." + +#: inc/services/class.yourls.service.php:64 +msgid "This is your user name to sign up to this YOURLS service." +msgstr "Ceci est votre nom d'utilisateur pour vous connecter à ce service YOURLS." + +#: inc/services/class.yourls.service.php:70 +msgid "This is your password to sign up to this YOURLS service." +msgstr "Ceci est votre mot de passe pour vous connecter à ce service YOURLS." + +#: index.php:31 +msgid "Please wait" +msgstr "Veuillez patienter" + +#: index.php:39 +msgid "Configure extension" +msgstr "Configurer l'extension" + +#: index.php:40 +msgid "Configure services" +msgstr "Configurer les services" + +#: index.php:52 +msgid "Configuration successfully saved" +msgstr "Configuration sauvegardée avec succès" + +#: index.php:53 +msgid "Services successfully updated" +msgstr "Services mises à jour avec succès" + +#: index.php:54 +msgid "Link successfully shorten" +msgstr "Lien raccourcie avec succès" + +#: index.php:55 +msgid "Links successfully deleted" +msgstr "Liens supprimés avec succès" + +msgid "This is your login to sign up to bit.ly." +msgstr "Ceci est votre login d'inscription sur bit.ly." + +msgid "This is your personnal bit.ly API key. You can find it on your account page." +msgstr "Ceci est votre clé personnelle pour l'API bit.ly. Vous pouvez la trouver sur la page de vore compte." + diff --git a/locales/fr/resources.php b/locales/fr/resources.php new file mode 100644 index 0000000..8f6f377 --- /dev/null +++ b/locales/fr/resources.php @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/release.txt b/release.txt new file mode 100644 index 0000000..1044682 --- /dev/null +++ b/release.txt @@ -0,0 +1,85 @@ +todo + - Added public page of the list of know urls and in/visible status + - Added passworded links + +2011.04.01 + * Changed version numbering + * Added service su.pr (stumbleUpon) + * Fixed wiki settings (thanks @ploum ) + +1.0 20110213 + * Added generic class to easlily access services + * Changed generic service class (and class extend it) + * Fixed config of default service + * Fixed display of admin fake section + * Added checkbox helpers on admin + +0.6.1 20110130 + * Cleaned up script + +0.6 20110118 'prepare the soCial révolution' + * Added default defined service (for all blogs of a multiblog) + * Added configurable external service + * Added goog.gl client service (first step) + * Added default settings for third part plugins + * Added behaviors after short link creation + * Added attribute to disable URL shortining on template tag with 'active mode' (fixed bug on URL of POST form) + * Remove all messenger functions: this is to another plugin to do that + * Remove priority in plugin definition + +0.5 20100909 + * Removed old Twitter functions + * Added StatusNet small functions (Identica) + * Required plugin Tac for Twitter ability + * Added YOURLS client service + +0.4.2 20100809 + * Fixed bug on dcTwitter shorten service + * Fixed bug on custom local link + * Added category URL to active mode + * Added priority to plugin definition + +0.4.1 20100701 + * Fixed multiple bugs + +0.4 20100628 + * Switched to DC 2.2 + * Fixed no short urls on preview mode + * Fixed lock hash of deleted urls + * Fixed hide new short url widget on kutrl pages + * Fixed typo + * Added active mode that shorten urls on default template values + * Added special tweeter message for post (can include post title) + * Added kutrl special 404 page + +0.3.3 20100528 + * Fixed settings in tweeter class + * Renamed tweeter class + +0.3.2 20100525 + * FIxed minor bugs + * Fixed DC 2.1.7 + +0.3 20100414 + * Added DC 2.2 compatibility (new settings) + * Added semi-custom hash on kUtRL service + * Added status update for Twitter/Identi.ca on new short link + * Added services error management (first step) + * Added options to widgets + * Upgraded bitly service to v3 + * Changed admin design + +0.2 20091223 + * Fixed public redirection with suffix + * Added short.to service + +0.1.2 20091212 + * Added option to short url of new entry by default + * Fixed typo + +0.1.1 20091212 + * Added option to display long url when unactive + * Fixed support of kutrl in feeds + +0.1 20091209 + * First lab release \ No newline at end of file